nkigumnov's picture
Update model.py
27e6827
#!g1.1
import torch
import torch.nn as nn
from transformers import AutoTokenizer, AutoModel
class CategoryHead(nn.Module):
def __init__(self):
super(CategoryHead, self).__init__()
self.lin1 = nn.Linear(256, 64)
self.lin2 = nn.Linear(64, 5)
def forward(self, x):
x = torch.relu(self.lin1(x))
x = self.lin2(x)
return x
class SentimentHead(nn.Module):
def __init__(self):
super(SentimentHead, self).__init__()
self.lin1 = nn.Linear(256, 64)
self.lin2 = nn.Linear(64, 1, bias=False)
def forward(self, x):
x = torch.relu(self.lin1(x))
x = self.lin2(x)
return x
def mean_pooling(model_output, attention_mask):
input_mask_expanded = attention_mask.unsqueeze(-1).expand(model_output.size()).float()
sum_embeddings = torch.sum(model_output * input_mask_expanded, 1)
sum_mask = torch.clamp(input_mask_expanded.sum(1), min=1e-9)
return sum_embeddings / sum_mask
class UnionModel(nn.Module):
def __init__(self, model_path):
super(UnionModel, self).__init__()
self.bert_model = AutoModel.from_pretrained(model_path)
for _, param in self.bert_model.named_parameters():
param.requires_grad = False
self.bert_model.pooler = nn.Linear(in_features=768, out_features=256)
self.bert_model.to('cpu')
self.category_head = CategoryHead()
self.sentiment_head = SentimentHead()
def forward(self, input):
output = self.bert_model(**input)
output = output.pooler_output
output = mean_pooling(output, input['attention_mask'])
return self.category_head(output), self.sentiment_head(output)
class LogisticCumulativeLink(nn.Module):
"""
Converts a single number to the proportional odds of belonging to a class.
Parameters
----------
num_classes : int
Number of ordered classes to partition the odds into.
init_cutpoints : str (default='ordered')
How to initialize the cutpoints of the model. Valid values are
- ordered : cutpoints are initialized to halfway between each class.
- random : cutpoints are initialized with random values.
"""
def __init__(self, num_classes: int,
init_cutpoints: str = 'ordered') -> None:
assert num_classes > 2, (
'Only use this model if you have 3 or more classes'
)
super().__init__()
self.num_classes = num_classes
self.init_cutpoints = init_cutpoints
if init_cutpoints == 'ordered':
num_cutpoints = self.num_classes - 1
cutpoints = torch.arange(num_cutpoints).float() - num_cutpoints / 2
self.cutpoints = nn.Parameter(cutpoints)
elif init_cutpoints == 'random':
cutpoints = torch.rand(self.num_classes - 1).sort()[0]
self.cutpoints = nn.Parameter(cutpoints)
else:
raise ValueError(f'{init_cutpoints} is not a valid init_cutpoints '
f'type')
def forward(self, X: torch.Tensor) -> torch.Tensor:
"""
Equation (11) from
"On the consistency of ordinal regression methods", Pedregosa et. al.
"""
sigmoids = torch.sigmoid(self.cutpoints - X)
link_mat = sigmoids[:, 1:] - sigmoids[:, :-1]
link_mat = torch.cat((
sigmoids[:, [0]],
link_mat,
(1 - sigmoids[:, [-1]])
), dim=1)
return link_mat
class CustomOrdinalLogisticModel(nn.Module):
def __init__(self, predictor: nn.Module, num_classes: int,
init_cutpoints: str = 'ordered') -> None:
super().__init__()
self.num_classes = num_classes
self.predictor = predictor
self.link = LogisticCumulativeLink(self.num_classes,
init_cutpoints=init_cutpoints)
def forward(self, *args, **kwargs):
cat, sent = self.predictor(*args, **kwargs)
return cat, self.link(sent)
tokenizer = AutoTokenizer.from_pretrained('blanchefort/rubert-base-cased-sentiment-rusentiment')
model = CustomOrdinalLogisticModel(UnionModel('blanchefort/rubert-base-cased-sentiment-rusentiment'), 3).to('cpu')
model.load_state_dict(torch.load('best_model.pth', map_location='cpu'), strict=False)
def inference(input_data):
tokenized = tokenizer(input_data['sentence'])
input_ids = torch.LongTensor(tokenized['input_ids']).unsqueeze(0).to('cpu')
attention_mask = torch.IntTensor(tokenized['attention_mask']).unsqueeze(0).to('cpu')
model.eval()
answer = model({'input_ids': input_ids, 'attention_mask': attention_mask})
answer[0][0] = torch.sigmoid(answer[0][0])
return dict(answer=answer)