Ptato commited on
Commit
c0f174a
·
1 Parent(s): 3bc73f1

document_app

Browse files
Files changed (1) hide show
  1. app.py +183 -8
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import streamlit as st
2
  import time
3
  from transformers import pipeline
@@ -7,52 +8,132 @@ import torch
7
  import numpy as np
8
  import pandas as pd
9
 
10
-
11
  os.environ['KMP_DUPLICATE_LIB_OK'] = "True"
12
 
13
-
14
  st.title("Sentiment Analysis App")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  if 'logs' not in st.session_state:
16
  st.session_state.logs = dict()
 
 
17
  if 'labels' not in st.session_state:
18
  st.session_state.labels = ['toxic', 'severe_toxic', 'obscene', 'threat', 'insult', 'identity_hate']
19
- if 'id2label' not in st.session_state:
20
- st.session_state.id2label = {idx: label for idx, label in enumerate(st.session_state.labels)}
21
  if 'filled' not in st.session_state:
22
  st.session_state.filled = False
 
 
 
23
  if 'model' not in st.session_state:
24
  st.session_state.model = AutoModelForSequenceClassification.from_pretrained("Ptato/Modified-Bert-Toxicity-Classification")
25
  st.session_state.model.eval()
 
 
 
 
 
26
  if 'tokenizer' not in st.session_state:
27
  st.session_state.tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
28
 
 
 
29
  form = st.form(key='Sentiment Analysis')
 
 
30
  st.session_state.options = [
31
  'bertweet-base-sentiment-analysis',
32
  'distilbert-base-uncased-finetuned-sst-2-english',
33
  'twitter-roberta-base-sentiment',
34
  'Modified Bert Toxicity Classification'
35
  ]
 
 
36
  box = form.selectbox('Select Pre-trained Model:', st.session_state.options, key=1)
 
 
 
 
 
 
37
  tweet = form.text_input(label='Enter text to analyze:', value="\"We've seen in the last few months, unprecedented amounts of Voter Fraud.\" @SenTedCruz True!")
 
 
38
  submit = form.form_submit_button(label='Submit')
 
 
39
  if 'df' not in st.session_state:
40
  st.session_state.df = pd.read_csv("test.csv")
41
 
 
42
  if not st.session_state.filled:
 
43
  for s in st.session_state.options:
44
  st.session_state.logs[s] = []
 
 
45
  if not st.session_state.filled:
 
 
46
  st.session_state.filled = True
 
 
47
  for x in range(10):
 
 
48
  print(x)
 
 
49
  text = st.session_state.df["comment_text"].iloc[x][:128]
 
 
50
  for s in st.session_state.options:
 
 
 
 
51
  pline = None
 
 
52
  predictions = None
 
 
53
  encoding = None
 
 
54
  logits = None
55
  probs = None
 
 
56
  if s == 'bertweet-base-sentiment-analysis':
57
  pline = pipeline(task="sentiment-analysis", model="finiteautomata/bertweet-base-sentiment-analysis")
58
  elif s == 'twitter-roberta-base-sentiment':
@@ -60,25 +141,45 @@ if not st.session_state.filled:
60
  elif s == 'distilbert-base-uncased-finetuned-sst-2-english':
61
  pline = pipeline(task="sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english")
62
  else:
 
63
  encoding = st.session_state.tokenizer(text, return_tensors="pt")
64
  encoding = {k: v.to(st.session_state.model.device) for k, v in encoding.items()}
 
 
65
  predictions = st.session_state.model(**encoding)
 
 
66
  logits = predictions.logits
67
  sigmoid = torch.nn.Sigmoid()
68
  probs = sigmoid(logits.squeeze().cpu())
 
 
69
  predictions = np.zeros(probs.shape)
70
  predictions[np.where(probs >= 0.5)] = 1
71
- predicted_labels = [st.session_state.id2label[idx] for idx, label in enumerate(predictions) if label == 1.0]
 
72
  log = []
 
 
73
  if pline:
 
74
  predictions = pline(text)
 
 
75
  log = [0] * 4
 
 
76
  log[1] = text
 
 
77
  for p in predictions:
 
 
 
78
  if s == 'bertweet-base-sentiment-analysis':
79
  if p['label'] == "POS":
80
  log[0] = 0
