参考伯禹学习平台《动手学深度学习》课程内容内容撰写的学习笔记
原文链接:https://www.boyuai.com/elites/course/cZu18YmweLv10OeV/lesson/vPFktupNxTK8CzV789PCeV
感谢伯禹平台,Datawhale,和鲸,AWS给我们提供的免费学习机会!!
总的学习感受:伯禹的课程做的很好,课程非常系统,每个较高级别的课程都会有需要掌握的前续基础知识的介绍,因此很适合本人这种基础较差的同学学习,建议基础较差的同学可以关注伯禹的其他课程:
数学基础:https://www.boyuai.com/elites/course/D91JM0bv72Zop1D3
机器学习基础:https://www.boyuai.com/elites/course/5ICEBwpbHVwwnK3C

词嵌入基础

我们在“循环神经网络的从零开始实现”一节中使用 one-hot 向量表示单词,虽然它们构造起来很容易,但通常并不是一个好选择。一个主要的原因是,one-hot 词向量无法准确表达不同词之间的相似度,如我们常常使用的余弦相似度。

Word2Vec 词嵌入工具的提出正是为了解决上面这个问题,它将每个词表示成一个定长的向量,并通过在语料库上的预训练使得这些向量能较好地表达不同词之间的相似和类比关系,以引入一定的语义信息。基于两种概率模型的假设,我们可以定义两种 Word2Vec 模型:

  1. Skip-Gram 跳字模型:假设背景词由中心词生成,即建模 P(wo∣wc)P(w_o\mid w_c)P(wowc),其中 wcw_cwc 为中心词,wow_owo 为任一背景词;

  1. CBOW (continuous bag-of-words) 连续词袋模型:假设中心词由背景词生成,即建模 P(wc∣Wo)P(w_c\mid \mathcal{W}_o)P(wcWo),其中 Wo\mathcal{W}_oWo 为背景词的集合。

在这里我们主要介绍 Skip-Gram 模型的实现,CBOW 实现与其类似,读者可之后自己尝试实现。后续的内容将大致从以下四个部分展开:

  1. PTB 数据集
  2. Skip-Gram 跳字模型
  3. 负采样近似
  4. 训练模型

import collections
import math
import random
import sys
import time
import os
import numpy as np
import torch
from torch import nn
import torch.utils.data as Data

PTB 数据集

简单来说,Word2Vec 能从语料中学到如何将离散的词映射为连续空间中的向量,并保留其语义上的相似关系。那么为了训练 Word2Vec 模型,我们就需要一个自然语言语料库,模型将从中学习各个单词间的关系,这里我们使用经典的 PTB 语料库进行训练。PTB (Penn Tree Bank) 是一个常用的小型语料库,它采样自《华尔街日报》的文章,包括训练集、验证集和测试集。我们将在PTB训练集上训练词嵌入模型。

载入数据集

数据集训练文件 ptb.train.txt 示例:

aer banknote berlitz calloway centrust cluett fromstein gitano guterman ...
pierre  N years old will join the board as a nonexecutive director nov. N
mr.  is chairman of  n.v. the dutch publishing group
...

