QUESTION - ANSWER - DistilBert - CREATE CUSTOM MODEL
Qui vediamo come creare un modello personalizzato per automatizzare un sistema di domande/risposte in automatico tramite una nuova versione di Bert(Bidirectional Encoder Representations from Transformers) la DistilBert che risulta essere più snella di BERT. Prendiamo un modello pretrained quindi già pronto in lingua inglese e facciamo la fase di Fine Tuning inserendo dei dati in lingua italiana.
Si descrive innanzi tutto come creare i dati da inserire per creare il modello.
Si creano 3 file uno per il contesto, uno con le domande ed il terzo con le risposte.
I 3 file verranno poi convertiti in liste contenenti stringhe. La lista delle risposte è composta da dictionary composta da key testo con value risposta e dalle key inizio e fine con inici dell'inizio e della fine della risposta all'interno del relativo contesto.
Sotto i dati usati per questo esempio.

context:
["La Mariner 9 fu la prima sonda spaziale a orbitare intorno a Marte nel 1971. Raccolse informazioni inedite sul Pianeta Rosso, rivelando la presenza di grandi vulcani sulla superficie marziana, di enormi sistemi di canaloni e di indizi del fatto che un tempo c'era dell'acqua. Prese anche le prime immagini dettagliate delle due piccole lune di Marte, Phobos e Deimos.",
"Marte è il quarto pianeta del sistema solare in ordine di distanza dal Sole;[3] è visibile a occhio nudo ed è l'ultimo dei pianeti di tipo terrestre dopo Mercurio, Venere e la Terra. Marte viene chiamato pianeta rosso per via del suo colore caratteristico causato dalla grande quantità di ossido di ferro che lo ricopre,[3] Marte prende il nome dall'omonima divinità della mitologia romana[3] e il suo simbolo astronomico è la rappresentazione stilizzata dello scudo e della lancia del dio.",
"Rosetta è stata una missione spaziale sviluppata dall'Agenzia spaziale europea e lanciata nel 2004 e finita nel 2016. L'obiettivo della missione fu lo studio della cometa 67P/Churyumov-Gerasimenko. La missione era formata da due elementi: la sonda vera e propria Rosetta e il lander Philae, atterrato il 12 novembre 2014 sulla superficie della cometa 67P/Churyumov Gerasimenko. La missione si è conclusa il 30 settembre 2016, con lo schianto programmato dell'orbiter sulla cometa e disattivazione del segnale.",
'Vostok 1 (in russo Восток-1) fu la prima missione con equipaggio umano svoltasi nel corso del programma sovietico di esplorazione spaziale Vostok nonché il primo volo umano nello spazio in assoluto. Il 12 aprile 1961 il cosmonauta Jurij Alekseevič Gagarin divenne il primo essere umano a orbitare intorno alla Terra.',
"il Programma Gemini, o semplicemente Gemini, è il secondo programma di volo umano nello spazio intrapreso dagli Stati Uniti. Pur essendo stato il terzo programma in ordine cronologico ad essere iniziato, venne concluso prima del lancio della prima missione del programma Apollo.Il progetto venne battezzato Gemini poiché la navicella spaziale poteva ospitare un equipaggio di due uomini. Condotto durante il periodo 1963-1966, il suo scopo fu quello di sviluppare le tecniche per i viaggi spaziali avanzati utilizzati poi durante il programma Apollo per portare l'uomo sulla Luna."]
fonte: Wikipedia
question:
['Chi fu la Mariner 9 ?',
'Perchè Marte viene chiamato il pianeta rosso ?',
"Quale fu l'obiettivo della missione spaziale Rosetta ?",
'Chi è stato il primo essere umano a orbitare intorno alla Terra ?',
'Perchè il progetto venne battezzato Programma Gemini ?']
answers:
[{'fine': 75, 'inizio': 0,'testo': 'La Mariner 9 fu la prima sonda spaziale a orbitare intorno a Marte nel 1971'},
{'fine': 255, 'inizio': 183, 'testo': 'Marte viene chiamato pianeta rosso per via del suo colore caratteristico'},
{'fine': 196, 'inizio': 118,'testo': "L'obiettivo della missione fu lo studio della cometa 67P/Churyumov-Gerasimenko"},
{'fine': 255, 'inizio': 217,'testo': 'il cosmonauta Jurij Alekseevič Gagarin'},
{'fine': 386, 'inizio': 278, 'testo': 'Il progetto venne battezzato Gemini poiché la navicella spaziale poteva ospitare un equipaggio di due uomini'}]