81
- log[2] = "POSITIVE"
82
  log[3] = f"{ round(p['score'] * 100, 1)}%"
83
  elif p['label'] == "NEU":
84
  log[0] = 2
@@ -110,17 +211,29 @@ if not st.session_state.filled:
110
  log[0] = 2
111
  log[2] = "NEUTRAL"
112
  log[3] = f"{round(p['score'] * 100, 1)}%"
 
 
113
  else:
 
 
114
  log = [0] * 6
115
  log[1] = text
 
 
116
  if max(predictions) == 0:
 
117
  log[0] = 0
118
  log[2] = ("NO TOXICITY")
119
  log[3] = (f"{100 - round(probs[0].item() * 100, 1)}%")
120
  log[4] = ("N/A")
121
  log[5] = ("N/A")
 
 
122
  else:
 
123
  log[0] = 1
 
 
124
  _max = 0
125
  _max2 = 2
126
  for i in range(1, len(predictions)):
@@ -128,22 +241,36 @@ if not st.session_state.filled:
128
  _max = i
129
  if i > 2 and probs[i].item() > probs[_max2].item():
130
  _max2 = i
 
 
131
  log[2] = (st.session_state.labels[_max])
132
  log[3] = (f"{round(probs[_max].item() * 100, 1)}%")
133
  log[4] = (st.session_state.labels[_max2])
134
  log[5] = (f"{round(probs[_max2].item() * 100, 1)}%")
 
135
  st.session_state.logs[s].append(log)
136
 
 
137
  if submit and tweet:
 
 
138
  with st.spinner('Analyzing...'):
139
  time.sleep(1)
140
 
 
141
  if tweet is not None:
 
 
142
  pline = None
 
 
 
143
  if box != 'Modified Bert Toxicity Classification':
144
  col1, col2, col3 = st.columns(3)
145
  else:
146
  col1, col2, col3, col4, col5 = st.columns(5)
 
 
147
  if box == 'bertweet-base-sentiment-analysis':
148
  pline = pipeline(task="sentiment-analysis", model="finiteautomata/bertweet-base-sentiment-analysis")
149
  elif box == 'twitter-roberta-base-sentiment':
@@ -151,33 +278,60 @@ if submit and tweet:
151
  elif box == 'distilbert-base-uncased-finetuned-sst-2-english':
152
  pline = pipeline(task="sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english")
153
  else:
 
 
154
  encoding = st.session_state.tokenizer(tweet, return_tensors="pt")
155
  encoding = {k: v.to(st.session_state.model.device) for k,v in encoding.items()}
 
 
156
  predictions = st.session_state.model(**encoding)
 
 
157
  logits = predictions.logits
158
  sigmoid = torch.nn.Sigmoid()
159
  probs = sigmoid(logits.squeeze().cpu())
160
- print(probs[0].item())
 
161
  predictions = np.zeros(probs.shape)
162
  predictions[np.where(probs >= 0.5)] = 1
163
- predicted_labels = [st.session_state.id2label[idx] for idx, label in enumerate(predictions) if label == 1.0]
 
 
164
  if pline:
 
 
165
  predictions = pline(tweet)
 
 
166
  col2.header("Judgement")
167
  else:
 
168
  col2.header("Category")
169
  col4.header("Type")
170
  col5.header("Score")
171
 
 
172
  col1.header("Tweet")
173
  col3.header("Score")
174
 
 
175
  if pline:
 
176
  log = [0] * 4
 
 
177
  log[1] = tweet
 
 
178
  for p in predictions:
 
 
 
179
  if box == 'bertweet-base-sentiment-analysis':
180
  if p['label'] == "POS":
 
 
 
181
  col1.success(tweet.split("\n")[0][:20])
182
  log[0] = 0
183
  col2.success("POS")
@@ -235,8 +389,11 @@ if submit and tweet:
235
  col3.warning(f"{round(p['score'] * 100, 1)}%")
236
  log[3] = f"{round(p['score'] * 100, 1)}%"
237
  log[2] = "NEUTRAL"
 
 
238
  for a in st.session_state.logs[box][::-1]:
239
  if a[0] == 0:
 
240
  col1.success(a[1].split("\n")[0][:20])
241
  col2.success(a[2])
242
  col3.success(a[3])
@@ -248,11 +405,21 @@ if submit and tweet:
248
  col1.warning(a[1].split("\n")[0][:20])
