持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情
构建单词向量
构建单词向量的核心思想是,在向量空间中,每个单词周围都存在着与之相似的单词。例如:“queen
” 和 “princess
” 单词的周围会出现类似的词,如 “kingdom
”。从某种意义上说,这些词的上下文同样是相似的。
使用句子 “I love watching movie
” 和 “I like watching movie
”,当我们一个句子中的某个单词作为输出,而句子中的其余单词作为输入时,可以构造以下数据集:
输入 | 输出 | ||
---|---|---|---|
love | watching | movie | I |
I | watching | movie | love |
I | love | movie | watching |
I | love | watching | movie |
like | watching | movie | I |
I | watching | movie | like |
I | like | movie | watching |
I | like | watching | movie |
当我们将某一个单词用作输出,其余单词用作输入,将输入和输出进行独热编码后得到以下形式的向量:
输入向量 | 输出向量 | ||||||||
---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
1 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 |
1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 |
1 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 |
可以看到,输入向量的第一行为 {0, 1, 1, 1, 0}
,因为输入单词的索引为 {1, 2, 3}
,输出为 {1, 0, 0, 0, 0}
,因为输出单词的索引为 {0}
。 如果我们使用的神经网络中隐藏层包含三个神经元,则神经网络架构如下所示:
网络中每层的信息如下:
网络层 | 尺寸 | 描述 |
---|---|---|
输入层 | 5 |
每个输入向量尺寸为 5 |
输入层权重 | 5x3 |
隐藏层中的 3 个神经元各有 5 个连接到输入层的权重 |
隐藏层 | 3 |
隐藏层包含 3 个神经元 |
输出层权重 | 3x5 |
由于有 5 个不同单词,因此 3 个隐藏单元输出映射到输出层的 5 个输出 |
输出层 | 5 |
输出向量的尺寸为 5 ,每一单词对应一个预测单词概率 |
在构建单词向量时,在隐藏层中并不使用激活函数。使用 softmax
函数处理输出层输出值,以便得到单词概率,使用交叉熵损失作为损失函数,使用 Adam
优化器优化网络权重值。当向网络中输入单词(而非输入语句)的独热编码时,给定单词的编码向量可以使用隐藏层的输出值表示。
从零开始构建单词向量
根据我们在上一节中介绍的单词向量的生成方式,我们使用 Keras
实现单词编码向量神经网络。 首先,定义输入句子:
docs = ["I love watching movie", "I like watching movie"]
在以上语句中,我们期望 love
和 like
的词向量是相似的,因为 love
和 like
的上下文是完全相同的。 然后,我们为每个句子创建一个独热编码:
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer(min_df=0, token_pattern=r"\b\w+\b")
vectorizer.fit(docs)
vectorizer
定义了将文档转换为向量格式的参数。此外,通过传递参数 min_df
确保在 CountVectorizer
中不会过滤掉诸如 I
之类的词,使用定义的输入句子拟合 vectorizer
得到合适的单词向量化模型。 将文档 docs
转换为向量格式:
vector = vectorizer.transform(docs)
验证执行转换后的语句向量:
print(vectorizer.vocabulary_)
print(vector.shape)
print(vector.toarray())
vocabulary_
返回各种单词的索引,而 vector.toarray
将返回句子的独热编码,输出结果如下:
{'i': 0, 'love': 2, 'watching': 4, 'movie': 3, 'like': 1}
(2, 5)
[[1 0 1 1 1]
[1 1 0 1 1]]
创建输入和输出数据集:
x = []
y = []
for i in range(len(docs)):
for j in range(len(docs[i].split())):
t_x = []
t_y = []
for k in range(4):
if(j==k):
t_y.append(docs[i].split()[k])
continue
else:
t_x.append(docs[i].split()[k])
x.append(t_x)
y.append(t_y)
x2 = []
y2 = []
for i in range(len(x)):
x2.append(' '.join(x[i]))
y2.append(' '.join(y[i]))
从前面的代码中,我们创建了输入和输出数据集,我们可以打印数据集,查看其内容:
print(x2)
print(y2)
打印粗的输入和输出数据如下:
['love watching movie', 'I watching movie', 'I love movie', 'I love watching', 'like watching movie', 'I watching movie', 'I like movie', 'I like watching']
['I', 'love', 'watching', 'movie', 'I', 'like', 'watching', 'movie']
将前面的输入和输出单词转换为向量:
vector_x = vectorizer.transform(x2)
vector_y = vectorizer.transform(y2)
vector_x = vector_x.toarray()
vector_y = vector_y.toarray()
# 打印输入与输出数组
print('Input: ', vector_x)
print('Output: ' vector_y)
打印出的输入和输出数组如下:
Input: [[0 0 1 1 1]
[1 0 0 1 1]
[1 0 1 1 0]
[1 0 1 0 1]
[0 1 0 1 1]
[1 0 0 1 1]
[1 1 0 1 0]
[1 1 0 0 1]]
Output: [[1 0 0 0 0]
[0 0 1 0 0]
[0 0 0 0 1]
[0 0 0 1 0]
[1 0 0 0 0]
[0 1 0 0 0]
[0 0 0 0 1]
[0 0 0 1 0]]
根据定义的神经网络,构建模型:
from keras.layers import Dense
from keras.models import Sequential
model = Sequential()
model.add(Dense(3, input_shape=(5,)))
model.add(Dense(5,activation='sigmoid'))
model.summary()
模型简要架构信息输入如下:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 3) 18
_________________________________________________________________
dense_1 (Dense) (None, 5) 20
=================================================================
Total params: 38
Trainable params: 38
Non-trainable params: 0
_________________________________________________________________
编译并拟合模型:
model.compile(loss='categorical_crossentropy',optimizer='adam')
model.fit(vector_x, vector_y, epochs=1000, batch_size=2,verbose=1)
通过获取中间层值来提取词向量,其中输入是每个单个词的编码向量:
from keras.models import Model
layer_name = 'dense'
intermediate_layer_model = Model(inputs=model.input,outputs=model.get_layer(layer_name).output)
在以上代码中,我们从目标层中提取输出——通过模型中的名为 dense
的层获取单词编码向量。 接下来,向网络中传递单词的独热编码向量作为输入,提取中间层的输出:
for i in range(len(vectorizer.vocabulary_)):
word = list(vectorizer.vocabulary_.keys())[i]
word_vec = vectorizer.transform([list(vectorizer.vocabulary_.keys())[i]]).toarray()
print(word, intermediate_layer_model.predict(word_vec))
各个单词的编码向量如下:
i [[-1.41066 0.02432728 -1.0654368 ]]
love [[-1.1692711 1.7719828 0.54331756]]
watching [[ 1.163808 1.908086 -1.5191256]]
movie [[0.01165223 2.0688105 1.532387 ]]
like [[-1.197992 1.662775 0.5817174]]
可以看出,在以上单词编码向量中,“love
” 和 “like
” 这两个单词之间的相关性更高,因此可以更好地表示单词向量。