A questo punto vediamo il programma per creare un modello facendo il Fine-Tuning di un modello pretrained.
!pip install transformers

from torch.utils.data import DataLoader
from transformers import AdamW
from transformers import DistilBertTokenizerFast
from transformers import DistilBertForQuestionAnswering
from torch.utils.data import Dataset
import torch
from torch import nn
import pandas as pd
from google.colab import drive
drive.mount('/content/drive/')

def carica_dati():

""" carica dati e crea 1 lista x context data, 1 lista per question data e un dictionary per answer """

answers= []
filename_context = "/content/drive/My Drive/crypto/QA_BERT.txt"
contesto = pd.read_csv(filename_context, sep='\n', header=None)
filename_domande = "/content/drive/My Drive/crypto/domande.txt"
domande = pd.read_csv(filename_domande, sep='\n', header=None)
filename_risposte = "/content/drive/My Drive/crypto/risposte.txt"
risposte = pd.read_csv(filename_risposte, sep='\n', header=None)

context = [ x.strip() for x in contesto[0]]
question = [ x.strip() for x in domande[0]]
risposte = [ x.strip() for x in risposte[0]]

for i in range(len(risposte)):
answer = dict([('testo', '' ),('inizio', ''), ('fine','')])
answer['testo'] = risposte[i]
answer['inizio']= context[i].find(risposte[i])
answer['fine'] = context[i].find(risposte[i]) + len(risposte[i])
answers.append(answer)

return context, question, answers
class CreaDatasetSquad(Dataset):

def __init__(self, data):
self.data = data

def __getitem__(self, i):
return {key: torch.tensor(value[i]) for key, value in self.data.items()}

def __len__(self):
return len(self.data.input_ids)
def insTokenPositions(train, answers):

inizio = []
fine = []

for i in range(len(answers)):
inizio.append(train.char_to_token(i, answers[i]['inizio']))
fine.append(train.char_to_token(i, answers[i]['fine'] - 1))
# se lo non trova in answers inserisce lunghezza massima prevista dal modello

if inizio[-1] is None:
inizio[-1] = tokenizer.model_max_length

if fine[-1] is None:
fine[-1] = tokenizer.model_max_length

train.update({'inizio': inizio, 'fine': fine})
Qui carichiamo i dati e scarichiamo gli oggetti già disponibili come il tokenizer ed il modello pretrained.
context, question, answers = carica_dati()

tokenizer = DistilBertTokenizerFast.from_pretrained('distilbert-base-uncased')
model = DistilBertForQuestionAnswering.from_pretrained("distilbert-base-uncased")
Creazione del train_dataset con la tokenizzazione del context e relativa question. Nel riquadro vediamo il file creato con input_ids ed attention_mask.
train_dataset = tokenizer(context, question, truncation=True, padding=True)

{'input_ids': [[101, 2474, 3884, 2099, 1023 ... 102]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 .... ]}
Ora nel train_dataset si inseriscono i token dell'inizio e fine della risposta.
insTokenPositions(train_dataset, answers)
{'input_ids': [[101, 2474, 3884, 2099, 1023 ... 102]], 'attention_mask': [[1, 1... 1]], 'inizio': [1, 70, 42, 75, 84], 'fine': [23, 92, 68, 88, 118]}
Con la funzione CreaDatasetSquad creiamo il nostro dataset come un dataset Squad(The Stanford Question Answering Dataset). Nel riquadro successivo vediamo nel dettaglio l'item 1.
train_dataset = CreaDatasetSquad(train_dataset)

train_dataset.__getitem__(1)