249
  col2.warning(a[2])
250
  col3.warning(a[3])
 
251
  st.session_state.logs[box].append(log)
 
 
252
  else:
 
 
253
  log = [0] * 6
254
  log[1] = tweet
 
 
255
  if max(predictions) == 0:
 
 
 
256
  col1.success(tweet.split("\n")[0][:10])
257
  col2.success("NO TOXICITY")
258
  col3.success(f"{100 - round(probs[0].item() * 100, 1)}%")
@@ -264,6 +431,8 @@ if submit and tweet:
264
  log[4] = ("N/A")
265
  log[5] = ("N/A")
266
  else:
 
 
267
  _max = 0
268
  _max2 = 2
269
  for i in range(1, len(predictions)):
@@ -271,6 +440,8 @@ if submit and tweet:
271
  _max = i
272
  if i > 2 and probs[i].item() > probs[_max2].item():
273
  _max2 = i
 
 
274
  col1.error(tweet.split("\n")[0][:10])
275
  col2.error(st.session_state.labels[_max])
276
  col3.error(f"{round(probs[_max].item() * 100, 1)}%")
@@ -281,6 +452,8 @@ if submit and tweet:
281
  log[3] = (f"{round(probs[_max].item() * 100, 1)}%")
282
  log[4] = (st.session_state.labels[_max2])
283
  log[5] = (f"{round(probs[_max2].item() * 100, 1)}%")
 
 
284
  for a in st.session_state.logs[box][::-1]:
285
  if a[0] == 0:
286
  col1.success(a[1].split("\n")[0][:10])
@@ -300,4 +473,6 @@ if submit and tweet:
300
  col3.warning(a[3])
301
  col4.warning(a[4])
302
  col5.warning(a[5])
 
 
303
  st.session_state.logs[box].append(log)
 
1
+ # Import stuff
2
  import streamlit as st
3
  import time
4
  from transformers import pipeline
 
8
  import numpy as np
9
  import pandas as pd
10
 
11
+ # Mitigates an error on Macs
12
  os.environ['KMP_DUPLICATE_LIB_OK'] = "True"
13
 
14
+ # Set the titel
15
  st.title("Sentiment Analysis App")
16
+
17
+ # Set the variables that should not be changed between refreshes of the app.
18
+
19
+ """
20
+ logs is a map that records the results of past sentiment analysis queries.
21
+ Type: dict() {"key" --> value[]}
22
+ key: model_name (string) - The name of the model being used
23
+ value: log[] (list) - The list of values that represent the model's results
24
+ --> For the pretrained labels, len(log) = 4
25
+ --> log[0] (int) - The prediction of the model on its input
26
+ --> 0 = Positive
27
+ --> 1 = Negative
28
+ --> 2 = Neutral (if applicable)
29
+ --> log[1] (string) - The tweet/inputted string
30
+ --> log[2] (string) - The judgement of the tweet/input (Positive/Neutral/Negative)
31
+ --> log[3] (string) - The score of the prediction (includes '%' sign)
32
+ --> For the finetuned model, len(log) = 6
33
+ --> log[0] (int) - The prediction of the model on the toxicity of the input
34
+ --> 0 = Nontoxic
35
+ --> 1 = Toxic
36
+ --> log[1] (string) - The tweet/inputted string
37
+ --> log[2] (string) - The highest scoring overall category of toxicity out of:
38
+ 'toxic', 'severe_toxic', 'obscene', 'threat', 'insult', and 'identity_hate'
39
+ --> log[3] (string) - The score of log[2] (includes '%' sign)
40
+ --> log[4] (string) - The predicted type of toxicity, the highest scoring category of toxicity out of:
41
+ 'obscene', 'threat', 'insult', and 'identity_hate'
42
+ --> log[5] (string) - The score of log[4] (includes '%' sign)
43
+ """
44
  if 'logs' not in st.session_state:
45
  st.session_state.logs = dict()
46
+
47
+ # labels is a list of toxicity categories for the finetuned model
48
  if 'labels' not in st.session_state:
49
  st.session_state.labels = ['toxic', 'severe_toxic', 'obscene', 'threat', 'insult', 'identity_hate']
50
+
51
+ # filled is a boolean that checks whether logs is prepopulated with data.
52
  if 'filled' not in st.session_state:
