|
|
| import torch |
| import torch.nn as nn |
| from transformers import BertModel |
|
|
|
|
| class BERTGRUSentiment(nn.Module): |
| def __init__( |
| self, |
| bert_model_name="bert-base-uncased", |
| hidden_dim=256, |
| output_dim=1, |
| n_layers=2, |
| bidirectional=True, |
| dropout=0.25, |
| ): |
| super().__init__() |
|
|
| self.bert = BertModel.from_pretrained(bert_model_name) |
| self.embedding_dim = self.bert.config.hidden_size |
|
|
| self.rnn = nn.GRU( |
| input_size=self.embedding_dim, |
| hidden_size=hidden_dim, |
| num_layers=n_layers, |
| bidirectional=bidirectional, |
| dropout=dropout if n_layers > 1 else 0, |
| batch_first=True, |
| ) |
|
|
| self.out = nn.Linear( |
| hidden_dim * 2 if bidirectional else hidden_dim, |
| output_dim, |
| ) |
|
|
| self.dropout = nn.Dropout(dropout) |
|
|
| def forward(self, input_ids): |
| |
| |
| with torch.no_grad(): |
| embedded = self.bert(input_ids)[0] |
|
|
| _, hidden = self.rnn(embedded) |
|
|
| if self.rnn.bidirectional: |
| hidden = self.dropout( |
| torch.cat((hidden[-2, :, :], hidden[-1, :, :]), dim=1) |
| ) |
| else: |
| hidden = self.dropout(hidden[-1, :, :]) |
|
|
| return self.out(hidden) |
|
|
|
|
| def build_model(device="cpu"): |
| model = BERTGRUSentiment( |
| bert_model_name="bert-base-uncased", |
| hidden_dim=256, |
| output_dim=1, |
| n_layers=2, |
| bidirectional=True, |
| dropout=0.25, |
| ) |
| model.to(device) |
| model.eval() |
| return model |
|
|
|
|
| def load_model(checkpoint_path, device="cpu"): |
| model = build_model(device=device) |
|
|
| state_dict = torch.load(checkpoint_path, map_location=device) |
|
|
| if isinstance(state_dict, dict) and "state_dict" in state_dict: |
| state_dict = state_dict["state_dict"] |
|
|
| if hasattr(state_dict, "state_dict"): |
| state_dict = state_dict.state_dict() |
|
|
| missing, unexpected = model.load_state_dict(state_dict, strict=True) |
|
|
| if missing or unexpected: |
| raise RuntimeError( |
| f"Checkpoint did not load cleanly. Missing={missing}, Unexpected={unexpected}" |
| ) |
|
|
| model.eval() |
| return model |
|
|
|
|
| def predict_sentiment(model, tokenizer, sentence, device="cpu"): |
| init_token_idx = tokenizer.convert_tokens_to_ids(tokenizer.cls_token) |
| eos_token_idx = tokenizer.convert_tokens_to_ids(tokenizer.sep_token) |
| max_input_length = tokenizer.model_max_length |
|
|
| tokens = tokenizer.tokenize(sentence) |
| tokens = tokens[: max_input_length - 2] |
|
|
| indexed = ( |
| [init_token_idx] |
| + tokenizer.convert_tokens_to_ids(tokens) |
| + [eos_token_idx] |
| ) |
|
|
| tensor = torch.LongTensor(indexed).to(device) |
| tensor = tensor.unsqueeze(0) |
|
|
| with torch.no_grad(): |
| probability = torch.sigmoid(model(tensor)).item() |
|
|
| label = "Positive" if probability >= 0.5 else "Negative" |
| confidence = probability if label == "Positive" else 1 - probability |
|
|
| return { |
| "label": label, |
| "positive_probability": probability, |
| "confidence": confidence, |
| } |
|
|