这篇教程来讲解自然语言处理中的词嵌入,也就是word embedding,并介绍GLoVe预训练参数的加载。
简单来说,word embedding是将单词转换为向量,从而进一步参与神经网络的计算。在tensorflow 2.0中,tensorflow.keras.layers.Embedding
实现了这一功能。其中embedding层计算了一个行向量乘矩阵的矩阵乘法,其中行向量是one hot形式的单词,矩阵为权重,所以本质上权重的每一行代表一个单词在语义空间的特征向量,该矩阵的行数为单词数。
下面以一个简单的文本分类实例具体讲解。数据库选用IMDB影评情感分析,该数据tensorflow提供官方下载与解析函数。
(train_sequences, train_labels), (test_sequences, test_labels) = tf.keras.datasets.imdb.load_data(num_words=word_num)
word_index = tf.keras.datasets.imdb.get_word_index()
其中sequences是序列化的结果,即以数字代替单词,num_words
参数是最大单词数,特别的,0代表空白符,用于后续补位,1代表起始符,2代表未知单词,3代表未使用的单词。后续4为最高词频单词’the’,5及之后按词频排序。
word_index即单词到数字的映射关系,有个坑是这个映射表里’the’对应的是1,之后需要特殊处理。
考虑到tensorflow的数据放在了googleapis.com上,对于网络状况不好的环境,也可以先从官方下载数据:imdb.npz,imdb_word_index.json,然后在上面的加载函数中指定path
参数。
将sequences输入到网络之前,还需要统一长度,小于指定长度的在前面补0,大于指定长度的截取后段。
train_sequences = pad_sequences(train_sequences, maxlen=max_len)
test_sequences = pad_sequences(test_sequences, maxlen=max_len)
此时,输入到网络的文本数据尺寸应为(batch_size, max_len)
,经过Embedding层,得到(batch_size, max_len, embedding_dim)
大小的词嵌入向量,其中embedding_dim
为语义空间维数。由此可以搭建一个简单的全连接网络对文本进行分类。
def Model():
model = tf.keras.Sequential()
model.add(Embedding(word_num, embedding_dim))
model.add(GlobalAveragePooling1D())
model.add(Dense(128, activation=tf.nn.relu))
model.add(Dense(2, activation='softmax'))
return model
然后便可以开始训练
word_num = 10000
max_len = 256
embedding_dim = 100
# get model
model = Model()
model.summary()
# train
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.fit(train_sequences,
train_labels,
batch_size=512,
epochs=10)
# test
test_loss, test_acc = model.evaluate(test_sequences, test_labels)
print(test_acc)
此时可以达到约87.1%的准确率。
更进一步,Embedding层的权重是随机初始化的,为了取得更好的结果,需要加载预训练的权重,如word2vec、GLoVe等,这里以GLoVe为例。
GLoVe权重可以从官方下载。考虑到权重的每一行代表一个单词在语义空间的特征向量,因此可以借助word_index
中的映射关系进行赋值。
def get_embedding_weight(weight_path, word_index):
# embedding_weight = np.zeros([word_num, embedding_dim])
embedding_weight = np.random.uniform(-0.05, 0.05, size=[word_num, embedding_dim])
cnt = 0
with open(weight_path, 'r') as f:
for line in f:
values = line.split()
word = values[0]
if word in word_index.keys() and word_index[word] + 3 < word_num:
"""
In tf.keras.dataset.imdb.load_data(), there are 4 special mark.
: 0
: 1
: 2
: 3
So word_index loaded from offical file, "mdb_word_index.json", need to +3.
"""
weight = np.asarray(values[1:], dtype='float32')
embedding_weight[word_index[word] + 3] = weight
cnt += 1
print('matched word num: {}'.format(cnt))
return embedding_weight
然后在Embedding层实例化时指定权重
Embedding(word_num, embedding_dim, weights=[embedding_weight])
我在实验中选用GLoVe_path = '/home1/dataset/GLoVe/glove.6B.100d.txt'
,在测试集上可以达到88.2%的准确率。相比随机初始化的方式,加载预训练权重提升了分类准确率。
考虑到本文仅作embedding层与GLoVe加载的简单教学,最终达到的准确率较低。代码中的超参数与模型本身可进一步调整以达到更好的结果。完整的代码可以在我的github上找到。
https://github.com/Apm5/tensorflow_2.0_tutorial/blob/master/RNN/simple_example.py