词向量的训练
前言
词向量技术是一种分布式表示【分散的把词信息分布到各个向量中】
词向量技术有以下特点:
- density稠密性
- semantic 语义
- capacity 表达能力
- global generalization 泛化能力
储备知识📋
np.argsort
np.argsort(v_count) 是用于返回数组 v_count 中元素的索引,并按照对应元素的值进行升序排序。[::-1] 则是对索引数组进行逆序操作,即倒序排列
nn.Embedding
使用nn.Embedding类可以方便地定义并初始化一个词嵌入层。在创建nn.Embedding对象时,需要指定两个参数:num_embeddings和embedding_dim。num_embeddings表示词汇表的大小,即不同离散符号的数量。embedding_dim表示词嵌入的维度,即每个离散符号映射到的实数向量的长度。
CBow模型
根据周围的词来预测本词;类似于完形填空
与经典的NNLM(前反馈模型)相比,没有了非线性的隐藏层,并且映射层对所有词是共享的
- 输入层:输入c个词向量
- 投影层:将输入层的c个向量进行累加
- 输出层:(1)【Huffman树方法】哈夫曼树为例,以各词在语料库中出现的次数当权值构造Huffman🌲;(2)【negative sampling方法】得到目标函数,然后使用梯度上升法更新参数
数据处理与准备
def process_w2v_data(corpus, skip_window=2, method="skip_gram"):
#将语料库中的句子分词,频率降序的得到所有的词汇,并转换为 Numpy 数组
# 将语料库中的句子按空格进行分词
all_words = [sentence.split(" ") for sentence in corpus]
# 将嵌套列表展开:chain函数能够将多个可迭代对象连接在一起,返回一个新的迭代器
all_words = np.array(list(itertools.chain(*all_words)))
# return_counts=True 表示返回出现的次数
vocab, v_count = np.unique(all_words, return_counts=True)
'''v_count:是各个词出现的频率
[ 5 8 6 14 7 6 9 3 7 16 9 6 5 6 8 5 8 4 7 7 1 10 5 5
2 7 3 7 1 6]'''
# np.argsort(v_count) 返回了 v_count 数组中元素的索引,并按照对应元素的值进行升序排序。
# 通过在索引数组加上 [::-1],可以实现对索引数组的逆序操作
vocab = vocab[np.argsort(v_count)[::-1]]
#建立从词汇到权重的映射 v2i 和从索引到词汇的映射 i2v
print("All vocabularies are sorted by frequency in decresing oreder")
v2i = {v: i for i, v in enumerate(vocab)}
i2v = {i: v for v, i in v2i.items()}
#将每个固定窗口内的上下文词与中心词构成一个训练样本
pairs = []
pairs2 = []
# js其中包含了从 -skip_window 到 skip_window + 1 的整数
js = [i for i in range(-skip_window, skip_window + 1) if i != 0]
# w_idx 是配对好的词的权重表示
for c in corpus:
# 将每个句子拆分成单词,并存储在 words 列表中
words = c.split(" ")
# w_idx 表示将句子拆分为单词后,每个单词对应的索引
w_idx = [v2i[w] for w in words]
前面部分是一样的
elif method.lower() == "cbow":
#遍历每个中心词的索引 w_idx[i]
for i in range(skip_window, len(w_idx) - skip_window):
context = []
for j in js:
#上下文词的索引列表 context 和中心词的索引 w_idx[i] 组合成一个列表
context.append(w_idx[i + j])
pairs.append(context + [w_idx[i]]) #处理数据时把中心词当作为最后一个数据
CBOW模型
class CBOW(nn.Module):
def __init__(self, v_dim, emb_dim): #v_dim=30 ,emb_dim=2
super().__init__()
self.v_dim = v_dim # 词汇表的大小和词嵌入的维度
self.embeddings = nn.Embedding(v_dim, emb_dim)
self.embeddings.weight.data.normal_(0, 0.1)
# self.opt = torch.optim.Adam(0.01)
self.hidden_out = nn.Linear(emb_dim, v_dim) # 2----30
#self.parameters() 来获取模型中需要进行梯度更新的参数。
#momentum 参数指定了动量的大小,可以用来加速优化过程,
#lr 参数设置学习率,决定了每次更新参数时的步长或者梯度下降的速度
self.opt = torch.optim.SGD(self.parameters(), momentum=0.9, lr=0.01)
def forward(self, x, training=None, mask=None):
# x.shape = [n,skip_window*2] = [ 16,4 ]
o = self.embeddings(x) # [n, skip_window*2, emb_dim] = [16,4,2]#进行词嵌入
# dim=1 表示在第一个维度(通常是行)上进行平均操作。这意味着函数将沿着第一个维度对 o 进行平均,并且返回一个维度减少的张量
o = torch.mean(o, dim=1) # [n, emb_dim] =[16,2]
return o
梯度更新
def loss(self, x, y, training=None):
embedded = self(x, training)
pred = self.hidden_out(embedded) # (16,2)----(16,30)
return cross_entropy(pred, y) #这里会自动对y进行embed再计算coss-entropy
def step(self, x, y):
self.opt.zero_grad() #梯度清零
loss = self.loss(x, y, True)
loss.backward()
self.opt.step()
return loss.detach().numpy()
Skip - Gram模型
提示:skip- Gram模型比CBow模型更加合理
Skip-Gram 相比 CBOW 最大的不同,就是剔除掉了中间的那个 SUM 求和的过程
C:上下文; W:中心词
C’:所有词库其他词
每个词都会作为中心词/上下文词出现,它的每个词有两个矩阵,一个是U矩阵【作为上下文的矩阵】,一个是V矩阵【作为 中心词时的矩阵】
skip-gram方法是通过本词来预测周围的词;它设置了一个window size窗口的大小
- 输入层:只含当前样本的中心词w的词向量v(w)
- 投影层:是一个恒等映射,v(w)映射后还是v(w)
- 输出层: (1) 【Huffman树方法】同CBow,也是一颗Huffman 🌲 ;
(2)【negative sampling方法】
def process_w2v_data(corpus, skip_window=2, method="skip_gram"):
#将语料库中的句子分词,频率降序的得到所有的词汇,并转换为 Numpy 数组
# 将语料库中的句子按空格进行分词
all_words = [sentence.split(" ") for sentence in corpus]
# 将嵌套列表展开:chain函数能够将多个可迭代对象连接在一起,返回一个新的迭代器
all_words = np.array(list(itertools.chain(*all_words)))
# return_counts=True 表示返回出现的次数
vocab, v_count = np.unique(all_words, return_counts=True)
'''v_count:是各个词出现的频率
[ 5 8 6 14 7 6 9 3 7 16 9 6 5 6 8 5 8 4 7 7 1 10 5 5
2 7 3 7 1 6]'''
# np.argsort(v_count) 返回了 v_count 数组中元素的索引,并按照对应元素的值进行升序排序。
# 通过在索引数组加上 [::-1],可以实现对索引数组的逆序操作
vocab = vocab[np.argsort(v_count)[::-1]]
#建立从词汇到权重的映射 v2i 和从索引到词汇的映射 i2v
print("All vocabularies are sorted by frequency in decresing oreder")
v2i = {v: i for i, v in enumerate(vocab)}
i2v = {i: v for v, i in v2i.items()}
#将每个固定窗口内的上下文词与中心词构成一个训练样本
pairs = []
pairs2 = []
# js其中包含了从 -skip_window 到 skip_window + 1 的整数
js = [i for i in range(-skip_window, skip_window + 1) if i != 0]
# w_idx 是配对好的词的权重表示
for c in corpus:
# 将每个句子拆分成单词,并存储在 words 列表中
words = c.split(" ")
# w_idx 表示将句子拆分为单词后,每个单词对应的索引
w_idx = [v2i[w] for w in words]
前面两个模型一样
# Skip-gram 模型 :w_idx中存放的是各个配对的索引
if method.lower() == "skip_gram":
for i in range(len(w_idx)):
for j in js:
# 计算得到的索引超出了边界,将会通过 continue 语句跳过,以避免越界错误
if i + j < 0 or i + j >= len(w_idx):
continue
pairs.append((w_idx[i], w_idx[i + j])) #每次只输出自己和自己旁边的
pairs2.append((words[i], words[i + j]))
进行输出看一下效果:第一组数据为"5 2 4 8 6 2 3 6 4"
# 转换为 NumPy 数组
pairs = np.array(pairs)
print("10 expample pairs:\n", pairs[:10],pairs2[:10])
'''
10 expample pairs:
[[16 14]
[16 9]
[14 16]
[14 9]
[14 12]
[ 9 16]
[ 9 14]
[ 9 12]
[ 9 3]
[12 14]] [('5', '2'), ('5', '4'), ('2', '5'), ('2', '4'), ('2', '8'), ('4', '5'), ('4', '2'), ('4', '8'), ('4', '6'), ('8', '2')]
'''
# loss用于计算模型的损失函数
def loss(self, x, y, training=None):
embedded = self(x, training) # 输入样本进行词嵌入
pred = self.hidden_out(embedded) # 进行线性变换,得到与原始词汇维度相同的预测结果
return cross_entropy(pred, y)
#用于对模型进行一次参数更新
def step(self, x, y):
self.opt.zero_grad() #梯度清零
#第三个参数True表示当前处于训练模式,可能会在损失函数中使用一些特定的训练技巧
loss = self.loss(x, y, True)
loss.backward()
self.opt.step()
return loss.detach().numpy() #loss.detach()返回一个新的没有梯度信息的Tensor
class SkipGram(nn.Module):
def __init__(self, v_dim, emb_dim): #v_dim = 30,emb_dim = 2
super().__init__()
self.v_dim = v_dim # 词的维度,也就是词的数量
self.embeddings = nn.Embedding(v_dim, emb_dim) # 词嵌入的维度,可以得到[n,30,2]的向量
self.embeddings.weight.data.normal_(0, 0.1) # 进行normal
self.hidden_out = nn.Linear(emb_dim, v_dim) # 进行一个线性变换得到v_dim=30维的
self.parameters()
#创建了一个Adam优化器,表示获取模型中的所有参数,lr学习率 = 0.01
self.opt = torch.optim.Adam(self.parameters(), lr=0.01) #
def forward(self, x, training=None, mask=None):
# x.shape = [n,]
o = self.embeddings(x) # [n, emb_dim] = [8,2]
return o
关键技术
hierarchical softmax技术
梯度计算:hierarchical softmax 是word2 vector中用于提高性能的一项关键技术
Huffman树中存在一条从根节点到词w的路径pw,路径pw唯一,且路径上存在lw-1个分支,将每个分支看成一个二分类,每一次分类产生一个概率,这些概率乘起来就是p(w|context(w))
negative sampling技术
用来提高训练速度并改善所得到的词向量的质量;不再使用Huffman树,而是利用随机负采样
增大正样本的概率同时降低负样本的概率
Elmo模型
Bert模型
总结
CBOW(Continuous Bag-of-Words)和Skip-gram是两个常用的词嵌入模型,用于将词语映射到低维向量表示。
在CBOW模型中,给定上下文词语预测目标词语,模型通过将上下文词语的词向量进行求和或平均来得到目标词语的表示,然后使用该表示来预测目标词语。因此,CBOW模型的训练过程需要对上下文词语的词向量进行梯度更新。
在Skip-gram模型中,给定目标词语预测上下文词语,模型通过将目标词语的词向量输入到一个神经网络中,以预测上下文词语。在这种情况下,Skip-gram模型的训练过程并不需要对词向量进行梯度更新,而是直接更新神经网络的参数。
这两种模型的差异导致了它们在训练过程中对梯度更新的需求不同。CBOW模型需要对词向量进行更新,因为它直接使用词向量来表示上下文,并将其用于目标词语的预测。而Skip-gram模型通过神经网络进行预测,不需要对词向量进行直接的更新,但它仍然需要更新神经网络的参数,以优化预测结果。
代码来自莫烦python,感谢莫烦老师
莫烦python老师主页