{'attention_mask': tensor([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]),
'fine': tensor(92),
'inizio': tensor(70),
'input_ids': tensor([ 101, 20481, 2063, 1041, 6335, 24209, 8445, 2080, 24624, 7159,
2050, 3972, 24761, 18532, 2050, 5943, 2063, 1999, 2030, 10672,
4487, 4487, 12693, 4143, 17488, 7082, 1025, 1031, 1017, 1033,
1041, 25292, 12322, 9463, 1037, 1051, 25955, 2080, 16371, 3527,
3968, 1041, 1048, 1005, 17359, 3775, 5302, 14866, 24624, 7159,
2072, 4487, 5955, 2080, 25170, 3367, 2890, 2079, 6873, 21442,
10841, 9488, 1010, 2310, 3678, 2063, 1041, 2474, 14403, 1012,
20481, 2063, 20098, 2638, 9610, 8067, 3406, 24624, 7159, 2050,
5811, 2080, 2566, 3081, 3972, 10514, 2080, 3609, 2063, 14418,
12079, 6553, 2080, 6187, 10383, 3406, 17488, 2721, 9026, 24110,
3775, 2696, 4487, 9808, 5332, 3527, 4487, 10768, 18933, 18178,
8840, 7043, 28139, 1010, 1031, 1017, 1033, 20481, 2063, 3653,
13629, 6335, 2053, 4168, 17488, 2140, 1005, 18168, 10698, 2863,
4487, 6371, 6590, 8611, 10210, 12898, 10440, 3142, 2050, 1031,
1017, 1033, 1041, 6335, 10514, 2080, 21934, 14956, 2080, 28625,
3630, 7712, 2080, 1041, 2474, 9680, 28994, 4765, 16103, 5643,
25931, 10993, 4143, 2696, 12418, 2080, 8040, 6784, 2080, 1041,
8611, 17595, 7405, 3972, 4487, 2080, 1012, 102, 21836, 2063,
20481, 2063, 20098, 2638, 9610, 8067, 3406, 6335, 24624, 7159,
2050, 5811, 2080, 1029, 102, 0, 0, 0, 0, 0,
0, 0, 0])}
Il passo successivo è quello di creare con DataLoader(by Torch) un iterator sul file di input. Poi si definiscono dei parametri come l'optimizer qui viene applicato quello creato appositamente per questo modello.
trainI = DataLoader(train_dataset, batch_size=16, shuffle=True)

EPOCHS = 200
totalLoss = 0
check_iter = 1

optim = AdamW(model.parameters(), lr=5e-5)
device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')

model.train()
model.to(device)
Passiamo alla fase di training per la creazione del modello. Qui si applicano 200 epochs visto l'esiguo numero di dati inseriti nel modello.
for epoch in range(EPOCHS):

for i,train in enumerate(trainI):
optim.zero_grad()

# estrazione dati processati precedentemente
input_ids = train['input_ids'].to(device)
attention_mask = train['attention_mask'].to(device)
start_positions = train['inizio'].to(device)
end_positions = train['fine'].to(device)

outputs = model(input_ids, attention_mask=attention_mask, start_positions=start_positions, end_positions=end_positions)
loss = outputs[0]
loss.backward()
optim.step()
totalLoss += loss.item()

if (i + 1) % check_iter == 0:
lossAvg = totalLoss / check_iter
print("epoch %d, loss = %.3f" % (epoch + 1, lossAvg ))
totalLoss = 0



Una volta creato il modello passiamo ad effettuare una valutazione dello stesso selezionando anche una domanda ed il relativo contesto.
model.eval()
domanda= question[3] # Chi è stato il primo essere umano a orbitare intorno alla Terra ?
contesto = context[3]

tokenCod = tokenizer.encode_plus(domanda,contesto)
input_ids, attention_mask = tokenCod["input_ids"], tokenCod["attention_mask"]
inizioRisultato, fineRisultato = model(torch.tensor([input_ids]), attention_mask=torch.tensor([attention_mask]))

risTokens = input_ids[torch.argmax(inizioRisultato) : torch.argmax(fineRisultato)+1]
answerTokens = tokenizer.convert_ids_to_tokens(risTokens , skip_special_tokens=True)

tokensTuttInp = tokenizer.convert_ids_to_tokens(input_ids)

AnswerPredicted = tokenizer.convert_tokens_to_string(answerTokens)

tokensTuttInp :

['il', 'co', '##smo', '##naut', '##a', 'ju', '##ri', '##j', 'ale', '##ks', '##ee', '##vic', 'gaga', '##rin']


Risposta predicted :
il cosmonauta jurij alekseevic gagarin