53
  st.session_state.filled = False
54
+
55
+ # model is the finetuned model that I created. It wasn't working well locally on HuggingFace so I uploaded it to HuggingFace as
56
+ # a pretrained model. I also set it to evaluation mode.
57
  if 'model' not in st.session_state:
58
  st.session_state.model = AutoModelForSequenceClassification.from_pretrained("Ptato/Modified-Bert-Toxicity-Classification")
59
  st.session_state.model.eval()
60
+
61
+
62
+ # tokenizer is the same tokenizer that is used by the "bert-base-uncased" model, which my finetuned model is built off of.
63
+ # tokenizer is used to input the tweets into my model for prediction.
64
+
65
  if 'tokenizer' not in st.session_state:
66
  st.session_state.tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
67
 
68
+
69
+ # This form allows users to select their preferred model for training
70
  form = st.form(key='Sentiment Analysis')
71
+
72
+ # st.session_state.options pre-sets the available model choices.
73
  st.session_state.options = [
74
  'bertweet-base-sentiment-analysis',
75
  'distilbert-base-uncased-finetuned-sst-2-english',
76
  'twitter-roberta-base-sentiment',
77
  'Modified Bert Toxicity Classification'
78
  ]
79
+
80
+ # box is the dropdown box that users use to select their choice of model
81
  box = form.selectbox('Select Pre-trained Model:', st.session_state.options, key=1)
82
+
83
+ """
84
+ tweet refers to the text box for users to input their tweets.
85
+ Has a default value of "\"We've seen in the last few months, unprecedented amounts of Voter Fraud.\" @SenTedCruz True!"
86
+ (Tweeted by former president Donald Trump)
87
+ """
88
  tweet = form.text_input(label='Enter text to analyze:', value="\"We've seen in the last few months, unprecedented amounts of Voter Fraud.\" @SenTedCruz True!")
89
+
90
+ # Submit button
91
  submit = form.form_submit_button(label='Submit')
92
+
93
+ # Read in some test data for prepopulation
94
  if 'df' not in st.session_state:
95
  st.session_state.df = pd.read_csv("test.csv")
96
 
97
+ # Initializes logs if not already initialized
98
  if not st.session_state.filled:
99
+ # Iterates through all the options, initializing the logs for each.
100
  for s in st.session_state.options:
101
  st.session_state.logs[s] = []
102
+
103
+ # Pre-populates logs if not already pre-populated
104
  if not st.session_state.filled:
105
+
106
+ # Esnure pre-population happen again
107
  st.session_state.filled = True
108
+
109
+ # Initialize 10 entries
110
  for x in range(10):
111
+
112
+ # Helps me see which entry is being evaluated on the backend
113
  print(x)
114
+
115
+ # Shorten tweets, as some models may not handle longer ones
116
  text = st.session_state.df["comment_text"].iloc[x][:128]
117
+
118
+ # Iterate thru the models
119
  for s in st.session_state.options:
120
+
121
+ # Reset everything
122
+
123
+ # pline is the pipeline, which is used to load in the proper HuggingFace model for analysis
124
  pline = None
125
+
126
+ # predictions refer to the predictions made by each model
127
  predictions = None
128
+
129
+ # encoding is used by the finetuned model as input
130
  encoding = None
131
+
132
+ # logits and probs are used to transform the results from predictions into usable/outputable data
133
  logits = None
134
  probs = None
135
+
136
+ # Perform different actions based on the model selected by the user
137
  if s == 'bertweet-base-sentiment-analysis':
138
  pline = pipeline(task="sentiment-analysis", model="finiteautomata/bertweet-base-sentiment-analysis")
139
  elif s == 'twitter-roberta-base-sentiment':
 
141
  elif s == 'distilbert-base-uncased-finetuned-sst-2-english':
142
  pline = pipeline(task="sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english")
143
  else:
144
+ # encode data
145
  encoding = st.session_state.tokenizer(text, return_tensors="pt")
146
  encoding = {k: v.to(st.session_state.model.device) for k, v in encoding.items()}
147
+
148
+ # feed data into model and store the predictions
149
  predictions = st.session_state.model(**encoding)
150
+
151
+ # modify the data to get probabilities for each toxicity (scale of 0 - 1)
152
  logits = predictions.logits
153
  sigmoid = torch.nn.Sigmoid()
