commit fb74f22df28e721458179f09f311500f2308b58a
parent 50a36d4ff1592d2f1b3ace5b2f835d71feae512a
Author: William Lindholm <william_lindholm@outlook.com>
Date: Sat, 4 Nov 2023 19:44:55 +0100
Now using tf model to filter incoming messages.
Diffstat:
12 files changed, 79 insertions(+), 16 deletions(-)
diff --git a/MessageTagging/__init__.py b/MessageTagging/__init__.py
diff --git a/MessageTagging/saved_model/fingerprint.pb b/MessageTagging/saved_model/fingerprint.pb
@@ -0,0 +1 @@
+٬ʑi曳؊ƴ D(Е2
+\ No newline at end of file
diff --git a/MessageTagging/saved_model/keras_metadata.pb b/MessageTagging/saved_model/keras_metadata.pb
@@ -0,0 +1,8 @@
+
+*root"_tf_keras_sequential**{"name": "sequential", "trainable": true, "expects_training_arg": true, "dtype": "float32", "batch_input_shape": null, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": false, "class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 5530]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "embedding_input"}}, {"class_name": "Embedding", "config": {"name": "embedding", "trainable": true, "dtype": "float32", "batch_input_shape": {"class_name": "__tuple__", "items": [null, 5530]}, "input_dim": 57562, "output_dim": 16, "embeddings_initializer": {"class_name": "RandomUniform", "config": {"minval": -0.05, "maxval": 0.05, "seed": null}}, "embeddings_regularizer": null, "activity_regularizer": null, "embeddings_constraint": null, "mask_zero": false, "input_length": 5530}}, {"class_name": "GlobalAveragePooling1D", "config": {"name": "global_average_pooling1d", "trainable": true, "dtype": "float32", "data_format": "channels_last", "keepdims": false}}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 16, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 1, "activation": "sigmoid", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}]}, "shared_object_id": 10, "input_spec": [{"class_name": "InputSpec", "config": {"dtype": null, "shape": {"class_name": "__tuple__", "items": [null, 5530]}, "ndim": 2, "max_ndim": null, "min_ndim": null, "axes": {}}}], "build_input_shape": {"class_name": "TensorShape", "items": [null, 5530]}, "is_graph_network": true, "full_save_spec": {"class_name": "__tuple__", "items": [[{"class_name": "TypeSpec", "type_spec": "tf.TensorSpec", "serialized": [{"class_name": "TensorShape", "items": [null, 5530]}, "float32", "embedding_input"]}], {}]}, "save_spec": {"class_name": "TypeSpec", "type_spec": "tf.TensorSpec", "serialized": [{"class_name": "TensorShape", "items": [null, 5530]}, "float32", "embedding_input"]}, "keras_version": "2.14.0", "backend": "tensorflow", "model_config": {"class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": {"class_name": "__tuple__", "items": [null, 5530]}, "dtype": "float32", "sparse": false, "ragged": false, "name": "embedding_input"}, "shared_object_id": 0}, {"class_name": "Embedding", "config": {"name": "embedding", "trainable": true, "dtype": "float32", "batch_input_shape": {"class_name": "__tuple__", "items": [null, 5530]}, "input_dim": 57562, "output_dim": 16, "embeddings_initializer": {"class_name": "RandomUniform", "config": {"minval": -0.05, "maxval": 0.05, "seed": null}, "shared_object_id": 1}, "embeddings_regularizer": null, "activity_regularizer": null, "embeddings_constraint": null, "mask_zero": false, "input_length": 5530}, "shared_object_id": 2}, {"class_name": "GlobalAveragePooling1D", "config": {"name": "global_average_pooling1d", "trainable": true, "dtype": "float32", "data_format": "channels_last", "keepdims": false}, "shared_object_id": 3}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 16, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 4}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 5}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 6}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 1, "activation": "sigmoid", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 7}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 8}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 9}]}}, "training_config": {"loss": "binary_crossentropy", "metrics": [[{"class_name": "MeanMetricWrapper", "config": {"name": "accuracy", "dtype": "float32", "fn": "binary_accuracy"}, "shared_object_id": 12}]], "weighted_metrics": null, "loss_weights": null, "optimizer_config": {"class_name": "Custom>Adam", "config": {"name": "Adam", "weight_decay": null, "clipnorm": null, "global_clipnorm": null, "clipvalue": null, "use_ema": false, "ema_momentum": 0.99, "ema_overwrite_frequency": null, "jit_compile": false, "is_legacy_optimizer": false, "learning_rate": 0.0010000000474974513, "beta_1": 0.9, "beta_2": 0.999, "epsilon": 1e-07, "amsgrad": false}}}}2
+root.layer_with_weights-0"_tf_keras_layer*{"name": "embedding", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": {"class_name": "__tuple__", "items": [null, 5530]}, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": false, "class_name": "Embedding", "config": {"name": "embedding", "trainable": true, "dtype": "float32", "batch_input_shape": {"class_name": "__tuple__", "items": [null, 5530]}, "input_dim": 57562, "output_dim": 16, "embeddings_initializer": {"class_name": "RandomUniform", "config": {"minval": -0.05, "maxval": 0.05, "seed": null}, "shared_object_id": 1}, "embeddings_regularizer": null, "activity_regularizer": null, "embeddings_constraint": null, "mask_zero": false, "input_length": 5530}, "shared_object_id": 2, "build_input_shape": {"class_name": "TensorShape", "items": [null, 5530]}}2
+root.layer-1"_tf_keras_layer*{"name": "global_average_pooling1d", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "GlobalAveragePooling1D", "config": {"name": "global_average_pooling1d", "trainable": true, "dtype": "float32", "data_format": "channels_last", "keepdims": false}, "shared_object_id": 3, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": 3, "max_ndim": null, "min_ndim": null, "axes": {}}, "shared_object_id": 13}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 5530, 16]}}2
+root.layer_with_weights-1"_tf_keras_layer*{"name": "dense", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 16, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 4}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 5}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 6, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 2, "axes": {"-1": 16}}, "shared_object_id": 14}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16]}}2
+root.layer_with_weights-2"_tf_keras_layer*{"name": "dense_1", "trainable": true, "expects_training_arg": false, "dtype": "float32", "batch_input_shape": null, "stateful": false, "must_restore_from_config": false, "preserve_input_structure_in_config": false, "autocast": true, "class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 1, "activation": "sigmoid", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}, "shared_object_id": 7}, "bias_initializer": {"class_name": "Zeros", "config": {}, "shared_object_id": 8}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "shared_object_id": 9, "input_spec": {"class_name": "InputSpec", "config": {"dtype": null, "shape": null, "ndim": null, "max_ndim": null, "min_ndim": 2, "axes": {"-1": 16}}, "shared_object_id": 15}, "build_input_shape": {"class_name": "TensorShape", "items": [null, 16]}}2
+Xroot.keras_api.metrics.0"_tf_keras_metric*{"class_name": "Mean", "name": "loss", "dtype": "float32", "config": {"name": "loss", "dtype": "float32"}, "shared_object_id": 16}2
+Yroot.keras_api.metrics.1"_tf_keras_metric*{"class_name": "MeanMetricWrapper", "name": "accuracy", "dtype": "float32", "config": {"name": "accuracy", "dtype": "float32", "fn": "binary_accuracy"}, "shared_object_id": 12}2
+\ No newline at end of file
diff --git a/MessageTagging/saved_model/saved_model.pb b/MessageTagging/saved_model/saved_model.pb
Binary files differ.
diff --git a/MessageTagging/saved_model/variables/variables.data-00000-of-00001 b/MessageTagging/saved_model/variables/variables.data-00000-of-00001
Binary files differ.
diff --git a/MessageTagging/saved_model/variables/variables.index b/MessageTagging/saved_model/variables/variables.index
Binary files differ.
diff --git a/MessageTagging/spam_detector.py b/MessageTagging/spam_detector.py
@@ -0,0 +1,42 @@
+import pickle
+import os
+import tensorflow as tf
+from keras.preprocessing.sequence import pad_sequences
+
+
+class SpamDetector:
+ def __init__(self):
+ current_dir = os.getcwd()
+ model_save_path = os.path.join(current_dir, 'MessageTagging/saved_model')
+ tokenizer_save_path = os.path.join(current_dir, 'MessageTagging/tokenizer.pickle')
+
+ self.model = self.load_model(model_save_path)
+ self.tokenizer = self.load_tokenizer(tokenizer_save_path)
+
+ @staticmethod
+ def load_model(model_save_path):
+ return tf.keras.models.load_model(model_save_path)
+
+ @staticmethod
+ def load_tokenizer(tokenizer_save_path):
+ with open(tokenizer_save_path, 'rb') as handle:
+ return pickle.load(handle)
+
+ def detect_spam(self, text):
+ sequences = self.tokenizer.texts_to_sequences([text])
+ padded_sequences = pad_sequences(sequences, padding='post', maxlen=5530)
+
+ return self.model.predict(padded_sequences)[0][0]
+
+
+if __name__ == "__main__":
+ spam_detector = SpamDetector()
+
+ prediction = spam_detector.detect_spam('Hello, here today at not a scam.cu.uk.rust create your own mlm now')
+
+ if prediction < .05:
+ print("not spam")
+ else:
+ print("spam")
+
+ print(f'Prediction: {prediction:.2f}')
+\ No newline at end of file
diff --git a/MessageTagging/tokenizer.pickle b/MessageTagging/tokenizer.pickle
Binary files differ.
diff --git a/WebAPI/routes.py b/WebAPI/routes.py
@@ -1,3 +1,4 @@
+from MessageTagging.spam_detector import SpamDetector
from . import api
from flask_restx import Resource, fields
@@ -6,6 +7,7 @@ from .mailer import Mailer
mailer = Mailer()
db_repo = DatabaseRepository()
+spam_detection = SpamDetector()
contact_fields = api.model('Contact', {
'email': fields.String(required=True),
@@ -13,7 +15,6 @@ contact_fields = api.model('Contact', {
'message_content': fields.String(required=True)
})
-
@api.route('/contact')
class contact(Resource):
@api.expect(contact_fields)
@@ -26,12 +27,16 @@ class contact(Resource):
formatted_subject = mailer.format_subject(email)
formatted_message = mailer.format_message(name, email, message_content)
- try:
- mailer.send_notification(formatted_subject, formatted_message)
- except Exception as e:
- return {"message": f"An unknown error occurred: {str(e)}"}, 500
+ prediction = spam_detection.detect_spam(message_content)
+ rounded_prediction = float(f'{prediction:.2f}')
+
+ if prediction < .05:
+ try:
+ mailer.send_notification(formatted_subject, formatted_message)
+ except Exception as e:
+ return {"message": f"An unknown error occurred: {str(e)}"}, 500
- db_repo.save_message(name, email, formatted_subject, message_content) # Save message to database
+ db_repo.save_message(name, email, formatted_subject, message_content, rounded_prediction)
return {"message": "Message sent and saved successfully"}, 200
diff --git a/WebInterface/routes.py b/WebInterface/routes.py
@@ -2,12 +2,13 @@ import json
from pathlib import Path
from flask import Flask, request, redirect, url_for, render_template, session
-from dbrepository import DatabaseRepository
+from dbrepository import DatabaseRepository
from . import web_interface
db = DatabaseRepository()
+@web_interface.route('/')
@web_interface.route('/home')
def home():
if not session.get('logged_in'):
@@ -22,7 +23,8 @@ def home():
"email": message[2],
"subject": message[3],
"message_content": message[4],
- "timestamp": message[5]
+ "timestamp": message[5],
+ "relevance": message[6]
}
for message in messages
]
@@ -49,7 +51,8 @@ def login():
return render_template('login.html')
+
@web_interface.route('/logout')
def logout():
session.pop('logged_in', None)
- return redirect(url_for('web_interface.login'))
-\ No newline at end of file
+ return redirect(url_for('web_interface.login'))
diff --git a/dbrepository.py b/dbrepository.py
@@ -17,21 +17,22 @@ class DatabaseRepository:
email TEXT NOT NULL,
subject TEXT NOT NULL,
message_content TEXT NOT NULL,
- timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
+ relevance NUMERIC
);
"""
)
conn.commit()
conn.close()
- def save_message(self, name, email, subject, message_content):
+ def save_message(self, name, email, subject, message_content, relevance):
conn = sqlite3.connect(self.DATABASE_FILE)
cursor = conn.cursor()
cursor.execute(
"""
- INSERT INTO messages (name, email, subject, message_content)
- VALUES (?, ?, ?, ?)
- """, (name, email, subject, message_content)
+ INSERT INTO messages (name, email, subject, message_content, relevance)
+ VALUES (?, ?, ?, ?, ?)
+ """, (name, email, subject, message_content, relevance)
)
conn.commit()
conn.close()
diff --git a/templates/home.html b/templates/home.html
@@ -59,7 +59,8 @@
<ul class="list-group mt-3">
{% for message in messages %}
<li class="list-group-item">
- <strong>{{ message.subject }}</strong> from {{ message.name }} ({{ message.email }}) at {{ message.timestamp }}:
+ <strong>{{ message.subject }}</strong> from {{ message.name }} ({{ message.email }})@{{ message.timestamp }}
+ relevance: {{ message.relevance }}:
<p>{{ message.message_content }}</p>
</li>
{% endfor %}