Skip to content

Commit 2d6cdc7

Browse files
committed
feat: fastapi python to consume model
1 parent 42c740b commit 2d6cdc7

File tree

4 files changed

+159
-0
lines changed

4 files changed

+159
-0
lines changed

requirements.txt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
appdirs==1.4.4
2+
black==20.8b1
3+
certifi==2020.12.5
4+
chardet==4.0.0
5+
click==7.1.2
6+
fastapi==0.63.0
7+
filelock==3.0.12
8+
flake8==3.8.4
9+
gdown==3.12.2
10+
h11==0.12.0
11+
idna==2.10
12+
isort==5.7.0
13+
joblib==1.0.1
14+
mccabe==0.6.1
15+
mypy-extensions==0.4.3
16+
numpy==1.20.1
17+
packaging==20.9
18+
pathspec==0.8.1
19+
pycodestyle==2.6.0
20+
pydantic==1.8.1
21+
pyflakes==2.2.0
22+
pyparsing==2.4.7
23+
PySocks==1.7.1
24+
regex==2020.11.13
25+
requests==2.25.1
26+
sacremoses==0.0.43
27+
six==1.15.0
28+
starlette==0.13.6
29+
tokenizers==0.10.1
30+
toml==0.10.2
31+
torch==1.8.0
32+
tqdm==4.59.0
33+
transformers==4.3.3
34+
typed-ast==1.4.2
35+
typing-extensions==3.7.4.3
36+
urllib3==1.26.3
37+
uvicorn==0.13.4

src/api.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from typing import Dict
2+
3+
from fastapi import Depends, FastAPI
4+
from fastapi.middleware.cors import CORSMiddleware
5+
from pydantic import BaseModel
6+
7+
from .classifier.model import Model, get_model
8+
9+
app = FastAPI()
10+
11+
#Allowing CORS
12+
origins = ["*"]
13+
14+
app.add_middleware(
15+
CORSMiddleware,
16+
allow_origins=origins,
17+
allow_credentials=True,
18+
allow_methods=["*"],
19+
allow_headers=["*"],
20+
)
21+
22+
class SentimentRequest(BaseModel):
23+
text:str
24+
25+
class SentimentResponse(BaseModel):
26+
probabilities: Dict[str, float]
27+
sentiment: str
28+
confidence: float
29+
30+
31+
@app.post("/predict", response_model = SentimentResponse)
32+
def predict(request: SentimentRequest, model: Model = Depends(get_model)):
33+
'''
34+
The injection of the parameter model is done by the function get_model
35+
'''
36+
sentiment, confidence, probabilities = model.predict(request.text)
37+
return SentimentResponse(
38+
sentiment = sentiment,
39+
confidence = confidence,
40+
probabilities = probabilities
41+
)

src/classifier/model.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import json
2+
import torch
3+
import torch.nn.functional as F
4+
from transformers import BertTokenizer
5+
6+
from .sentiment_classifier import SentimentClassifier
7+
8+
with open("config.json") as json_file:
9+
config = json.load(json_file)
10+
11+
12+
class Model:
13+
def __init__(self):
14+
self.device = torch.device("cuda:0" if torch.cuda.is_available()
15+
else "cpu")
16+
self.tokenizer = BertTokenizer.from_pretrained(config["BERT_MODEL"])
17+
18+
classifier = SentimentClassifier(len(config["CLASS_NAMES"]))
19+
classifier.load_state_dict(
20+
torch.load(config['PRE_TRAINED_MODEL'], map_location=self.device)
21+
)
22+
classifier = classifier.eval()
23+
self.classifier = classifier.to(self.device)
24+
25+
def predict(self, text):
26+
encoded_text = \
27+
self.tokenizer.encode_plus( text,
28+
max_lenght = config["MAX_SEQUENCE_LEN"],
29+
add_special_tokens = True,
30+
return_token_type_ids = False,
31+
pad_to_max_length = True,
32+
return_attetion_mask = True,
33+
return_tensors = "pt")
34+
input_ids = encoded_text["input_ids"].to(self.device)
35+
attention_mask = encoded_text["attention_mask"].to(self.device)
36+
37+
with torch.no_grad():
38+
#Getting the prediction
39+
probabilities = F.softmax(self.classifier(input_ids, attention_mask),
40+
dim = 1)
41+
42+
#Taking the most confident result
43+
confidence, predicted_class = torch.max(probabilities, dim = 1)
44+
predicted_class = predicted_class.cpu().item()
45+
probabilities = probabilities.flatten().cpu().numpy().tolist()
46+
47+
return(
48+
config["CLASS_NAMES"][predicted_class],
49+
confidence,
50+
dict(zip(config["CLASS_NAMES"], probabilities))
51+
)
52+
53+
'''
54+
[init model is heavy] use the singleton pattern to avoid more one model initialization
55+
'''
56+
57+
model = Model()
58+
def get_model():
59+
return model
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import json
2+
3+
from torch import nn
4+
from transformers import BertModel
5+
6+
with open("config.json") as json_file:
7+
config = json.load(json_file)
8+
9+
10+
class SentimentClassifier(nn.Module):
11+
def __init__(self, n_classes):
12+
#TODO: O que é esse super?
13+
super(SentimentClassifier, self).__init__()
14+
self.bert = BertModel.from_pretrained(config["BERT_MODEL"],
15+
return_dict=False)
16+
self.drop = nn.Dropout(p=0.3)
17+
self.out = nn.Linear(self.bert.config.hidden_size, n_classes)
18+
19+
def forward(self, input_ids, attention_mask):
20+
_, pooled_output = self.bert(input_ids=input_ids, attention_mask=attention_mask)
21+
output = self.drop(pooled_output)
22+
return self.out(output)

0 commit comments

Comments
 (0)