Keras深度学习——从零开始构建单词向量

持续创作,加速成长!这是我参与「掘金日新计划 · 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"]

在以上语句中,我们期望 lovelike 的词向量是相似的,因为 lovelike 的上下文是完全相同的。 然后,我们为每个句子创建一个独热编码:

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” 这两个单词之间的相关性更高,因此可以更好地表示单词向量。

猜你喜欢

转载自juejin.im/post/7113448241525424142