Hesham99's picture
Update app.py
8d0237d verified
Raw
History Blame Contribute Delete
6.75 kB
import streamlit as st
import tensorflow as tf
import numpy as np
import requests
import zipfile
import os
import pickle
# GitHub repository details (no longer used for direct download, but kept for context if needed)
GITHUB_REPO_OWNER = "HeshamSaadi"
GITHUB_REPO_NAME = "sentiment-app"
GITHUB_RELEASE_TAG = "v1.0" # Assuming this is the tag for your release
GITHUB_MODEL_ZIP_NAME = "models.zip"
# Paths for model and tokenizer within the extracted zip
MODEL_FILE_NAME = "simplified_lstm_20250622-170533_final.keras" # Updated to the correct filename
TOKENIZER_FILE_NAME = "simplified_lstm_20250622-170533_tokenizer.pickle"
LABEL_MAPPING_FILE_NAME = "simplified_lstm_20250622-170533_label_mapping.pickle"
# Simple Attention Layer compatible with all Keras versions
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):
# inputs shape: (batch_size, seq_len, features)
# Attention scores - using Keras backend operations
e = tf.keras.backend.tanh(tf.keras.backend.dot(inputs, self.W))
e = tf.keras.backend.squeeze(e, axis=-1)
# Attention weights
alpha = tf.keras.backend.softmax(e)
# Apply attention weights to inputs
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
# Custom FocalLoss class from the notebook
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())
# Ensure 1 is a tensor for tf.where comparison
# Use tf.cast to ensure dtype consistency for 1 and 1.0
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.")
# Define paths to the extracted files
# Assuming the zip extracts to a \"models\" directory at the root
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 dictionary for model loading
custom_objects = {
"FocalLoss": FocalLoss,
"SimpleAttention": SimpleAttention,
}
# Attempt to load the model with compile=False
with tf.keras.utils.custom_object_scope(custom_objects):
model = tf.keras.models.load_model(model_path, compile=False)
# If the model loads successfully, it can be used for prediction directly.
# Recompilation is not strictly necessary for inference.
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:
# Preprocess the input text (you might need to adapt this based on your model\"s preprocessing)
# For an LSTM, typically tokenization and padding are needed
# Ensure the tokenizer is fitted on the same vocabulary as during training
# and the max_len matches your model\"s input shape.
# Example preprocessing (adjust as per your model\"s requirements):
# Assuming your tokenizer expects text and outputs sequences
# And your model expects padded sequences
sequence = tokenizer.texts_to_sequences([user_input])
padded_sequence = tf.keras.preprocessing.sequence.pad_sequences(sequence, maxlen=model.input_shape[1])
# Make prediction
prediction = model.predict(padded_sequence)
st.write(f"Raw prediction probabilities: {prediction}") # Added for debugging
predicted_class = np.argmax(prediction, axis=1)[0]
st.write(f"Predicted class index: {predicted_class}") # Added for debugging
# Map prediction to sentiment label
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.")