154
  probs = sigmoid(logits.squeeze().cpu())
155
+
156
+ # Reform the predictions to note where probabilities are actually high
157
  predictions = np.zeros(probs.shape)
158
  predictions[np.where(probs >= 0.5)] = 1
159
+
160
+ # Prepare the log entry
161
  log = []
162
+
163
+ # If there was a pipeline, then we used a pretrained model.
164
  if pline:
165
+ # Get the prediction
166
  predictions = pline(text)
167
+
168
+ # Initialize the log to the proper shape
169
  log = [0] * 4
170
+
171
+ # Record the text
172
  log[1] = text
173
+
174
+ # predictions ends up being length 1, so this only happens for the prediction with the highest probability (the returned value)
175
  for p in predictions:
176
+
177
+ # Different models have different outputs, so we standardize them in the logs
178
+ # Note, some unecessary repetions may occur here
179
  if s == 'bertweet-base-sentiment-analysis':
180
  if p['label'] == "POS":
181
  log[0] = 0
182
+ log[2] = "POS"
183
  log[3] = f"{ round(p['score'] * 100, 1)}%"
184
  elif p['label'] == "NEU":
185
  log[0] = 2
 
211
  log[0] = 2
212
  log[2] = "NEUTRAL"
213
  log[3] = f"{round(p['score'] * 100, 1)}%"
214
+
215
+ # Otherwise, we are using the finetuned model
216
  else:
217
+
218
+ #Initialize log to the proper shape and store the text
219
  log = [0] * 6
220
  log[1] = text
221
+
222
+ # Determine whether or not there was toxicity
223
  if max(predictions) == 0:
224
+ # No toxicity, input log values as such
225
  log[0] = 0
226
  log[2] = ("NO TOXICITY")
227
  log[3] = (f"{100 - round(probs[0].item() * 100, 1)}%")
228
  log[4] = ("N/A")
229
  log[5] = ("N/A")
230
+
231
+ # There was toxicity
232
  else:
233
+ # Record the toxicity
234
  log[0] = 1
235
+
236
+ # Find the maximum overall toxic category and the maximum toxic category of each type
237
  _max = 0
238
  _max2 = 2
239
  for i in range(1, len(predictions)):
 
241
  _max = i
242
  if i > 2 and probs[i].item() > probs[_max2].item():
243
  _max2 = i
244
+
245
+ # Input data into log
246
  log[2] = (st.session_state.labels[_max])
247
  log[3] = (f"{round(probs[_max].item() * 100, 1)}%")
248
  log[4] = (st.session_state.labels[_max2])
249
  log[5] = (f"{round(probs[_max2].item() * 100, 1)}%")
250
+ # Add the log to the proper model's logs
251
  st.session_state.logs[s].append(log)
252
 
253
+ # Check if there was a submitted input
254
  if submit and tweet:
255
+
256
+ # Small loading message :)
257
  with st.spinner('Analyzing...'):
258
  time.sleep(1)
259
 
260
+ # Double check that there was an input
261
  if tweet is not None:
262
+
263
+ # Reset variable
264
  pline = None
265
+
266
+ # Set up shape for output
267
+ # Pretrained models should have 3 columns, while the finetuned model should have 5
268
  if box != 'Modified Bert Toxicity Classification':
269
  col1, col2, col3 = st.columns(3)
270
  else:
271
  col1, col2, col3, col4, col5 = st.columns(5)
272
+
273
+ # Perform different actions based on the model selected by the user
274
  if box == 'bertweet-base-sentiment-analysis':
275
  pline = pipeline(task="sentiment-analysis", model="finiteautomata/bertweet-base-sentiment-analysis")
276
  elif box == 'twitter-roberta-base-sentiment':
 
278
  elif box == 'distilbert-base-uncased-finetuned-sst-2-english':
279
  pline = pipeline(task="sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english")
280
  else:
281
+
282
+ # encode data
283
  encoding = st.session_state.tokenizer(tweet, return_tensors="pt")
284
  encoding = {k: v.to(st.session_state.model.device) for k,v in encoding.items()}
285
+
286
+ # feed data into model and store the predictions
287
  predictions = st.session_state.model(**encoding)
288
+
289
+ # modify the data to get probabilities for each toxicity (scale of 0 - 1)
290
  logits = predictions.logits
