gsar78 commited on
Commit
2f153ef
·
verified ·
1 Parent(s): ff77414

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +211 -0
app.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from transformers import AutoTokenizer, AutoConfig, pipeline
3
+ import torch
4
+ import os
5
+ from torch import nn
6
+ from torch.nn import Dropout
7
+ from transformers import XLMRobertaForSequenceClassification
8
+
9
+ HF_TOKEN = os.getenv('HF_TOKEN')
10
+ hf_writer = gr.HuggingFaceDatasetSaver(HF_TOKEN, "crowdsourced-sentiment")
11
+
12
+ # Define the CustomModel class which is predicting Both SENTIMENT POLARITY & EMOTIONS
13
+ class CustomModel(XLMRobertaForSequenceClassification):
14
+ def __init__(self, config, num_emotion_labels):
15
+ super(CustomModel, self).__init__(config)
16
+ self.num_emotion_labels = num_emotion_labels
17
+ self.dropout_emotion = nn.Dropout(config.hidden_dropout_prob)
18
+ self.emotion_classifier = nn.Sequential(
19
+ nn.Linear(config.hidden_size, 512),
20
+ nn.Mish(),
21
+ nn.Dropout(0.3),
22
+ nn.Linear(512, num_emotion_labels)
23
+ )
24
+ self._init_weights(self.emotion_classifier[0])
25
+ self._init_weights(self.emotion_classifier[3])
26
+
27
+ def _init_weights(self, module):
28
+ if isinstance(module, nn.Linear):
29
+ module.weight.data.normal_(mean=0.0, std=self.config.initializer_range)
30
+ if module.bias is not None:
31
+ module.bias.data.zero_()
32
+
33
+ def forward(self, input_ids=None, attention_mask=None, sentiment=None, labels=None):
34
+ outputs = self.roberta(input_ids=input_ids, attention_mask=attention_mask)
35
+ sequence_output = outputs[0]
36
+ if len(sequence_output.shape) != 3:
37
+ raise ValueError(f"Expected sequence_output to have 3 dimensions, got {sequence_output.shape}")
38
+ cls_hidden_states = sequence_output[:, 0, :]
39
+ cls_hidden_states = self.dropout_emotion(cls_hidden_states)
40
+ emotion_logits = self.emotion_classifier(cls_hidden_states)
41
+ with torch.no_grad():
42
+ cls_token_state = sequence_output[:, 0, :].unsqueeze(1)
43
+ sentiment_logits = self.classifier(cls_token_state).squeeze(1)
44
+ if labels is not None:
45
+ class_weights = torch.tensor([1.0] * self.num_emotion_labels).to(labels.device)
46
+ loss_fct = nn.BCEWithLogitsLoss(pos_weight=class_weights)
47
+ loss = loss_fct(emotion_logits, labels)
48
+ return {"loss": loss, "emotion_logits": emotion_logits, "sentiment_logits": sentiment_logits}
49
+ return {"emotion_logits": emotion_logits, "sentiment_logits": sentiment_logits}
50
+
51
+ # Load the tokenizer and model from the local directory
52
+ model_dir = "gsar78/HellenicSentimentAI_v2"
53
+ tokenizer = AutoTokenizer.from_pretrained(model_dir)
54
+ config = AutoConfig.from_pretrained(model_dir)
55
+ model = CustomModel.from_pretrained(model_dir, config=config, num_emotion_labels=18)
56
+
57
+ # Function to predict sentiment and emotion
58
+ def predict(texts):
59
+ # Tokenize the input texts
60
+ inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt", max_length=512)
61
+
62
+ # Move inputs to the same device as the model
63
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
64
+ inputs = {k: v.to(device) for k, v in inputs.items()}
65
+
66
+ # Ensure the model is on the correct device
67
+ model.to(device)
68
+ model.eval() # Set the model to evaluation mode
69
+
70
+ # Clear any gradients
71
+ model.zero_grad()
72
+
73
+ # Get model predictions
74
+ with torch.no_grad():
75
+ outputs = model(**inputs)
76
+
77
+ # Extract logits
78
+ emotion_logits = outputs["emotion_logits"]
79
+ sentiment_logits = outputs["sentiment_logits"]
80
+
81
+ # Convert logits to probabilities
82
+ emotion_probs = torch.sigmoid(emotion_logits)
83
+ sentiment_probs = torch.softmax(sentiment_logits, dim=1)
84
+
85
+ # Convert tensors to lists for easier handling
86
+ emotion_probs_list = (emotion_probs * 100).tolist() # Convert to %
87
+ sentiment_probs_list = (sentiment_probs * 100).tolist() # Convert to %
88
+
89
+ # Define the sentiment and emotion labels
90
+ sentiment_labels = ['negative', 'neutral', 'positive']
91
+ emotion_labels = [
92
+ 'joy', 'trust', 'excitement', 'gratitude', 'hope', 'love', 'pride',
93
+ 'anger', 'disgust', 'fear', 'sadness', 'anxiety', 'frustration', 'guilt',
94
+ 'disappointment', 'surprise', 'anticipation', 'neutral'
95
+ ]
96
+
97
+ # Threshold for displaying probabilities
98
+ threshold = 0.0
99
+
100
+ # Map emotion probabilities to their corresponding labels
101
+ emotion_results = [
102
+ {label: prob for label, prob in zip(emotion_labels, emotion_probs_sample) if prob > 10.0}
103
+ for emotion_probs_sample in emotion_probs_list
104
+ ]
105
+
106
+ # Map sentiment probabilities to their corresponding labels
107
+ sentiment_results = [
108
+ {label: prob for label, prob in zip(sentiment_labels, sentiment_probs_sample) if prob > threshold}
109
+ for sentiment_probs_sample in sentiment_probs_list
110
+ ]
111
+
112
+ return emotion_results, sentiment_results
113
+
114
+ def sentiment_analysis_generate_table(text):
115
+ sentences = text.split('|')
116
+ emotion_results, sentiment_results = predict(sentences)
117
+
118
+ # Generate the HTML table with enhanced colors and bold headers
119
+ html = """
120
+ <html>
121
+ <head>
122
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bootstrap.min.css">
123
+ <style>
124
+ .label {
125
+ transition: .15s;
126
+ border-radius: 8px;
127
+ padding: 5px 10px;
128
+ font-size: 14px;
129
+ text-transform: uppercase;
130
+ }
131
+ .positive {
132
+ background-color: rgb(54, 176, 75);
133
+ color: white;
134
+ }
135
+ .negative {
136
+ background-color: rgb(237, 83, 80);
137
+ color: white;
138
+ }
139
+ .neutral {
140
+ background-color: rgb(255, 165, 0);
141
+ color: white;
142
+ }
143
+ th {
144
+ font-weight: bold;
145
+ color: rgb(106, 38, 198);
146
+ }
147
+ </style>
148
+ </head>
149
+ <body>
150
+ <table class="table table-striped">
151
+ <thead>
152
+ <tr>
153
+ <th scope="col">Text</th>
154
+ <th scope="col">Score</th>
155
+ <th scope="col">Sentiment</th>
156
+ <th scope="col">Emotions</th>
157
+ </tr>
158
+ </thead>
159
+ <tbody>
160
+ """
161
+ for sentence, emotions, sentiment in zip(sentences, emotion_results, sentiment_results):
162
+ text = sentence.strip()
163
+ sentiment_label = max(sentiment, key=sentiment.get)
164
+ score = f"{sentiment[sentiment_label]:.2f}%"
165
+
166
+ # Determine the sentiment class
167
+ if sentiment_label.lower() == "positive":
168
+ sentiment_class = "positive"
169
+ elif sentiment_label.lower() == "negative":
170
+ sentiment_class = "negative"
171
+ else:
172
+ sentiment_class = "neutral"
173
+
174
+ # Generate emotion tags
175
+ emotion_tags = ", ".join([f"{label} ({prob:.2f}%)" for label, prob in emotions.items()])
176
+
177
+ # Generate table rows
178
+ html += f'<tr><td>{text}</td><td>{score}</td><td><span class="label {sentiment_class}">{sentiment_label}</span></td><td>{emotion_tags}</td></tr>'
179
+
180
+ html += """
181
+ </tbody>
182
+ </table>
183
+ </body>
184
+ </html>
185
+ """
186
+
187
+ return html
188
+
189
+ if __name__ == "__main__":
190
+ iface = gr.Interface(
191
+ fn=sentiment_analysis_generate_table,
192
+ inputs=gr.Textbox(placeholder="Enter sentence here..."),
193
+ outputs=gr.HTML(),
194
+ title="Hellenic Sentiment AI - Version 2.0",
195
+ description="A sentiment & emotion analysis model, primarily for the Greek language.<br>"
196
+ "Type in some text in Greek, to classify its sentiment & emotion: positive, neutral, or negative, along with detected emotions.<br>"
197
+ "Multiple sentences can be classified when separated by the | character.<br>"
198
+ "Version 2.0 - Developed by GeoSar",
199
+ examples=[
200
+ ["Η πικάντικη γεύση αυτής της σούπας λαχανικών ήταν ακριβώς αυτό που χρειαζόμουν σήμερα. Είχε μια ωραία γαργαλιστική αίσθηση χωρίς να είναι πολύ καυτερή."],
201
+ ["Η πίτσα ήταν καμένη και τα υλικά φθηνής ποιότητας. Σίγουρα δεν θα ξαναπαραγγείλω από εκεί."]
202
+ ],
203
+ allow_flagging="manual",
204
+ flagging_options=["Incorrect", "Ambiguous"],
205
+ flagging_callback=hf_writer,
206
+ examples_per_page=2,
207
+ allow_duplication=False,
208
+ concurrency_limit="default"
209
+ )
210
+
211
+ iface.launch(share=True)