Applicazione delle reti neurali in ambito NLP: codifica One Hot vector, esempio in Keras.

AI, INTELLIGENZA ARTIFICIALE

In questa serie di articoli spiegherò come utilizzare le reti neurali in ambito di NLP (Natural Language processing) per effettuare classificazione di testo, analisi della semantica del testo, generazione automatica di testi. Occorre prima di tutto chiarire che le reti neurali lavorano esclusivamente su dati numerici, per cui, risulta intuitivo capire che  lavorare direttamente sul testo non è possibile. Il primo passo, pertanto, da fare, quando si vogliono utilizzare le reti neural, in ambito NLP,  è quello di imparare a convertire  il testo  in sequenze numeriche. L’operazione di trasformazione da testo a numeri, la chiameremo d’ora in poi tokenizzazione. Di seguito proverò a spiegare gli approcci che potrebbero essere utilizzati per effettuare questo tipo di trasformazione. L’operazione di  trasformazione del testo in sequenze numeriche introduce l’esigenza  di utilizzare un indice o un dizionario da cui sarà possibile evincere l’associazione univoca tra carattere/parola ed ID numerico.

Conversione dei soli caratteri

Come primo tentativo, potremmo trasformare un testo in una sequenza numerica, considerando la possibilità di trasformare ogni singolo carattere in un numero ed ottenere di conseguenza l’equivalente numerico di una stringa. In questa prima ipotesi un dizionario di riferimento potrebbe essere, ad esempio, quello del codice ascii, in quanto come sappiamo esso identifica univocamente i caratteri utilizzabili su un computer. Per completezza, riporto una tabella che riassume il codice ascii dei principali caratteri stampabili.

DEC OCT HEX BIN Simbolo
32 40 20 100000 space
33 41 21 100001 !
34 42 22 100010
35 43 23 100011 #
36 44 24 100100 $
37 45 25 100101 %
38 46 26 100110 &
39 47 27 100111
40 50 28 101000 (
41 51 29 101001 )
42 52 2A 101010 *
43 53 2B 101011 +
44 54 2C 101100 ,
45 55 2D 101101
46 56 2E 101110 .
47 57 2F 101111 /
48 60 30 110000 0
49 61 31 110001 1
50 62 32 110010 2
51 63 33 110011 3
52 64 34 110100 4
53 65 35 110101 5
54 66 36 110110 6
55 67 37 110111 7
56 70 38 111000 8
57 71 39 111001 9
58 72 3A 111010 :
59 73 3B 111011 ;
60 74 3C 111100 <
61 75 3D 111101 =
62 76 3E 111110 >
63 77 3F 111111 ?
64 100 40 1000000 @
65 101 41 1000001 A
66 102 42 1000010 B
67 103 43 1000011 C
68 104 44 1000100 D
69 105 45 1000101 E
70 106 46 1000110 F
71 107 47 1000111 G
72 110 48 1001000 H
73 111 49 1001001 I
74 112 4A 1001010 J
75 113 4B 1001011 K
76 114 4C 1001100 L
77 115 4D 1001101 M
78 116 4E 1001110 N
79 117 4F 1001111 O
80 120 50 1010000 P
81 121 51 1010001 Q
82 122 52 1010010 R
83 123 53 1010011 S
84 124 54 1010100 T
85 125 55 1010101 U
86 126 56 1010110 V
87 127 57 1010111 W
88 130 58 1011000 X
89 131 59 1011001 Y
90 132 5A 1011010 Z
91 133 5B 1011011 [
92 134 5C 1011100 \
93 135 5D 1011101 ]
94 136 5E 1011110 ^
95 137 5F 1011111 _
96 140 60 1100000 `
97 141 61 1100001 a
98 142 62 1100010 b
99 143 63 1100011 c
100 144 64 1100100 d
101 145 65 1100101 e
102 146 66 1100110 f
103 147 67 1100111 g
104 150 68 1101000 h
105 151 69 1101001 i
106 152 6A 1101010 j
107 153 6B 1101011 k
108 154 6C 1101100 l
109 155 6D 1101101 m
110 156 6E 1101110 n
111 157 6F 1101111 o
112 160 70 1110000 p
113 161 71 1110001 q
114 162 72 1110010 r
115 163 73 1110011 s
116 164 74 1110100 t
117 165 75 1110101 u
118 166 76 1110110 v
119 167 77 1110111 w
120 170 78 1111000 x
121 171 79 1111001 y
122 172 7A 1111010 z
123 173 7B 1111011 {
124 174 7C 1111100 |
125 175 7D 1111101 }
126 176 7E 1111110 ~
127 177 7F 1111111 delete

 

Il codice ascii ci consente di trasformare ogni singolo carattere in un numero, per essere più precisi, ogni carattere può essere rappresentato in un byte.

Cerchiamo, di capire cosa potrebbe succedere nel caso in cui decidessimo di utilizzare la trasformazione carattere -> codice ascii, e per farlo facciamo riferimento al seguente esempio:

Il gatto appartiene al genere dei felini

oppure

Al genere dei felini appartiene il gatto

Leggendo le 2 frasi, risulta evidente che, sebbene siano sinteticamente diverse, non lo sono invece da un punto di vista strettamente semantico. Ciò significa che il contenuto intrinseco delle due frasi è identico. Se provassimo pertanto a trasformare le 2 frasi in sequenze di numeri utilizzando il codice ascii il risultato sarebbe qualcosa del genere:

73 108 32 103 97 116 116 111 32 97 112 112 97 114 116 105 101 110 101 32 97 108 32 103 101 110 101 114 101 32 100 101 105 32 102 101 108 105 110 105

65 108 32 103 101 110 101 114 101 32 100 101 105 32 102 101 108 105 110 105 32 97 112 112 97 114 116 105 101 110 101 32 105 108 32 103 97 116 116 111

Oppure in binario:

01001001 01101100 00100000 01100111 01100001 01110100 01110100 01101111 00100000 01100001 01110000 01110000 01100001 01110010 01110100 01101001 01100101 01101110 01100101 00100000 01100001 01101100 00100000 01100111 01100101 01101110 01100101 01110010 01100101 00100000 01100100 01100101 01101001 00100000 01100110 01100101 01101100 01101001 01101110 01101001

01000001 01101100 00100000 01100111 01100101 01101110 01100101 01110010 01100101 00100000 01100100 01100101 01101001 00100000 01100110 01100101 01101100 01101001 01101110 01101001 00100000 01100001 01110000 01110000 01100001 01110010 01110100 01101001 01100101 01101110 01100101 00100000 01101001 01101100 00100000 01100111 01100001 01110100 01110100 01101111

Dal confronto delle due sequenze numeriche ci si rende conto che sebbene le 2 frasi siano semanticamente identiche, la rappresentazione numerica delle stesse risulta estremamente differente; il ché ci pone di fronte ad un primo problema, ovvero individuare un metodo che ci possa garantire in un certo senso una rappresentazione delle frasi che mantenga anche intatto il contenuto semantico. Risulta infatti chiaro che le due sequenze non sono confrontabili  e far comprendere ad una rete neurale che le stesse hanno un contenuto semantico identico può diventare estremamente oneroso.

Codifica One Hot vector

Proviamo, dunque, ad utilizzare un approccio differente, andando a codificare le parole e non i singoli caratteri. In questo caso, a differenza della trasformazione carattere -> testo, in cui disponevamo di uno dizionario standard, dobbiamo preoccuparci, prima di tutto,  di costruirci un dizionario di riferimento. Riprendiamo pertanto gli esempi precedenti e cerchiamo di  costruire il nostro dizionario.

Riprendiamo le due frasi:

Il gatto appartiene al genere dei felini

oppure

Al genere dei felini appartiene il gatto

effettuiamo la costruzione del dizionario assegnando  ad ogni singola parola un ID univoco :  parola -> indice. Ad esempio:

il -> 1
gatto -> 2
appartiene -> 3
al -> 4
genere -> 5
dei -> 6
felini -> 7

In tal caso le frasi diventerebbero:

Il gatto appartiene al genere dei felini -> 1 2 3 4 5 6 7

Al genere dei felini appartiene il gatto -> 4 5 6 7 3 1 2

Con questo approccio si nota chiaramente che sebbene le sequenze codificate siano diverse, si possono individuare facilmente le singole parole con lo stesso ID.  Se  applicassimo, ad esempio, un algoritmo di similitudine basato  sulla presenza o meno  delle parole contenute nelle due frasi, si troverebbe facilmente la similitudine delle stesse.  Questo approccio, in definitiva, utilizza  l’operazione di intersezione tra insiemi, quale metodo per calcolare la similitudine di due frasi ; infatti applicando l’operazione di intersezione su due insiemi i cui elementi sono le parole contenute nelle due frasi, si ottine che più l’intersezione si avvicina all’isimene pieno  più le frasi sono simili.

 

N.B

Esistono metodi più efficaci per la codifica delle parole, uno tra tutti è l’ embedding word2vec, ma, di questo, ne parleremo nel prossimo articolo.

Per il momento cerchiamo di capire come sia possibile con Keras effettuare la tokenizzazione delle frasi in poche righe di codice.

Come prima cosa dovremmo importare keras e Tokenizer

import keras
from keras.preprocessing.text import Tokenizer

Definiamo l’array delle frasi su cui costruire il dizionario.

sentence = [
'Il gatto appartiene al genere dei felini',
'Al genere dei felini appartiene il gatto'
]

Istanziamo il Tokenizer, da tener presente che il tag <oov> identifica tutte le parole che non hanno un equivalente in dizionario: out of vocabulary.

tk = Tokenizer(num_words =100,oov_token="<oov>")

generiamo il dizionario con fit_on_texts

tk.fit_on_texts(sentence)

creiamo l’associazione word -> index generato

word_index=tk.word_index

stampiamo il dizionario, come si vede <oov> avrà indice 1. Pertanto tutte le parole non incluse nel dizionario saranno associate con id 1:

print(word_index)

{‘<oov>’: 1, ‘il’: 2, ‘gatto’: 3, ‘appartiene’: 4, ‘al’: 5, ‘genere’: 6, ‘dei’: 7, ‘felini’: 8}

sentences = []

Proviamo a tokenizzare una frase in cui inseriamo anche parole non esistenti in dizionario:

sentences.append('Il gatto è bello')

eseguiamo la tokenizzazione com texts_to_sequences

sequences = tk.texts_to_sequences(sentences)

Stampiamo la sequenza tokenizzata:

print(sequences)
[[2, 3, 1, 1]]

 

seconda parte in progress>>

Comments