詞向量,英文名叫Word Embedding,在天然語言處理中,用於抽取語言模型中的特徵,簡單來講,就是把單詞用一個向量來表示。最著名的Word Embedding模型應該是托馬斯·米科洛夫(Tomas Mikolov)在Google帶領的研究團隊創造的Word2vec。javascript
詞向量的訓練原理就是爲了構建一個語言模型,咱們假定一個詞的出現機率是由它的上下問來決定的,那麼咱們找來不少的語素來訓練這個模型,也就是經過上下文來預測某個詞語出現的機率。java
如上圖所示,詞嵌入向量的訓練主要有兩種模式:python
那麼訓練出來的詞向量它的含義是什麼呢?express
詞向量是該單詞映射到一個n維空間的表示,首先,全部的單詞只有在表示爲數學上的向量後在能參與神經網絡的運算,其次,單詞在空間中的位置反映了詞與詞之間的關係,距離相近的詞可能意味着它們有相近的含義,或者常常一出現。api
用神經網絡構建語言模型的時候,Embedding經常是做爲第一個層出現的,它就是從文本中提取數字化的特徵。那麼咱們今天就看看如何利用TensorflowJS在訓練一個詞向量嵌入模型吧。瀏覽器
首先倒入個人文本,這裏個人文本很簡單,你能夠替換任何你想要訓練的文本bash
const sentence = "Mary and Samantha arrived at the bus station early but waited until noon for the bus.";
而後,抽取文本中全部的單詞序列,在天然語言處理中,Tokenize是意味着把文本變成序列,我這個例子中的單詞的抽取用了很簡單的regular expression,實際的應用中,你可使用不一樣的天然語言處理庫提供的Tokenize方法。TensorflowJS中並無提供Tokenize的方法。(Tensorflow 中由提供 https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/preprocessing/text/Tokenizer)網絡
const tokenize = words => { return words.match(/[^\s\.]+/g); } // tokenize const tokens = tokenize(sentence);
單詞序列以下:函數
["Mary", "and", "Samantha", "arrived", "at", "the", "bus", "station", "early", "but", "waited", "until", "noon", "for", "the", "bus"]
下一步咱們要對全部的單詞編碼,也就是用數字來表示每個單詞學習
const encode = tokens => { let encoding_map = {}; let decoding_map = {}; let index = 0; tokens.map( token => { if( !encoding_map.hasOwnProperty(token) ) { const pair = {}; const unpair = {}; pair[token] = index; unpair[index] = token; encoding_map = {...encoding_map, ...pair}; decoding_map = {...decoding_map, ...unpair}; index++; } }) return { map: encoding_map, count: index, encode: function(word) { return encoding_map[word]; }, decode: function(index) { return decoding_map[index]; } }; } const encoding = encode(tokens); const vocab_size = encoding.count;
編碼的方式很簡單,咱們統計每個出現的單詞,而後給每個單詞一個對應的數字。咱們使用了兩個map,一個存放從單詞到數字索引的映射,另外一個存放相反的從索引到單詞的映射。這個例子中,一種出現了14個單詞,那麼索引的數字就是從0到13。
下一步,咱們來準備訓練數據:
const to_one_hot = (index, size) => { return tf.oneHot(index, size); } // training data const data = []; const window_size = 2 + 1; for ( let i = 0; i < tokens.length; i ++ ) { const token = tokens[i]; for ( let j = i - window_size; j < i + window_size; j ++) { if ( j >= 0 && j !=i && j < tokens.length) { data.push( [ token, tokens[j]] ) } } } const x_train_data = []; const y_train_data = []; data.map( pair => { x = to_one_hot(encoding.encode(pair[0]), vocab_size); y = to_one_hot(encoding.encode(pair[1]), vocab_size); x_train_data.push(x); y_train_data.push(y); }) const x_train = tf.stack(x_train_data); const y_train = tf.stack(y_train_data); console.log(x_train.shape); console.log(y_train.shape);
one_hot encoding是一種經常使用的編碼方式,例如,對於索引爲2的單詞,它的one_hot encoding 就是[0,0,1 .... 0], 就是索引位是1其它都是0 的向量,向量的長度和全部單詞的數量相等。這裏咱們定義的上下文滑動窗口的大小爲2,對於每個詞,找到它的先後出現的4個單詞構成4對,用該詞做爲訓練的輸入,上下文的四個詞做爲目標。(注意在文首尾出的詞上下文不足四個)
0: (2) ["Mary", "and"] 1: (2) ["Mary", "Samantha"] 2: (2) ["and", "Mary"] 3: (2) ["and", "Samantha"] 4: (2) ["and", "arrived"] 5: (2) ["Samantha", "Mary"] 6: (2) ["Samantha", "and"] 7: (2) ["Samantha", "arrived"] 8: (2) ["Samantha", "at"] 9: (2) ["arrived", "Mary"] 10: (2) ["arrived", "and"] ... ...
訓練集合如上圖所示,第一個詞是訓練的輸入,第二次的訓練的目標。咱們這裏採用的方法相似Skip-Gram,由於上下文是預測對象。
訓練集合準備好,就能夠用開始構建模型了。
const build_model = (input_size,output_size) => { const model = tf.sequential(); model.add(tf.layers.dense({ units: 2, inputShape: output_size, name:'embedding' })); model.add(tf.layers.dense( {units: output_size, kernelInitializer: 'varianceScaling', activation: 'softmax'})); return model; } const model = build_model(vocab_size, vocab_size); model.compile({ optimizer: tf.train.adam(), loss: tf.losses.softmaxCrossEntropy, metrics: ['accuracy'], });
咱們的模型很簡單,是一個兩層的神經網絡,第一層就是咱們要訓練的嵌入層,第二層是一個激活函數爲Softmax的Dense層。由於咱們的目標是預測到底是哪個單詞,其實就是一個分類問題。這裏要注意得是我是用的嵌入層的unit是2,也就是說訓練的向量的長度是2,實際用戶能夠選擇任何長度的詞向量空間,這裏我用2是爲了便於下面的詞向量的可視化,省去了降維的操做。
訓練的過程也很簡單:
const batchSize = 16; const epochs = 500; model.fit(x_train, y_train, { batchSize, epochs, shuffle: true, });
訓練完成後,咱們能夠利用該模型的embeding層來生成每個單詞的嵌入向量。而後在二維空間中展現。
// visualize embedding layer const embedding_layer = model.getLayer('embedding'); const vis_model = tf.sequential(); vis_model.add(embedding_layer); const vis_result = vis_model.predict(predict_inputs).arraySync(); console.log(vis_result); const viz_data = []; for ( let i = 0; i < vocab_size; i ++ ) { const word = encoding.decode(i); const pos = vis_result[i]; console.log(word,pos); viz_data.push( { label:word, x:pos[0], y :pos[1]}); } const chart = new G2.Chart({ container: 'chart', width: 600, height: 600 }); chart.source(viz_data); chart.point().position('x*y').label('label'); chart.render();
生成的詞向量的例子以下:
"Mary" [-0.04273216053843498, -0.18541619181632996] "and" [0.09561611711978912, -0.29422900080680847] "Samantha" [0.08887559175491333, 0.019271137192845345] "arrived" [-0.47705259919166565, -0.024428391829133034]
可視化關係以下圖:
詞向量嵌入經常是天然語言處理的第一步操做,用於提取文本特徵。咱們演示瞭如何訓練一個模型來構建詞向量。固然實際操做中,你能夠直接使用https://js.tensorflow.org/api/latest/#layers.embedding 來構建你的文本模型,本文是爲了演示詞向量的基本原理。代碼參見https://codepen.io/gangtao/full/jJqbQb