在瀏覽器中進行深度學習:TensorFlow.js (九)訓練詞向量

詞向量,英文名叫Word Embedding,在自然語言處理中,用於抽取語言模型中的特徵,簡單來說,就是把單詞用一個向量來表示。最著名的Word Embedding模型應該是托馬斯·米科洛夫(Tomas Mikolov)在Google帶領的研究團隊創造的Word2vec。

詞向量的訓練原理就是為了構建一個語言模型,我們假定一個詞的出現概率是由它的上下問來決定的,那麼我們找來很多的語素來訓練這個模型,也就是通過上下文來預測某個詞語出現的概率。

在瀏覽器中進行深度學習:TensorFlow.js (九)訓練詞向量

如上圖所示,詞嵌入向量的訓練主要有兩種模式:

  • 連續詞袋 CBOW, 在這個方法中,我們用出現在該單詞的上下文的詞來預測該單詞出現的概率,如上圖就是該單詞的前兩個和後兩個。然後我們可以掃描全部的訓練語素(所有的句子),對於每一次出現的詞都找到對於的上下文的4個詞,這樣我們就可以構建一個訓練集合來訓練詞向量了。
  • Skip-Gram和CBOW正好相反,它是用該單詞來預測前後的4個上下文的單詞。注意這裡和上面的4個都是例子,你可以選擇上下文的長度。

那麼訓練出來的詞向量它的含義是什麼呢?

在瀏覽器中進行深度學習:TensorFlow.js (九)訓練詞向量

詞向量是該單詞映射到一個n維空間的表示,首先,所有的單詞只有在表示為數學上的向量後在能參與神經網絡的運算,其次,單詞在空間中的位置反映了詞與詞之間的關係,距離相近的詞可能意味著它們有相近的含義,或者經常一出現。

用神經網絡構建語言模型的時候,Embedding常常是作為第一個層出現的,它就是從文本中提取數字化的特徵。那麼我們今天就看看如何利用TensorflowJS在訓練一個詞向量嵌入模型吧。

倒入文本

首先倒入我的文本,這裡我的文本很簡單,你可以替換任何你想要訓練的文本

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]

可視化關係如下圖:

在瀏覽器中進行深度學習:TensorFlow.js (九)訓練詞向量

總結

詞向量嵌入常常是自然語言處理的第一步操作,用於提取文本特徵。我們演示瞭如何訓練一個模型來構建詞向量。當然實際操作中,你可以直接使用https://js.tensorflow.org/api/latest/#layers.embedding 來構建你的文本模型,本文是為了演示詞向量的基本原理。代碼參見https://codepen.io/gangtao/full/jJqbQb

參考










分享到:


相關文章: