word2vec 训练embedding的一些理解

以 word2vec 中的CBOW模型为例

CBOW模型原理通过训练上游任务达到训练embedding的效果。

我们可以通过 代码示例, 和 数据流的 shape 的变化把握该模型究竟如何工作的。

==上游任务== 通过句子中的上下文 预测中心词。

首先对数据进行预处理;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#Create windows
# 将向量压平 变成一个一维列表
flatten = lambda outer_list: [item for inner_list in outer_list for item in inner_list]
# 句子结构: windows_size target_word window_size
windows = flatten([list(nltk.ngrams([MASK_TOKEN]*args.window_size + sentence.split(' ') + \
[MASK_TOKEN] * args.window_size, args.window_size * 2 + 1)) \
for sentence in tqdm(cleaned_sentences)])

# Create cbow data
# 将目标词与上下文切分,即 context 与 target
data = []
for window in tqdm(windows):
# 目标词,需要预测的词
# windows_size target_word window_size
target_token = window[args.window_size]
context = []
for i, token in enumerate(window):
if token == MASK_TOKEN or i == args.window_size:
continue
else:
context.append(token)

data.append([' '.join(token for token in context), target_token])


# Convert to dataframe
cbow_data = pd.DataFrame(data, columns=["context", "target"])

以上代码,对句子进行滑动采样。滑动窗size:2*window_size+1 ,然后将采样结果存入 dict 中, 中心词为预测目标,在 dict 中对应target 这个key;左右长度为window_size 的上下文拼接,在dict中对应context这个key。采样结果定长,不足长度用MASK_TOKEN 补齐。数据预处理结束。

模型主要模块

我们要feed进神经网络的输入是 dict 中的context, 用batch_size 记为minibatch的大小。输入x_in的shape为:(batch_size, 2*window_size), 我们将这样一个矩阵输入 embedding 层。embedding 层初始化如下:

1
2
3
self.embedding = nn.Embedding(num_embeddings=vocabulary_size,
embedding_dim=embedding_size,
padding_idx=padding_idx)

这里用 pytorch 中封装好的 torch.nn.Embedding函数来实现。(也可以造轮子,自己实现该模块)

embedding 层的两个重要参数 字典的长度(len(vocab)),记作 vocab_size,和你想要训练的embedding 的size,记作 embed_size。

神经网络在NLP中常见的pipeline

预处理完的数据,不能直接丢进神经网络模型,需要对其数值化。这部分的处理是套路化的几部。在大部分NLP任务几乎都有

  1. 首先 根据原始数据 构建 vocab,该模块的核心是构建一张 word-to-index 的dict。完成 word, index的互查

  2. vectorizer 模块,这部分的核心功能是 完成对 原始数据的 数值化,核心函数 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def vectorize(self, context, vector_length=-1):
    """
    :param context(str): the string of words separated by a space
    :param vector_length(int): an argument for forcing the length of index vectors
    :return:
    """
    indices = [self.cbow_vocab.lookup_token(token) for token in context.split(' ')]
    if vector_length < 0:
    vector_length = len(indices)

    out_vector = np.zeros(vector_length, dtype=np.int64)
    out_vector[:len(indices)] = indices
    out_vector[len(indices):] = self.cbow_vocab.mask_index

    return out_vector
  3. dataset 模块,对 pytorch 中 torch.utils.data.DataSet 的继承。对一个封装好的对象的继承,可以很好的完成数据到 torch.tensor的转换,需要人工做的主要部分是在其_getitem_()函数中实现对原始数据的vectorize.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    def __getitem__(self, index):
    """the primary entry point method for PyTorch datasets

    Args:
    index (int): the index to the data point
    Returns:
    a dictionary holding the data point's features (x_data) and label (y_target)
    """
    row = self._target_df.iloc[index]

    context_vector = \
    self._vectorizer.vectorize(row.context, self._max_seq_length)
    target_index = self._vectorizer.cbow_vocab.lookup_token(row.target)

    return {'x_data': context_vector,
    'y_target': target_index}

经过上述数值化处理后的结果可以feed进神经网络模型。这时x_in的size 为(batch_size, 2*window_size), 值为 token对应的index。

然后将x_in feed进embedding层后,生成的结果 shape:(batch_size, 2*window_size, embed_size), 为了训练 需要 去除 dim=1的维度,使用sum函数在dim=1的维度上求和。然后feed进一个线性层。

线性层参数如下:

1
2
self.fc1 = nn.Linear(in_features=embedding_size,
out_features=vocabulary_size)

得到结果 y_out ; shape 为 (batch_size, vocab_size), 然后拿来后 y_target, shape (batch_size),求loss, 误差反向传播,更新优化器,这样 embedding层和线性层都得到训练。

==当然对于这个任务来说,我们关心的是训练好的embedding参数。==