with open(’/home/kesci/input/ptb_train1020/ptb.train.txt’, ‘r’) as f:
lines = f.readlines() # 该数据集中句子以换行符为分割
raw_dataset = [st.split() for st in lines] # st是sentence的缩写,单词以空格为分割
print(’# sentences: %d’ % len(raw_dataset))

#对于数据集的前3个句子,打印每个句子的词数和前5个词
#句尾符为 ‘’ ,生僻词全用 ‘’ 表示,数字则被替换成了 ‘N’
for st in raw_dataset[:3]:
print(’# tokens:’, len(st), st[:5])

建立词语索引

counter = collections.Counter([tk for st in raw_dataset for tk in st]) # tk是token的缩写
counter = dict(filter(lambda x: x[1] >= 5, counter.items())) # 只保留在数据集中至少出现5次的词

idx_to_token = [tk for tk, _ in counter.items()]
token_to_idx = {tk: idx for idx, tk in enumerate(idx_to_token)}
dataset = [[token_to_idx[tk] for tk in st if tk in token_to_idx]
for st in raw_dataset] # raw_dataset中的单词在这一步被转换为对应的idx
num_tokens = sum([len(st) for st in dataset])
‘# tokens: %d’ % num_tokens

二次采样

文本数据中一般会出现一些高频词,如英文中的“the”“a”和“in”。通常来说,在一个背景窗口中,一个词(如“chip”)和较低频词(如“microprocessor”)同时出现比和较高频词(如“the”)同时出现对训练词嵌入模型更有益。因此,训练词嵌入模型时可以对词进行二次采样。 具体来说,数据集中每个被索引词 wiw_iwi 将有一定概率被丢弃,该丢弃概率为

$$

P(w_i)=\max(1-\sqrt{\frac{t}{f(w_i)}},0)

$$

其中 f(wi)f(w_i)f(wi) 是数据集中词 wiw_iwi 的个数与总词数之比,常数 ttt 是一个超参数(实验中设为 10−410^{−4}104)。可见,只有当 f(wi)>tf(w_i)>tf(wi)>t 时,我们才有可能在二次采样中丢弃词 wiw_iwi,并且越高频的词被丢弃的概率越大。具体的代码如下:
def discard(idx):
‘’’
@params:
idx: 单词的下标
@return: True/False 表示是否丢弃该单词
‘’’
return random.uniform(0, 1) < 1 - math.sqrt(
1e-4 / counter[idx_to_token[idx]] * num_tokens)

subsampled_dataset = [[tk for tk in st if not discard(tk)] for st in dataset]
print(’# tokens: %d’ % sum([len(st) for st in subsampled_dataset]))

def compare_counts(token):
return ‘# %s: before=%d, after=%d’ % (token, sum(
[st.count(token_to_idx[token]) for st in dataset]), sum(
[st.count(token_to_idx[token]) for st in subsampled_dataset]))

print(compare_counts(‘the’))
print(compare_counts(‘join’))

提取中心词和背景词

def get_centers_and_contexts(dataset, max_window_size):
‘’’
@params:
dataset: 数据集为句子的集合,每个句子则为单词的集合,此时单词已经被转换为相应数字下标
max_window_size: 背景词的词窗大小的最大值
@return:
centers: 中心词的集合
contexts: 背景词窗的集合,与中心词对应,每个背景词窗则为背景词的集合
‘’’
centers, contexts = [], []
for st in dataset:
if len(st) < 2: # 每个句子至少要有2个词才可能组成一对“中心词-背景词”
continue
centers += st
for center_i in range(len(st)):
window_size = random.randint(1, max_window_size) # 随机选取背景词窗大小
indices = list(range(max(0, center_i - window_size),
min(len(st), center_i + 1 + window_size)))
indices.remove(center_i) # 将中心词排除在背景词之外
contexts.append([st[idx] for idx in indices])
return centers, contexts

all_centers, all_contexts = get_centers_and_contexts(subsampled_dataset, 5)

tiny_dataset = [list(range(7)), list(range(7, 10))]
print(‘dataset’, tiny_dataset)
for center, context in zip(*get_centers_and_contexts(tiny_dataset, 2)):
print(‘center’, center, ‘has contexts’, context)
注:数据批量读取的实现需要依赖负采样近似的实现,故放于负采样近似部分进行讲解。

Skip-Gram 跳字模型

在跳字模型中,每个词被表示成两个 ddd 维向量,用来计算条件概率。假设这个词在词典中索引为 iii ,当它为中心词时向量表示为 vi∈Rd\boldsymbol{v}_i\in\mathbb{R}^dviRd,而为背景词时向量表示为 ui∈Rd\boldsymbol{u}_i\in\mathbb{R}^duiRd 。设中心词 wcw_cwc 在词典中索引为 ccc,背景词 wow_owo 在词典中索引为 ooo,我们假设给定中心词生成背景词的条件概率满足下式:
$$

P(w_o\mid w_c)=\frac{\exp(\boldsymbol{u}_o^\top \boldsymbol{v}c)}{\sum{i\in\mathcal{V}}\exp(\boldsymbol{u}_i^\top \boldsymbol{v}_c)}

$$

负采样近似

由于 softmax 运算考虑了背景词可能是词典 V\mathcal{V}V 中的任一词,对于含几十万或上百万词的较大词典,就可能导致计算的开销过大。我们将以 skip-gram 模型为例,介绍负采样 (negative sampling) 的实现来尝试解决这个问题。

负采样方法用以下公式来近似条件概率 P(wo∣wc)=exp⁡(uo⊤vc)∑i∈Vexp⁡(ui⊤vc)P(w_o\mid w_c)=\frac{\exp(\boldsymbol{u}_o^\top \boldsymbol{v}_c)}{\sum_{i\in\mathcal{V}}\exp(\boldsymbol{u}_i^\top \boldsymbol{v}_c)}P(wowc)=iVexp(uivc)exp(uovc)

$$

P(w_o\mid w_c)=P(D=1\mid w_c,w_o)\prod_{k=1,w_k\sim P(w)}^K P(D=0\mid w_c,w_k)

$$

其中 P(D=1∣wc,wo)=σ(uo⊤vc)P(D=1\mid w_c,w_o)=\sigma(\boldsymbol{u}_o^\top\boldsymbol{v}_c)P(D=1wc,wo)=σ(uovc)σ(⋅)\sigma(\cdot)σ() 为 sigmoid 函数。对于一对中心词和背景词,我们从词典中随机采样 KKK 个噪声词(实验中设 K=5K=5K=5)。根据 Word2Vec 论文的建议,噪声词采样概率 P(w)P(w)P(w) 设为 www 词频与总词频之比的 0.750.750.75 次方。

词嵌入进阶

在“Word2Vec的实现”一节中,我们在小规模数据集上训练了一个 Word2Vec 词嵌入模型,并通过词向量的余弦相似度搜索近义词。虽然 Word2Vec 已经能够成功地将离散的单词转换为连续的词向量,并能一定程度上地保存词与词之间的近似关系,但 Word2Vec 模型仍不是完美的,它还可以被进一步地改进:

  1. 子词嵌入(subword embedding):FastText 以固定大小的 n-gram 形式将单词更细致地表示为了子词的集合,而 BPE (byte pair encoding) 算法则能根据语料库的统计信息,自动且动态地生成高频子词的集合;
  2. GloVe 全局向量的词嵌入: 通过等价转换 Word2Vec 模型的条件概率公式,我们可以得到一个全局的损失函数表达,并在此基础上进一步优化模型。

实际中,我们常常在大规模的语料上训练这些词嵌入模型,并将预训练得到的词向量应用到下游的自然语言处理任务中。本节就将以 GloVe 模型为例,演示如何用预训练好的词向量来求近义词和类比词。

GloVe 全局向量的词嵌入

GloVe 模型

先简单回顾以下 Word2Vec 的损失函数(以 Skip-Gram 模型为例,不考虑负采样近似):

$$

-\sum_{t=1}^T\sum_{-m\le j\le m,j\ne 0} \log P(w^{(t+j)}\mid w^{(t)})

$$

其中

$$

P(w_j\mid w_i) = \frac{\exp(\boldsymbol{u}_j\top\boldsymbol{v}_i)}{\sum_{k\in\mathcal{V}}\exp(\boldsymbol{u}_k\top\boldsymbol{v}_i)}

$$

wiw_iwi 为中心词,wjw_jwj 为背景词时 Skip-Gram 模型所假设的条件概率计算公式,我们将其简写为 qijq_{ij}qij

注意到此时我们的损失函数中包含两个求和符号,它们分别枚举了语料库中的每个中心词和其对应的每个背景词。实际上我们还可以采用另一种计数方式,那就是直接枚举每个词分别作为中心词和背景词的情况:

$$

-\sum_{i\in\mathcal{V}}\sum_{j\in\mathcal{V}} x_{ij}\log q_{ij}

$$

其中 xijx_{ij}xij 表示整个数据集中 wjw_jwj 作为 wiw_iwi 的背景词的次数总和。

我们还可以将该式进一步地改写为交叉熵 (cross-entropy) 的形式如下:

$$

-\sum_{i\in\mathcal{V}}x_i\sum_{j\in\mathcal{V}}p_{ij} \log q_{ij}

$$

其中 xix_ixiwiw_iwi 的背景词窗大小总和,pij=xij/xip_{ij}=x_{ij}/x_ipij=xij/xiwjw_jwjwiw_iwi 的背景词窗中所占的比例。

从这里可以看出,我们的词嵌入方法实际上就是想让模型学出 wjw_jwj 有多大概率是 wiw_iwi 的背景词,而真实的标签则是语料库上的统计数据。同时,语料库中的每个词根据 xix_ixi 的不同,在损失函数中所占的比重也不同。

注意到目前为止,我们只是改写了 Skip-Gram 模型损失函数的表面形式,还没有对模型做任何实质上的改动。而在 Word2Vec 之后提出的 GloVe 模型,则是在之前的基础上做出了以下几点改动:

  1. 使用非概率分布的变量 pij′=xijp'_{ij}=x_{ij}pij=xijq′ij=exp⁡(uj⊤vi)q′_{ij}=\exp(\boldsymbol{u}^\top_j\boldsymbol{v}_i)qij=exp(ujvi),并对它们取对数;
  2. 为每个词 wiw_iwi 增加两个标量模型参数:中心词偏差项 bib_ibi 和背景词偏差项 cic_ici,松弛了概率定义中的规范性;
  3. 将每个损失项的权重 xix_ixi 替换成函数 h(xij)h(x_{ij})h(xij),权重函数 h(x)h(x)h(x) 是值域在 [0,1][0,1][0,1] 上的单调递增函数,松弛了中心词重要性与 xix_ixi 线性相关的隐含假设;
  4. 用平方损失函数替代了交叉熵损失函数。

综上,我们获得了 GloVe 模型的损失函数表达式:

$$

\sum_{i\in\mathcal{V}}\sum_{j\in\mathcal{V}} h(x_{ij}) (\boldsymbol{u}^\top_j\boldsymbol{v}i+b_i+c_j-\log x{ij})^2

$$

由于这些非零 xijx_{ij}xij 是预先基于整个数据集计算得到的,包含了数据集的全局统计信息,因此 GloVe 模型的命名取“全局向量”(Global Vectors)之意。

载入预训练的 GloVe 向量

GloVe 官方 提供了多种规格的预训练词向量,语料库分别采用了维基百科、CommonCrawl和推特等,语料库中词语总数也涵盖了从60亿到8,400亿的不同规模,同时还提供了多种词向量维度供下游模型使用。

torchtext.vocab 中已经支持了 GloVe, FastText, CharNGram 等常用的预训练词向量,我们可以通过声明 torchtext.vocab.GloVe 类的实例来加载预训练好的 GloVe 词向量。

import torch
import torchtext.vocab as vocab

print([key for key in vocab.pretrained_aliases.keys() if “glove” in key])
cache_dir = “/home/kesci/input/GloVe6B5429”
glove = vocab.GloVe(name=‘6B’, dim=50, cache=cache_dir)
print(“一共包含%d个词。” % len(glove.stoi))
print(glove.stoi[‘beautiful’], glove.itos[3366])

求近义词和类比词

求近义词

由于词向量空间中的余弦相似性可以衡量词语含义的相似性(为什么?),我们可以通过寻找空间中的 k 近邻,来查询单词的近义词。

def knn(W, x, k):
‘’’
@params:
W: 所有向量的集合
x: 给定向量
k: 查询的数量
@outputs:
topk: 余弦相似性最大k个的下标
[…]: 余弦相似度
‘’’
cos = torch.matmul(W, x.view((-1,))) / (
(torch.sum(W * W, dim=1) + 1e-9).sqrt() * torch.sum(x * x).sqrt())
_, topk = torch.topk(cos, k=k)
topk = topk.cpu().numpy()
return topk, [cos[i].item() for i in topk]

def get_similar_tokens(query_token, k, embed):
‘’’
@params:
query_token: 给定的单词
k: 所需近义词的个数
embed: 预训练词向量
‘’’
topk, cos = knn(embed.vectors,
embed.vectors[embed.stoi[query_token]], k+1)
for i, c in zip(topk[1:], cos[1:]): # 除去输入词
print(‘cosine sim=%.3f: %s’ % (c, (embed.itos[i])))

get_similar_tokens(‘chip’, 3, glove)

get_similar_tokens(‘baby’, 3, glove)

get_similar_tokens(‘beautiful’, 3, glove)

求类比词

除了求近义词以外,我们还可以使用预训练词向量求词与词之间的类比关系,例如“man”之于“woman”相当于“son”之于“daughter”。求类比词问题可以定义为:对于类比关系中的4个词“aaa 之于 bbb 相当于 ccc 之于 ddd”,给定前3个词 a,b,ca,b,ca,b,cddd。求类比词的思路是,搜索与 vec(c)+vec(b)−vec(a)\text{vec}(c)+\text{vec}(b)−\text{vec}(a)vec(c)+vec(b)vec(a) 的结果向量最相似的词向量,其中 vec(w)\text{vec}(w)vec(w)www 的词向量。

def get_analogy(token_a, token_b, token_c, embed):
‘’’
@params:
token_a: 词a
token_b: 词b
token_c: 词c
embed: 预训练词向量
@outputs:
res: 类比词d
‘’’
vecs = [embed.vectors[embed.stoi[t]]
for t in [token_a, token_b, token_c]]
x = vecs[1] - vecs[0] + vecs[2]
topk, cos = knn(embed.vectors, x, 1)
res = embed.itos[topk[0]]
return res

get_analogy(‘man’, ‘woman’, ‘son’, glove)

get_analogy(‘beijing’, ‘china’, ‘tokyo’, glove)

get_analogy(‘bad’, ‘worst’, ‘big’, glove)

get_analogy(‘do’, ‘did’, ‘go’, glove)

参考

  • Dive into Deep Learning. Ch14.1-14.4.
  • 动手学深度学习. Ch10.1-10.3.
  • Dive-into-DL-PyTorch on GitHub

动手学深度学习之词嵌入基础及进阶相关推荐

  1. 动手学深度学习之目标检测基础

    参考伯禹学习平台<动手学深度学习>课程内容内容撰写的学习笔记 原文链接:https://www.boyuai.com/elites/course/cZu18YmweLv10OeV/less ...

  2. 动手学深度学习-12 循环神经网络基础

    循环神经网络基础 循环神经网络 从零开始实现循环神经网络 我们先尝试从零开始实现一个基于字符级循环神经网络的语言模型,这里我们使用周杰伦的歌词作为语料,首先我们读入数据: import torch i ...

  3. 伯禹 动手学深度学习 打卡12之 RNN 进阶

    GRU RNN存在的问题:梯度较容易出现衰减或爆炸(BPTT) ⻔控循环神经⽹络:捕捉时间序列中时间步距离较⼤的依赖关系 RNN: GRU: 重置⻔有助于捕捉时间序列⾥短期的依赖关系: 更新⻔有助于捕 ...

  4. 《动手学深度学习》task7_3 词嵌入进阶

    系统学习<动手学深度学习>点击下面这个链接,有全目录哦~ https://blog.csdn.net/Shine_rise/article/details/104754764 本篇目录 词 ...

  5. 李沐新书中文版上线,零基础也可以《动手学深度学习》| 这不是0.7版

    李沐老师的新书 (又) 上线了. 是<动手学深度学习>的中文预览版. 和7月发布的0.7版相比,几乎每个章节都有肉眼可见的改动. 比如,计算机视觉一章,新加了多尺度目标检测这一小节. 不变 ...

  6. 动手学深度学习(文本预处理+语言模型+循环神经网络基础)

    文本预处理 文本是一类序列数据,一篇文章可以看作是字符或单词的序列,本节将介绍文本数据的常见预处理步骤,预处理通常包括四个步骤: 读入文本 分词 建立字典,将每个词映射到一个唯一的索引(index) ...

  7. 第3章(3.11~3.16节)模型细节/Kaggle实战【深度学习基础】--动手学深度学习【Tensorflow2.0版本】

    项目地址:https://github.com/TrickyGo/Dive-into-DL-TensorFlow2.0 UC 伯克利李沐的<动手学深度学习>开源书一经推出便广受好评.很多开 ...

  8. 动手学深度学习 - 3. 深度学习基础

    动手学深度学习 - 3. 深度学习基础 动手学深度学习 - Dive into Deep Learning Aston Zhang, Zachary C. Lipton, Mu Li, and Ale ...

  9. 资源 | 李沐等人开源中文书《动手学深度学习》预览版上线

    来源:机器之心 本文约2000字,建议阅读10分钟. 本文为大家介绍了一本交互式深度学习书籍. 近日,由 Aston Zhang.李沐等人所著图书<动手学深度学习>放出了在线预览版,以供读 ...

最新文章

  1. 数据中台(二)数据质量分析及提升
  2. python中变量名有哪些_Python变量范围有哪些?如何在Python中定义变量?
  3. [linux] 进程五状态模型
  4. Facebook 开源 Skip,面向对象+函数式编程语言
  5. linux+++乘法口诀_Shell的九九乘法口诀
  6. 数据分析入门:如何训练数据分析思维?
  7. textview 结束后释放_等待按键释放,你的代码如何写?
  8. spring项目获取ServletContext
  9. 《go 语言程序设计》读书笔记(六)Goroutine与系统线程的区别
  10. 傅立叶变换系列(四)离散傅立叶变换
  11. 智能车制作pdf 王盼宝_智能车制作——从元器件、机电系统、控制算法到完整的智能车设计(Word+PDF+ePub+PPT)...
  12. VBA代码宝(列表框)20220402
  13. 安装linux系统的pe软件,Linux和win系统制作U盘pe启动方法
  14. mac 回车键、空格键失灵(非物理原因)解决方法
  15. 【生活】深圳驾考经历
  16. “二十一天好习惯”第一期-20
  17. Springboot-banner图-定制化
  18. 苹果留给 iOS 开发者的时间不多了:30 天内必须更新旧版本!
  19. 2022 IDEA大会引领科技创新趋势 沈向洋团队重磅发布低空经济白皮书
  20. 强化学习-Q-Learning算法

热门文章

  1. 使用tushare社区实现简单的三因子模型
  2. Gradle详细解析***
  3. 仿王者荣耀对战资料选项中的【雷达网图展示详细数据】
  4. 一台计算机怎么创建2桌面上,电脑桌面怎样设置成2个桌面?
  5. 自律的人生,更自由。
  6. 小程序技术在软件供应链安全也能帮得上忙?
  7. Android Study Jam 在线答疑第三期:Jetpack Compose 案例实战
  8. 华为手机能隐藏蓝牙吗_华为手机隐藏的十个功能,华为手机实用小技巧
  9. android ble蓝牙不稳定,Android滞后/不稳定的蓝牙连接
  10. 机械工程学报latex模板_高考报志愿:卓越大学联盟E9,华南理工大学,南方电网摇篮,人工智能挺厉害!...