Download bert_multiclass_classification_commented.py from HuM-HILab/pub_comm_classification: direct link, hf CLI and curl.
- Browser
- Download file 9.55 kB
-
https://huggingface.co/HuM-HILab/pub_comm_classification/resolve/main/bert_multiclass_classification_commented.py
- Command line
-
hf download hf://HuM-HILab/pub_comm_classification/bert_multiclass_classification_commented.py
-
curl -L -o bert_multiclass_classification_commented.py https://huggingface.co/HuM-HILab/pub_comm_classification/resolve/main/bert_multiclass_classification_commented.py
9.55 kB
| #!/usr/bin/env python | |
| # coding: utf-8 | |
| # Installing the transformers library and additional libraries if looking process | |
| # Install the required Transformers library (if running in a notebook) | |
| get_ipython().system('pip install -q transformers') | |
| # Importing stock ml libraries | |
| # Import libraries for data handling, modeling, and evaluation | |
| import numpy as np | |
| import pandas as pd | |
| from sklearn import metrics | |
| import transformers | |
| from torchviz import make_dot | |
| import torch | |
| from torch.utils.data import Dataset, DataLoader, RandomSampler, SequentialSampler | |
| from transformers import BertTokenizer, BertModel, BertConfig | |
| from sklearn.preprocessing import LabelEncoder | |
| # Preparing for TPU usage | |
| # import torch_xla | |
| # import torch_xla.core.xla_model as xm | |
| # device = xm.xla_device() | |
| # # Setting up the device for GPU usage | |
| from torch import cuda | |
| # Set up device: use GPU if available, else fallback to CPU | |
| device = 'cuda' if cuda.is_available() else 'cpu' | |
| # Load the dataset from Excel file | |
| df = pd.read_excel("DatiPerClassificazioneIncrociata.xlsx") | |
| colonna_rif = 'ricercatore2' | |
| # allowed_values (set of valid categories to include in the training subset) | |
| # NOTE: For 'ricercatore2', exclude 'comunicazione normativa' since it was not used | |
| # Define the set of valid categories to keep in the dataset | |
| valori_ammessi = {'Comunicazione dei /nei processi partecipativi', 'Comunicazione di servizio', 'Comunicazione delle attività istituzionali', 'Comunicazione delle issues socialmente rilevanti', 'Comunicazione delle politiche pubbliche', 'Comunicazione interna e valorizzazione delle risorse umane', 'Comunicazione di brand'} | |
| # Filter dataset to include only allowed categories | |
| df_filtrato = df[df[colonna_rif].isin(valori_ammessi)] | |
| valori_distinti = df_filtrato[colonna_rif ].unique() | |
| # Encode string labels into integers | |
| label_encoder = LabelEncoder() | |
| df_filtrato[colonna_rif] = label_encoder.fit_transform(df_filtrato[colonna_rif]) | |
| df_filtrato['list'] = df_filtrato[colonna_rif] | |
| new_df = df_filtrato[['Text', 'list']].copy() | |
| new_df.head() | |
| # Defining some key variables that will be used later on in the training | |
| # Define model training hyperparameters | |
| MAX_LEN = 200 | |
| TRAIN_BATCH_SIZE = 8 | |
| VALID_BATCH_SIZE = 4 | |
| EPOCHS = 5 | |
| LEARNING_RATE = 1e-05 | |
| tokenizer = BertTokenizer.from_pretrained('dbmdz/bert-base-italian-cased') | |
| # Define custom PyTorch Dataset to prepare model inputs | |
| class CustomDataset(Dataset): | |
| def __init__(self, dataframe, tokenizer, max_len): | |
| self.tokenizer = tokenizer | |
| self.data = dataframe | |
| self.comment_text = dataframe.Text | |
| self.targets = self.data.list | |
| self.max_len = max_len | |
| def __len__(self): | |
| return len(self.comment_text) | |
| def __getitem__(self, index): | |
| comment_text = str(self.comment_text[index]) | |
| comment_text = " ".join(comment_text.split()) | |
| inputs = self.tokenizer.encode_plus( | |
| comment_text, | |
| None, | |
| add_special_tokens=True, | |
| max_length=self.max_len, | |
| pad_to_max_length=True, | |
| return_token_type_ids=True | |
| ) | |
| ids = inputs['input_ids'] | |
| mask = inputs['attention_mask'] | |
| token_type_ids = inputs["token_type_ids"] | |
| return { | |
| 'ids': torch.tensor(ids, dtype=torch.long), | |
| 'mask': torch.tensor(mask, dtype=torch.long), | |
| 'token_type_ids': torch.tensor(token_type_ids, dtype=torch.long), | |
| 'targets': torch.tensor(self.targets[index], dtype=torch.long) | |
| } | |
| # Creating the dataset and dataloader for the neural network | |
| train_size = 0.8 | |
| # Split dataset into training and testing subsets | |
| train_dataset=new_df.sample(frac=train_size,random_state=200) | |
| test_dataset=new_df.drop(train_dataset.index).reset_index(drop=True) | |
| train_dataset = train_dataset.reset_index(drop=True) | |
| print("FULL Dataset: {}".format(new_df.shape)) | |
| print("TRAIN Dataset: {}".format(train_dataset.shape)) | |
| print("TEST Dataset: {}".format(test_dataset.shape)) | |
| # Define model training hyperparameters | |
| training_set = CustomDataset(train_dataset, tokenizer, MAX_LEN) | |
| # Define model training hyperparameters | |
| testing_set = CustomDataset(test_dataset, tokenizer, MAX_LEN) | |
| train_params = {'batch_size': TRAIN_BATCH_SIZE, | |
| 'shuffle': True, | |
| 'num_workers': 0 | |
| } | |
| test_params = {'batch_size': VALID_BATCH_SIZE, | |
| 'shuffle': True, | |
| 'num_workers': 0 | |
| } | |
| # Create PyTorch DataLoaders for batching | |
| training_loader = DataLoader(training_set, **train_params) | |
| testing_loader = DataLoader(testing_set, **test_params) | |
| testing_set[50] | |
| # Creating the customized model, by adding a drop out and a dense layer on top of distil bert to get the final output for the model. | |
| # Define BERT-based classification model with dropout and linear layer | |
| class BERTClass(torch.nn.Module): | |
| def __init__(self): | |
| super(BERTClass, self).__init__() | |
| self.l1 = transformers.BertModel.from_pretrained('dbmdz/bert-base-italian-cased') | |
| self.l2 = torch.nn.Dropout(0.3) | |
| # For 'ricercatore2', use 7 output classes because "comunicazione normativa" was not used and therefore does not appear among the classes | |
| self.l3 = torch.nn.Linear(768, 7) | |
| def forward(self, ids, mask, token_type_ids): | |
| _, output_1= self.l1(ids, attention_mask = mask, token_type_ids = token_type_ids, return_dict=False) | |
| output_2 = self.l2(output_1) | |
| output = self.l3(output_2) | |
| return output | |
| model = BERTClass() | |
| model.to(device) | |
| # Define the loss function (CrossEntropy for multiclass classification) | |
| def loss_fn(outputs, targets): | |
| # Using CrossEntropyLoss for multiclass classification (only one label per input) | |
| return torch.nn.CrossEntropyLoss()(outputs, targets) | |
| # Define the optimizer (Adam) | |
| optimizer = torch.optim.Adam(params = model.parameters(), lr=LEARNING_RATE) | |
| # Training loop for one epoch | |
| def train(epoch): | |
| model.train() | |
| for _,data in enumerate(training_loader, 0): | |
| ids = data['ids'].to(device, dtype = torch.long) | |
| mask = data['mask'].to(device, dtype = torch.long) | |
| token_type_ids = data['token_type_ids'].to(device, dtype = torch.long) | |
| targets = data['targets'].to(device, dtype = torch.long) | |
| outputs = model(ids, mask, token_type_ids) | |
| optimizer.zero_grad() | |
| # Define the loss function (CrossEntropy for multiclass classification) | |
| loss = loss_fn(outputs, targets) | |
| if _%5000==0: | |
| print(f'Epoch: {epoch}, Loss: {loss.item()}') | |
| optimizer.zero_grad() | |
| loss.backward() | |
| optimizer.step() | |
| for epoch in range(EPOCHS): | |
| train(epoch) | |
| # Validation loop to evaluate model on unseen data | |
| def validation(epoch): | |
| model.eval() | |
| fin_targets=[] | |
| fin_outputs=[] | |
| with torch.no_grad(): | |
| for _, data in enumerate(testing_loader, 0): | |
| ids = data['ids'].to(device, dtype = torch.long) | |
| mask = data['mask'].to(device, dtype = torch.long) | |
| token_type_ids = data['token_type_ids'].to(device, dtype = torch.long) | |
| targets = data['targets'].to(device, dtype = torch.long) | |
| outputs = model(ids, mask, token_type_ids) | |
| fin_targets.extend(targets.cpu().detach().numpy().tolist()) | |
| fin_outputs.extend(torch.softmax(outputs, dim=1).cpu().detach().numpy().tolist()) | |
| return fin_outputs, fin_targets | |
| for epoch in range(EPOCHS): | |
| outputs, targets = validation(epoch) | |
| outputs = np.argmax(outputs, axis=1) | |
| accuracy = metrics.accuracy_score(targets, outputs) | |
| f1_score_micro = metrics.f1_score(targets, outputs, average='micro') | |
| f1_score_macro = metrics.f1_score(targets, outputs, average='macro') | |
| f1_score_weighted = metrics.f1_score(targets, outputs, average='weighted') | |
| print(f"Accuracy Score = {accuracy}") | |
| print(f"F1 Score (Micro) = {f1_score_micro}") | |
| print(f"F1 Score (Macro) = {f1_score_macro}") | |
| print(f"F1 Score (Weighted) = {f1_score_weighted}") | |
| # Save the trained model | |
| # Save model weights and optimizer state | |
| def save_model(model, epoch, path="model.pth"): | |
| state = { | |
| 'epoch': epoch, | |
| 'state_dict': model.state_dict(), | |
| 'optimizer': optimizer.state_dict(), | |
| } | |
| torch.save(state, path) | |
| print(f"Model saved to {path}") | |
| # Save model weights and optimizer state | |
| save_model(model, epoch, path=f"model_epoch_{epoch+1}_{colonna_rif}.pth") | |
| # Print the confusion matrix | |
| import matplotlib.pyplot as plt | |
| import seaborn as sns | |
| from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay | |
| # Assuming you have called the validation function and stored the results | |
| fin_outputs, fin_targets = validation(epoch) | |
| # Convert model output probabilities into predicted class labels | |
| fin_outputs = np.argmax(fin_outputs, axis=1) | |
| # Compute the confusion matrix | |
| # Compute and plot normalized confusion matrix | |
| cm = confusion_matrix(fin_targets, fin_outputs) | |
| # Normalize the confusion matrix by row (true labels) | |
| cm_normalized = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis] | |
| # Convert to percentage | |
| cm_normalized_percentage = np.round(cm_normalized * 100,2) | |
| # Plot the confusion matrix | |
| plt.figure(figsize=(10, 8)) | |
| sns.heatmap(cm_normalized_percentage, annot=True, fmt='.2f', cmap='Blues', xticklabels=range(8), yticklabels=range(8)) | |
| plt.xlabel('Predicted Labels') | |
| plt.ylabel('True Labels') | |
| plt.title('Confusion Matrix') | |
| # Save the figure as a PNG file | |
| plt.savefig(f'normalized_confusion_matrix_{colonna_rif}.png') | |
| plt.show() | |