Skip to content
Snippets Groups Projects
Commit 9e112c0d authored by Menezes, Luke J (PG/T - Comp Sci & Elec Eng)'s avatar Menezes, Luke J (PG/T - Comp Sci & Elec Eng)
Browse files

Upload New File

parent 022542a1
No related branches found
No related tags found
No related merge requests found
Pipeline #72641 canceled
%% Cell type:code id: tags:
``` python
from flask import Flask, jsonify, request
import json
import datasets, evaluate
from transformers import pipeline
import torch
from datetime import datetime
import numpy as np
import re
from transformers import AutoTokenizer, AutoModelForTokenClassification, DataCollatorForTokenClassification, TrainingArguments, Trainer, EarlyStoppingCallback
```
%% Cell type:code id: tags:
``` python
CW_datasets = datasets.load_dataset("surrey-nlp/PLOD-CW")
```
%% Cell type:code id: tags:
``` python
train_dataset = CW_datasets["train"]
test_dataset = CW_datasets["test"]
label_encoding = {"B-O": 0, "B-AC": 1, "B-LF": 2, "I-LF": 3}
metric = evaluate.load("seqeval")
```
%% Cell type:code id: tags:
``` python
def create_label_list(dataset, label_encoding):
label_list = []
for sample in dataset["ner_tags"]:
label_list.append([label_encoding[tag] for tag in sample])
return label_list
```
%% Cell type:code id: tags:
``` python
def turn_dict_to_list_of_dict(d):
new_list = []
for labels, inputs in zip(d["labels"], d["input_ids"]):
entry = {"input_ids": inputs, "labels": labels}
new_list.append(entry)
return new_list
```
%% Cell type:code id: tags:
``` python
def tokenize_and_align_labels(tokenizer, dataset, label_list, max_length=500, truncation=True, is_split_into_words=True):
tokenized_inputs = tokenizer(
dataset["tokens"],
max_length=max_length,
truncation=truncation,
is_split_into_words=is_split_into_words)
labels = []
for i, labels_per_sample in enumerate(label_list):
word_ids = tokenized_inputs.word_ids(batch_index=i)
label_ids, previous_word_idx = [], None
for word_idx in word_ids:
if word_idx is None:
label_ids.append(-100)
elif word_idx != previous_word_idx:
label_ids.append(labels_per_sample[word_idx])
else:
label_ids.append(labels_per_sample[word_idx])
previous_word_idx = word_idx
labels.append(label_ids)
tokenized_inputs["labels"] = labels
return tokenized_inputs
```
%% Cell type:code id: tags:
``` python
label_list = ["B-O", "B-AC", "B-LF", "I-LF"]
def compute_metrics(p):
predictions, labels = p
predictions = np.argmax(predictions, axis=2)
true_predictions = [[label_list[p] for (p, l) in zip(prediction, label) if l != -100] for prediction, label in zip(predictions, labels)]
true_labels = [[label_list[l] for (p, l) in zip(prediction, label) if l != -100] for prediction, label in zip(predictions, labels)]
results = metric.compute(predictions=true_predictions, references=true_labels)
return {"precision": results["overall_precision"], "recall": results["overall_recall"], "f1": results["overall_f1"], "accuracy": results["overall_accuracy"]}
```
%% Cell type:code id: tags:
``` python
def predict_tags(tokens, tokenizer, model, label_encoding, max_length=512):
# Tokenize input tokens
inputs = tokenizer(tokens, is_split_into_words=True, return_tensors="pt", max_length=max_length, padding="max_length", truncation=True)
input_ids = inputs["input_ids"]
attention_mask = inputs["attention_mask"]
# Predict using the model
with torch.no_grad():
outputs = model(input_ids, attention_mask=attention_mask)
# Get predicted labels
predictions = torch.argmax(outputs.logits, dim=2)
predicted_labels = predictions[0].tolist()
# Decode predicted labels, ignoring padding and special tokens
reversed_label_encoding = {v: k for k, v in label_encoding.items()}
decoded_labels = [reversed_label_encoding[label] for label in predicted_labels if label != -100]
# Combine tokens and labels, excluding padding tokens
decoded_labels = [reversed_label_encoding[label] for label, token in zip(predicted_labels, tokens) if token not in ["[PAD]", "[CLS]", "[SEP]"]]
return list(zip(tokens, decoded_labels))
```
%% Cell type:code id: tags:
``` python
def save_results(inputs, ner_pred, filename='log_file.txt'):
timestamp = datetime.now().isoformat()
results = {
'inputs': inputs,
'ner_predictions': ner_pred,
'timestamp': timestamp
}
with open(filename, 'a') as file:
file.write(json.dumps(results) + '\n')
```
%% Cell type:code id: tags:
``` python
def load_results(filename='log_file.txt'):
with open(filename, 'r') as file:
lines = file.readlines()
results = [json.loads(line) for line in lines]
return results
```
%% Cell type:code id: tags:
``` python
def split_string(s):
return re.findall(r"\b\w+\b|\S", s)
```
%% Cell type:markdown id: tags:
Beginning of Flask endpoint code
%% Cell type:code id: tags:
``` python
app = Flask(__name__)
```
%% Cell type:code id: tags:
``` python
@app.route('/test', methods=['POST'])
# Test endpoint for Q3, showing POST functionality
# Example command to run:
# curl -s -H "Content-Type: application/json" -X POST -d '{"input": "Hello World!"}' localhost:8080/test
def test():
inputs = request.get_json().get('input')
output = "Test successful, this was your input: " + inputs
return jsonify(output=output)
```
%% Cell type:code id: tags:
``` python
@app.route('/use-pretrained', methods=['GET'])
def use_pretrained():
"""Endpoint to load and use a pre-trained model."""
# Can be run with:
# curl localhost:8080/use-pretrained
try:
# Load the pre-trained model and tokenizer
global loaded_tokenizer, loaded_model
loaded_tokenizer = AutoTokenizer.from_pretrained("SciBERT-finetuned-NER")
loaded_model = AutoModelForTokenClassification.from_pretrained("SciBERT-finetuned-NER")
return jsonify(success="Pre-trained model loaded successfully")
except Exception as e:
return jsonify(error=str(e)), 500
```
%% Cell type:code id: tags:
``` python
# curl -X POST -H "Content-Type: application/json" -d "{\"model_name\": \"trainModelTest\", \"training_size\": 400}" http://127.0.0.1:8080/train
@app.route('/train', methods=['POST'])
def train():
global loaded_model, loaded_tokenizer
try:
data = request.get_json()
model_name = data.get('model_name')
training_size = data.get('training_size')
# Load dataset and select subset for training
if 'CW_datasets' not in globals():
return jsonify(error="Dataset not loaded"), 500
train_dataset = CW_datasets["train"][:training_size]
val_dataset = CW_datasets["validation"][:int(training_size/5)]
# Initialize the tokenizer and model for training using SciBERT
scibert_model_name = "allenai/scibert_scivocab_uncased"
tokenizer = AutoTokenizer.from_pretrained(scibert_model_name)
model = AutoModelForTokenClassification.from_pretrained(scibert_model_name, num_labels=4)
# Prepare labels and tokenize datasets
label_encoding = {"B-O": 0, "B-AC": 1, "B-LF": 2, "I-LF": 3}
label_list = create_label_list(train_dataset, label_encoding)
val_label_list = create_label_list(val_dataset, label_encoding)
tokenized_datasets = tokenize_and_align_labels(tokenizer, train_dataset, label_list)
tokenized_val_datasets = tokenize_and_align_labels(tokenizer, val_dataset, val_label_list)
tokenised_train = turn_dict_to_list_of_dict(tokenized_datasets)
tokenised_val = turn_dict_to_list_of_dict(tokenized_val_datasets)
# Data collator
data_collator = DataCollatorForTokenClassification(tokenizer)
# Training arguments
training_args = TrainingArguments(
output_dir=f'./results/{model_name}',
evaluation_strategy='steps',
eval_steps= int(training_size/10),
learning_rate=2e-5,
per_device_train_batch_size=2,
per_device_eval_batch_size=1,
num_train_epochs=1,
weight_decay=0.01,
save_steps=int(training_size/5),
metric_for_best_model='f1',
logging_dir=f'./logs/{model_name}',
logging_steps=int(training_size/5),
load_best_model_at_end=True
)
# Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenised_train,
eval_dataset=tokenised_val,
data_collator=data_collator,
tokenizer=tokenizer,
compute_metrics=compute_metrics,
callbacks=[EarlyStoppingCallback(early_stopping_patience=3)]
)
trainer.train()
# Save and update the global references
model.save_pretrained(f'./custom_trained_model/{model_name}')
tokenizer.save_pretrained(f'./custom_trained_model/{model_name}')
loaded_model = model
loaded_tokenizer = tokenizer
final_metrics = trainer.evaluate() # This will use the validation set
f1_score = final_metrics.get('eval_f1')
return jsonify(success=f"Model '{model_name}' trained successfully", f1_score=f1_score)
except Exception as e:
return jsonify(error=str(e)), 500
```
%% Cell type:code id: tags:
``` python
@app.route('/predict', methods=['POST'])
## Train must be run before this
## run from command line with: curl -s -H "Content-Type: application/json" -X POST -d '{"input": }' localhost:8080/predict
## examples:
## curl -s -H "Content-Type: application/json" -X POST -d '{"input": "For this purpose the Gothenburg Young Persons Empowerment Scale (GYPES) was developed."}' localhost:8080/predict
## curl -s -H "Content-Type: application/json" -X POST -d '{"input": "Recent work by us and others suggest that the host’s heat shock protein 90 (Hsp90) chaperone can modulate the evolutionary paths traversed by viruses [18, 19]."}' localhost:8080/predict
def predict():
inputs = request.get_json().get('input')
converted_inputs = split_string(inputs)
predictions = predict_tags(converted_inputs, loaded_tokenizer, loaded_model, label_encoding)
ner_tags = [i[1] for i in predictions]
save_results(converted_inputs, ner_tags)
return jsonify(predictions = str(predictions))
```
%% Cell type:code id: tags:
``` python
@app.route('/read_logs')
## Reads the txt file containing results from previous predictions and outputs to the user
## Example
## curl localhost:8080/read_logs
def read_logs():
logs = load_results()
return logs
```
%% Cell type:code id: tags:
``` python
## Must be run with debug=False
if __name__ == '__main__':
# Entry point for running on the local machine
# host is localhost; port is 8080; this file is index (.py)
app.run(host='127.0.0.1', port=8080, debug=False)
```
%% Output
* Serving Flask app '__main__'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:8080
Press CTRL+C to quit
127.0.0.1 - - [23/May/2024 13:01:01] "GET /read_logs HTTP/1.1" 200 -
/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/huggingface_hub/file_download.py:1132: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.
warnings.warn(
Some weights of BertForTokenClassification were not initialized from the model checkpoint at allenai/scibert_scivocab_uncased and are newly initialized: ['classifier.bias', 'classifier.weight']
You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.
/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/transformers/training_args.py:1474: FutureWarning: `evaluation_strategy` is deprecated and will be removed in version 4.46 of 🤗 Transformers. Use `eval_strategy` instead
warnings.warn(
{'eval_loss': 0.2954792082309723, 'eval_precision': 0.9192034139402561, 'eval_recall': 0.9145202377582791, 'eval_f1': 0.9168558456299659, 'eval_accuracy': 0.8985082578582845, 'eval_runtime': 14.9365, 'eval_samples_per_second': 5.356, 'eval_steps_per_second': 5.356, 'epoch': 0.2}
{'loss': 0.4214, 'grad_norm': 5.093618869781494, 'learning_rate': 1.2e-05, 'epoch': 0.4}
{'eval_loss': 0.2313002347946167, 'eval_precision': 0.9325906883747501, 'eval_recall': 0.9241437871497311, 'eval_f1': 0.9283480238839921, 'eval_accuracy': 0.9211507725093234, 'eval_runtime': 1.7865, 'eval_samples_per_second': 44.78, 'eval_steps_per_second': 44.78, 'epoch': 0.4}
{'eval_loss': 0.19281569123268127, 'eval_precision': 0.9394714407502132, 'eval_recall': 0.9357486555335409, 'eval_f1': 0.9376063528077142, 'eval_accuracy': 0.933404368673415, 'eval_runtime': 1.7433, 'eval_samples_per_second': 45.889, 'eval_steps_per_second': 45.889, 'epoch': 0.6}
{'loss': 0.2374, 'grad_norm': 2.5805234909057617, 'learning_rate': 4.000000000000001e-06, 'epoch': 0.8}
{'eval_loss': 0.20157472789287567, 'eval_precision': 0.9435206422018348, 'eval_recall': 0.9315029719784885, 'eval_f1': 0.9374732944025067, 'eval_accuracy': 0.9331379861481087, 'eval_runtime': 1.7554, 'eval_samples_per_second': 45.575, 'eval_steps_per_second': 45.575, 'epoch': 0.8}
{'eval_loss': 0.18856896460056305, 'eval_precision': 0.941643059490085, 'eval_recall': 0.9408434757996037, 'eval_f1': 0.9412430978337817, 'eval_accuracy': 0.9363345764517848, 'eval_runtime': 1.8332, 'eval_samples_per_second': 43.64, 'eval_steps_per_second': 43.64, 'epoch': 1.0}
{'train_runtime': 97.3226, 'train_samples_per_second': 4.11, 'train_steps_per_second': 2.055, 'train_loss': 0.3157902050018311, 'epoch': 1.0}
127.0.0.1 - - [23/May/2024 13:02:52] "POST /train HTTP/1.1" 200 -
[2024-05-23 13:03:15,257] ERROR in app: Exception on /predict [POST]
Traceback (most recent call last):
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/flask/app.py", line 2529, in wsgi_app
response = self.full_dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/flask/app.py", line 1825, in full_dispatch_request
rv = self.handle_user_exception(e)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/flask/app.py", line 1823, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/flask/app.py", line 1799, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/folders/r3/rgdzj4k90y31832__9r63w9w0000gn/T/ipykernel_20301/1553366013.py", line 10, in predict
predictions = predict_tags(converted_inputs, loaded_tokenizer, loaded_model, label_encoding)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/folders/r3/rgdzj4k90y31832__9r63w9w0000gn/T/ipykernel_20301/3237725419.py", line 9, in predict_tags
outputs = model(input_ids, attention_mask=attention_mask)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1511, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1520, in _call_impl
return forward_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/transformers/models/bert/modeling_bert.py", line 1885, in forward
outputs = self.bert(
^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1511, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1520, in _call_impl
return forward_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/transformers/models/bert/modeling_bert.py", line 1073, in forward
embedding_output = self.embeddings(
^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1511, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1520, in _call_impl
return forward_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/transformers/models/bert/modeling_bert.py", line 210, in forward
inputs_embeds = self.word_embeddings(input_ids)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1511, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1520, in _call_impl
return forward_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/sparse.py", line 163, in forward
return F.embedding(
^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/functional.py", line 2237, in embedding
return torch.embedding(weight, input, padding_idx, scale_grad_by_freq, sparse)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Placeholder storage has not been allocated on MPS device!
127.0.0.1 - - [23/May/2024 13:03:15] "POST /predict HTTP/1.1" 500 -
[2024-05-23 13:03:22,677] ERROR in app: Exception on /predict [POST]
Traceback (most recent call last):
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/flask/app.py", line 2529, in wsgi_app
response = self.full_dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/flask/app.py", line 1825, in full_dispatch_request
rv = self.handle_user_exception(e)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/flask/app.py", line 1823, in full_dispatch_request
rv = self.dispatch_request()
^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/flask/app.py", line 1799, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/folders/r3/rgdzj4k90y31832__9r63w9w0000gn/T/ipykernel_20301/1553366013.py", line 10, in predict
predictions = predict_tags(converted_inputs, loaded_tokenizer, loaded_model, label_encoding)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/var/folders/r3/rgdzj4k90y31832__9r63w9w0000gn/T/ipykernel_20301/3237725419.py", line 9, in predict_tags
outputs = model(input_ids, attention_mask=attention_mask)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1511, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1520, in _call_impl
return forward_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/transformers/models/bert/modeling_bert.py", line 1885, in forward
outputs = self.bert(
^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1511, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1520, in _call_impl
return forward_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/transformers/models/bert/modeling_bert.py", line 1073, in forward
embedding_output = self.embeddings(
^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1511, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1520, in _call_impl
return forward_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/transformers/models/bert/modeling_bert.py", line 210, in forward
inputs_embeds = self.word_embeddings(input_ids)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1511, in _wrapped_call_impl
return self._call_impl(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1520, in _call_impl
return forward_call(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/modules/sparse.py", line 163, in forward
return F.embedding(
^^^^^^^^^^^^
File "/Users/lukemenezes/anaconda3/lib/python3.11/site-packages/torch/nn/functional.py", line 2237, in embedding
return torch.embedding(weight, input, padding_idx, scale_grad_by_freq, sparse)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Placeholder storage has not been allocated on MPS device!
127.0.0.1 - - [23/May/2024 13:03:22] "POST /predict HTTP/1.1" 500 -
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment