| import streamlit as st |
| import tensorflow as tf |
| import numpy as np |
| import requests |
| import zipfile |
| import os |
| import pickle |
|
|
|
|
| |
| GITHUB_REPO_OWNER = "HeshamSaadi" |
| GITHUB_REPO_NAME = "sentiment-app" |
| GITHUB_RELEASE_TAG = "v1.0" |
| GITHUB_MODEL_ZIP_NAME = "models.zip" |
|
|
| |
| MODEL_FILE_NAME = "simplified_lstm_20250622-170533_final.keras" |
| TOKENIZER_FILE_NAME = "simplified_lstm_20250622-170533_tokenizer.pickle" |
| LABEL_MAPPING_FILE_NAME = "simplified_lstm_20250622-170533_label_mapping.pickle" |
|
|
| |
| class SimpleAttention(tf.keras.layers.Layer): |
| def __init__(self, **kwargs): |
| super(SimpleAttention, self).__init__(**kwargs) |
|
|
| def build(self, input_shape): |
| self.W = self.add_weight( |
| name="attention_weight", |
| shape=(input_shape[-1], 1), |
| initializer="glorot_uniform", |
| trainable=True |
| ) |
| super(SimpleAttention, self).build(input_shape) |
|
|
| def call(self, inputs): |
| |
|
|
| |
| e = tf.keras.backend.tanh(tf.keras.backend.dot(inputs, self.W)) |
| e = tf.keras.backend.squeeze(e, axis=-1) |
|
|
| |
| alpha = tf.keras.backend.softmax(e) |
|
|
| |
| output = inputs * tf.keras.backend.expand_dims(alpha, axis=-1) |
| output = tf.keras.backend.sum(output, axis=1) |
| return output |
|
|
| def get_config(self): |
| config = super(SimpleAttention, self).get_config() |
| return config |
|
|
| |
| class FocalLoss(tf.keras.losses.Loss): |
| def __init__(self, gamma=2.0, alpha=0.25, name="focal_loss", **kwargs): |
| super(FocalLoss, self).__init__(name=name, **kwargs) |
| self.gamma = gamma |
| self.alpha = alpha |
|
|
| def call(self, y_true, y_pred): |
| y_pred = tf.clip_by_value(y_pred, tf.keras.backend.epsilon(), 1. - tf.keras.backend.epsilon()) |
| |
| |
| pt = tf.where(tf.equal(y_true, tf.cast(1, dtype=y_true.dtype)), y_pred, tf.cast(1, dtype=y_true.dtype) - y_pred) |
| loss = -tf.keras.backend.mean(self.alpha * tf.keras.backend.pow(tf.cast(1.0, dtype=pt.dtype) - pt, self.gamma) * tf.keras.backend.log(pt), axis=-1) |
| return loss |
|
|
| def get_config(self): |
| config = super(FocalLoss, self).get_config() |
| config.update({ |
| "gamma": self.gamma, |
| "alpha": self.alpha, |
| }) |
| return config |
|
|
| @st.cache_resource |
| def load_model_and_tokenizer(): |
| model = None |
| tokenizer = None |
| label_mapping = None |
|
|
| st.write("Extracting model from local models.zip...") |
| try: |
| zip_path = GITHUB_MODEL_ZIP_NAME |
| if not os.path.exists(zip_path): |
| st.error(f"Model zip file not found at {zip_path}. Please ensure models.zip is in the root of your Hugging Face Space.") |
| return None, None, None |
|
|
| with zipfile.ZipFile(zip_path, "r") as zip_ref: |
| zip_ref.extractall(".") |
| st.write("Extraction complete.") |
|
|
| |
| |
| model_path = os.path.join("models", MODEL_FILE_NAME) |
| tokenizer_path = os.path.join("models", TOKENIZER_FILE_NAME) |
| label_mapping_path = os.path.join("models", LABEL_MAPPING_FILE_NAME) |
|
|
| |
| custom_objects = { |
| "FocalLoss": FocalLoss, |
| "SimpleAttention": SimpleAttention, |
| } |
|
|
| |
| with tf.keras.utils.custom_object_scope(custom_objects): |
| model = tf.keras.models.load_model(model_path, compile=False) |
|
|
| |
| |
|
|
| with open(tokenizer_path, "rb") as handle: |
| tokenizer = pickle.load(handle) |
| with open(label_mapping_path, "rb") as handle: |
| label_mapping = pickle.load(handle) |
|
|
| st.success("Model and tokenizer loaded successfully!") |
|
|
| except FileNotFoundError as e: |
| st.error(f"File not found after extraction: {e}. Please check the paths within your zip file.") |
| except zipfile.BadZipFile: |
| st.error("Downloaded file is not a valid zip file.") |
| except Exception as e: |
| st.error(f"An unexpected error occurred: {e}") |
| st.error("Could not load the model. Please check the model files and paths.") |
|
|
| return model, tokenizer, label_mapping |
|
|
| model, tokenizer, label_mapping = load_model_and_tokenizer() |
|
|
| if model and tokenizer and label_mapping: |
| st.title("Sentiment Analysis App") |
|
|
| user_input = st.text_area("Enter text for sentiment analysis:", "") |
|
|
| if st.button("Analyze Sentiment"): |
| if user_input: |
| |
| |
| |
| |
|
|
| |
| |
| |
| sequence = tokenizer.texts_to_sequences([user_input]) |
| padded_sequence = tf.keras.preprocessing.sequence.pad_sequences(sequence, maxlen=model.input_shape[1]) |
|
|
| |
| prediction = model.predict(padded_sequence) |
| st.write(f"Raw prediction probabilities: {prediction}") |
| predicted_class = np.argmax(prediction, axis=1)[0] |
| st.write(f"Predicted class index: {predicted_class}") |
|
|
| |
| sentiment_labels = {v: k for k, v in label_mapping.items()} |
| predicted_sentiment = sentiment_labels.get(predicted_class, "Unknown") |
|
|
| st.write(f"Sentiment: **{predicted_sentiment}**") |
| else: |
| st.warning("Please enter some text to analyze.") |
| else: |
| st.warning("Model could not be loaded. Please check the logs above for details.") |
|
|