Pubblicità

Come allenare un modello Word2Vec per l’italiano

In questo tutorial vedremo come trainare un modello Word2Vec per la lingua italiana sul corpus di Wikipedia.

Esistono moltissimi modelli pretrainati per l’italiano (ad esempio ce ne sono diversi inclusi in Spacy), ma penso che, almeno una volta, possa essere interessante vedere come si fa ad allenarne uno da zero.

Il train di un modello Word2Vec è molto semplice, eppure in rete si trovano pochissimi tutorial che spiegano come fare (e ovviamente nessuno di questi è in italiano), di conseguenza ho pensato che potesse essere utile scrivere questo post.

Non parlerò del modello in sè, sia per non rendere questo articolo eccessivamente lungo (magari in futuro ne scriverò uno ad hoc), sia perché in rete ci sono già moltissime risorse sull’argomento.

Requisiti

Useremo la libreria gensim, che puoi installare sia con pip che con conda:

pip install gensim -y
:: oppure:
conda install -c conda-forge gensim

Il train del modello è oneroso sia dal punto di vista computazionale che (soprattutto) da quello della memoria. Per questo motivo sconsiglierei di utilizzare un computer con meno di 16GB di RAM.

Corpus

Wikimedia fornisce dei dumps periodici di tutte le versioni di Wikipedia (e non solo). I dump della versione italiana si possono scaricare al seguente indirizzo:

https://dumps.wikimedia.org/itwiki/latest/

Nella repository ci sono molti files contenenti diversi tipi di backup. Quello che ci interessa è il file completo dell’ultimo snapshot della Wikipedia in itaiano, itwiki-latest-pages-articles.xml.bz2:

Possiamo scaricarlo nella nostra cartella locale usando la libreria nativa requests:

import os
import urllib.request 

wikipedia_dump_url = 'https://dumps.wikimedia.org/itwiki/latest/itwiki-latest-pages-articles.xml.bz2'

local_filename = os.path.join(os.getcwd(), 'itwiki-latest-pages-articles.xml.bz2')

urllib.request.urlretrieve(wikipedia_dump_url, local_filename)

Il file è molto grande (circa 3.4 GB mentre scrivo, ma cresce ogni giorno), per cui ci vorrà un po’ di tempo. Al termine avremo, nella nostra cartella locale, un file xml zippato chiamato itwiki-latest-pages-articles.xml.bz2.

Pre-processing

Abbiamo ottenuto il dump completo di wikipedia, completo di testo, hyperlink, collegamenti esterni, note, link ad immagini, etc.

Per renderlo digeribile al modello dovremo processare il file al fine di estrarne il solo testo. Ho scritto una funzione, basata sulla classe WikiCorpus di gensim, che semplifica al massimo questa operazione:

import logging
from gensim.models import Word2Vec

logger = logging.getLogger()

def make_corpus(in_f, out_f):
    """
    Funzione per convertire il file dump di Wikipedia in puro testo.
    
    Parameters:
    - in_f: path al file di input
    - out_f: path al file di output
    
    Return:
    - esito: True/False
    """
    logger.info("Opening dump file")
    output = open(out_f, 'w')
    logger.info("Parsing the data")
    wiki = WikiCorpus(in_f) 
    i = 0
    for text in wiki.get_texts():
        output.write(bytes(' '.join(text), 'utf-8').decode('utf-8') + '\n')
        i = i + 1
        if (i % 100000 == 0):
            logger.info(f'Processed {str(i)} articles')
    output.close()
    logger.info('Processing complete!')
    return True
    

make_corpus('itwiki-latest-pages-articles.xml.bz2', 
             'Wikipedia.txt')

Al termine di questa operazione otterremo, sempre nella nostra cartella di lavoro, un file chiamato Wikipedia.txt.

Questo file conterrà l’intero testo di wikipedia, un articolo su ogni riga, senza la punteggiatura e tutto in minuscolo.

Train

Possiamo passare all’allenamento vero e proprio. Anche in questa fase ci viene in aiuto la libreria gensim, nello specifico la classe Word2Vec, che possiamo inizializzare nel modo seguente:

model = Word2Vec(LineSentence('Wikipedia.txt'), # Corpus preprocessato
                 vector_size=100, # Dimensione dello spazio di embedding
                 window=5,  # Allena considerando una finestra di 5 parole sul testo
                 min_count=5, # esclude tutti i termini che compaiono meno di 5 volte
                 epochs=10, #Numero di epoche su cui allenare
                 workers=multiprocessing.cpu_count()) #usa tutti i processori

Gli argomenti forniti al costruttore in questo codice di esempio sono:

  1. Il corpus. Ho utilizzato la funzione LineSentence per creare un iteratore che legga e passi al modello una frase per volta, per questioni di RAM
  2. vector_size => la dimensione dello spazio vettoriale sul quale saranno definite le rappresentazioni vettoriali delle parole
  3. window => la dimensione del contesto da utilizzare
  4. min_count => ignora ogni parola del corpus che compaia meno di min_count volte
  5. epochs => numero di epoche
  6. workers => numero di core su cui parallelizzare il train

Per una lista completa delle opzioni che si possono settare consiglio di dare un’occhiata alla documentazione ufficiale.

Valutazione dei risultati

Il modello Word2Vec definisce una ipersfera sulla cui superficie sono definite le rappresentazioni vettoriali delle parole del dizionario. In altri termini, ogni parola è rappresentata da un vettore di modulo unitario.

Tali vettori possono essere ricavati facilmente dal modello appena allenato:

word_vectors = model.wv
word_vectors

Ci aspettiamo che la topologia definita su questo spazio vettoriale sia rappresentativa delle relazioni semantiche tra le parole, cioé che termini di significato simile (quindi “vicini tra loro” in un’ipotetico spazio semantico) siano vicine anche nello spazio vettoriale definito dal Word2Vec, e viceversa.

Ciò può essere verificato qualitativamente scegliendo alcune parole e verificando le relazioni che esistono tra i rispettivi vettori.

Nel codice di questo tutorial ho scritto due funzioni:

  1. most_similar => data una parola, restituisce le 10 paroli del dizionario i cui vettori hanno la massima cosine similarity con la parola in esame.
  2. find_analogies => verifica la consistenza tra le analogie tra due coppie di parole e le corrispondenti operazioni vettoriali tra le loro rappresentazioni.

Codice

Trovate il codice descritto in questo post a questo link.