291
  sigmoid = torch.nn.Sigmoid()
292
  probs = sigmoid(logits.squeeze().cpu())
293
+
294
+ # Reform the predictions to note where probabilities are actually high
295
  predictions = np.zeros(probs.shape)
296
  predictions[np.where(probs >= 0.5)] = 1
297
+
298
+ # Title columns differently for different models
299
+ # The existence of pline implies that a pretrained model was used
300
  if pline:
301
+
302
+ # Predict the tweet here
303
  predictions = pline(tweet)
304
+
305
+ # Title the column
306
  col2.header("Judgement")
307
  else:
308
+ # Titling columns
309
  col2.header("Category")
310
  col4.header("Type")
311
  col5.header("Score")
312
 
313
+ # Title more columns
314
  col1.header("Tweet")
315
  col3.header("Score")
316
 
317
+ # If we used a pretrained model, process the prediction below
318
  if pline:
319
+ # Set log to correct shape
320
  log = [0] * 4
321
+
322
+ # Store the tweet
323
  log[1] = tweet
324
+
325
+ # predictions ends up being length 1, so this only happens for the prediction with the highest probability (the returned value)
326
  for p in predictions:
327
+
328
+ # Different models have different outputs, so we standardize them in the logs
329
+ # Note, some unecessary repetions may occur here
330
  if box == 'bertweet-base-sentiment-analysis':
331
  if p['label'] == "POS":
332
+
333
+ # Only print the first 20 characters of the first line, so that the table lines up
334
+ # Also store the proper values into log while printing the outcome of this tweet
335
  col1.success(tweet.split("\n")[0][:20])
336
  log[0] = 0
337
  col2.success("POS")
 
389
  col3.warning(f"{round(p['score'] * 100, 1)}%")
390
  log[3] = f"{round(p['score'] * 100, 1)}%"
391
  log[2] = "NEUTRAL"
392
+
393
+ # Print out the past inputs in reverse order
394
  for a in st.session_state.logs[box][::-1]:
395
  if a[0] == 0:
396
+ # Again, only limit the tweet printed to 20 characters to have everything line up
397
  col1.success(a[1].split("\n")[0][:20])
398
  col2.success(a[2])
399
  col3.success(a[3])
 
405
  col1.warning(a[1].split("\n")[0][:20])
406
  col2.warning(a[2])
407
  col3.warning(a[3])
408
+ # Add the log to the logs
409
  st.session_state.logs[box].append(log)
410
+
411
+ # We used the finetuned model, so proceed below
412
  else:
413
+
414
+ # Initialize log to the proper shape and store the tweet
415
  log = [0] * 6
416
  log[1] = tweet
417
+
418
+ # Check if nontoxic
419
  if max(predictions) == 0:
420
+
421
+ # Only display the first 10 characters, as more columns means less characters can fit (make everything line up)
422
+ # Display and input the data as we go
423
  col1.success(tweet.split("\n")[0][:10])
424
  col2.success("NO TOXICITY")
425
  col3.success(f"{100 - round(probs[0].item() * 100, 1)}%")
 
431
  log[4] = ("N/A")
432
  log[5] = ("N/A")
433
  else:
434
+
435
+ # Look for the maximum toxicity category and the highest toxicity type
436
  _max = 0
437
  _max2 = 2
438
  for i in range(1, len(predictions)):
 
440
  _max = i
441
  if i > 2 and probs[i].item() > probs[_max2].item():
442
  _max2 = i
443
+
444
+ # Display and input the data as we go
445
  col1.error(tweet.split("\n")[0][:10])
446
  col2.error(st.session_state.labels[_max])
447
  col3.error(f"{round(probs[_max].item() * 100, 1)}%")
 
452
  log[3] = (f"{round(probs[_max].item() * 100, 1)}%")
453
  log[4] = (st.session_state.labels[_max2])
454
  log[5] = (f"{round(probs[_max2].item() * 100, 1)}%")
455
+
456
+ # Print out the past logs in reverse order
457
  for a in st.session_state.logs[box][::-1]:
458
  if a[0] == 0:
459
  col1.success(a[1].split("\n")[0][:10])
 
473
  col3.warning(a[3])
474
  col4.warning(a[4])
475
  col5.warning(a[5])
476
+
477
+ # Add result to logs
478
  st.session_state.logs[box].append(log)