伯禹公益AI《动手学深度学习PyTorch版》Task 07 学习笔记

Task 07:优化算法进阶;word2vec;词嵌入进阶

微信昵称:WarmIce

优化算法进阶

emmmm,讲实话,关于所谓的病态问题是什么,条件数在复杂优化目标函数下的计算,讲者也没说,咱也没法问,只知道要计算一个Hessian矩阵,然后计算得到特征值,但是具体怎么操作实属未知,还得去参看别的材料。总而言之,很多机器学习的优化目标函数是个病态的函数,整体就是个病态问题。其实在《数值分析》里面也有提到过条件数的计算,不过那个是放在“线性代数方程组数值解法”中提及的,如果对应的矩阵条件数过大,那么也构成病态问题,使用迭代求解的方法很难得到优良的近似解。

那么针对这个病态问题呢,两个方向可以解决:

  • Preconditioning gradient vector: applied in Adam, RMSProp, AdaGrad, Adelta, KFC, Natural gradient and other secord-order optimization algorithms. 说白了就是根据Hessian矩阵,对迭代过程中的优化量进行二阶修正。
  • Averaging history gradient: like momentum, which allows larger learning rates to accelerate convergence; applied in Adam, RMSProp, SGD momentum. 这个就更直白了,即根据历史梯度来决定接下来怎么走,也就是说历史梯度为优化提供了一些意见。

后面先介绍了动量算法,也就是利用历史梯度信息进行优化的算法。然后搞了花里胡哨的指数近似函数,害,都是骗人的,就是个加权平均。说好听点,指数移动平均。说难听点,这公式咱们在滤波里面也用过。

后面就介绍了一些花里胡哨,但是用下来都不咋地的优化算法,我就不想提了。

总之,这些算法都是梯度下降算法,也就是说,他们都无可避免地要遇到之前说的梯度下降必然有的问题,虽然能够在一定程度上缓解,但是同时也是在限定情形下的缓解,终究都不是普适的方法,说来说去,还是SGD够硬。例如AdaGrad的梯度消失,然后RMSProp和AdaDelta给AdaGrad擦屁股,Adam在不少算法中有用到,但是一些特定情形下,这个Adam也就只能悻悻然灰溜溜逃走。

word2vec

之前的笔记里,我就有用删除线删除了一些语句,那是因为,当时我不太清楚nn.Embedding()究竟是在干啥的。今天,在这位优秀的讲者(一个声音贼好听的小妹妹,人家还是本科生,上交的)的引领下,我略懂那么一些了。

不过呢,有一说一,讲得字正腔圆是没错,但是很多时候还是在陈述过程,但是我更想知道这么做的目的,或者为什么要这么做,这些我更加关心。

上来就甩出来一些概念,什么背景词、中心词,然后Skip-Gram跳字模型、CBOW (continuous bag-of-words) 连续词袋模型,然后又是条件概率公式,又是本节以Skip-Gram跳字模型实现为主云云,整得人晕晕。

但是我们要赶紧猜测或者说捋一捋这个word2vec究竟要干啥,简单来说,one-hot那种编码方法是离散的,每个词之间都是完全正交的,没有互相之间的关系,但是我们希望表征每个单词的向量能够包含语义上的信息,尤其是在语义相似度这一点上。所以,我们要如何获得这样的向量表达呢?

首先,整理数据集,建立词典(或者说是词语索引)。

接下来这一步就很重要了,二次采样,当当当当。

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

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

,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,并且越高频的词被丢弃的概率越大。

这一段说的很清楚了,其实这个视觉SLAM里面也有用到,不过那个里面就是把单词换成了图像特征而已,不多比比了。

再接下来,就是提取中心词和背景词。除了这两个概念,还有个视窗的概念。其实吧,都说到这儿了,这个意思已经很明确了,就是说我在一个足够长的句子里面(起码要有2个单词),从左到右,每个单词都有机会作为中心词,然后根据所开的视窗大小,中心词左边和右边的视窗大小个的单词就都是属于这个中心词的背景词。说白了,这个中心词此时就和这几个背景词挂上钩了,因为它们一起出现了

提取中心词和背景词的函数get_centers_and_contexts返回的是一个元组,第一个元素是中心词单层列表,第二个元素是一个双层列表,依序为对应中心词的背景词,视窗大小随机。

这里还顺带重新提到了torch自带的Embedding层,其实这个层乍一看有点懵逼,其实很好理解,初始化时候的num_embeddings是指给多少个字符进行词嵌入,embedding_dim是指最后形成的词向量的维度。因此,我们输入这个Embedding层的tensor其实是指定了idx,而这个idx必须在range(num_embeddings)范围内,否则就没有与之对应的词向量。

刚刚说这一节主要就是要将跳字模型。

在跳字模型中,每个词被表示成两个 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(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)

可以看到,式子中“中心词”是不变的,而在中心词确定的情况下,所有与之一起出现过的背景词,每个背景词就都有了一个softmax概率,exp函数计算的值隐式地表达了中心词和每个背景词的相似度。但是各位想一想,一个中心词可能有很多很多单词都是其背景词,一旦如此,这个计算开销可是顶不住。这该怎么好呢?还有一个问题,我不能只关注中心词和谁接近啊,我还得知道中心词和谁疏远,这个该咋整呢?

对于第一个问题,我们可以用数学的方法解决,对于第二个问题,我们引入噪声词。至于为啥有第二个问题,你想想啊,你是个刚刚生下来的孩子,我只告诉你什么样的好的,不告诉你什么是坏的,你就会倾向于给所有人发好人卡,因为只要做出是好的这个判断,在之前的训练过程中都有很低的损失。这也就是负样本的作用。

所以,解决上面这个问题的整个方法就叫做负采样近似

由于 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(wo∣wc)=P(D=1∣wc,wo)∏k=1,wk∼P(w)KP(D=0∣wc,wk)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(wowc)=P(D=1wc,wo)k=1,wkP(w)KP(D=0wc,wk)

其中 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 次方。

给出的代码里面的get_negatives函数实现的还是比较巧妙的,通过random.choices函数,其中的相对权重可以事先计算好传进去,最后循环,就得到了与中心词顺序对应的噪声词。

**这样一来,我们就有了中心词、背景词与噪声词了,且他们的次序是对应好的。**然后就可以用torch.utils.data.Dataset来getitem了。

但是还是不够,因为我们训练的时候是要按照batch这样的批量送进去的,每个getitem得到的数据,其背景词和噪声词的长度很大可能是不一样的,此时就需要对批量拉取出来的数据做进一步处理,箭头指向batchify函数。然后在Data.DataLoader进行批量化拉取数据时指定collect_fn为batchify,就会很美好,pytorch会在拉取好原始数据后自动调用collect_fn对数据进行处理。经处理后得到 (centers, contexts_negatives, masks, labels) 元组:

centers: 中心词下标,形状为 (n, 1) 的整数张量
contexts_negatives: 背景词和噪声词的下标,形状为 (n, m) 的整数张量
masks: 与补齐相对应的掩码,形状为 (n, m) 的0/1整数张量
labels: 指示中心词的标签,形状为 (n, m) 的0/1整数张量

最后就是训练模型了,但是还有一步咱们没做,损失函数是什么?

应用负采样方法后,我们可利用最大似然估计的对数等价形式将损失函数定义为如下

∑t=1T∑−m≤j≤m,j≠0[−log⁡P(D=1∣w(t),w(t+j))−∑k=1,wk∼P(w)Klog⁡P(D=0∣w(t),wk)]\sum_{t=1}^T\sum_{-m\le j\le m,j\ne 0} [-\log P(D=1\mid w^{(t)},w^{(t+j)})-\sum_{k=1,w_k\sim P(w)^K}\log P(D=0\mid w^{(t)},w_k)] t=1Tmjm,j=0[logP(D=1w(t),w(t+j))k=1,wkP(w)KlogP(D=0w(t),wk)]

根据这个损失函数的定义,我们可以直接使用二元交叉熵损失函数进行计算。

我理一下,说白了,中心词会生成一个对应的词向量,背景词和噪声词也会生成各自对应的词向量,我们的目标就是让中心词生成的词向量与背景词生成的词向量尽可能接近,同时要与噪声词生成的词向量尽可能疏远

使用余弦相似度获取相似语义的单词(近义词)时,要注意取topk时,跟自己肯定是最相近的,所以要取k+1个,然后去掉自己看看近义词是哪些。

词嵌入进阶

回顾刚才的word2vec,其实你可以发现,我们是回避了上面提到的全局运算量过大的问题,转而利用一个近似函数作为目标函数进行优化。那么这一节的Glove全局向量的词嵌入模型就要和这个问题正面刚,是男人就跟他刚,看谁够硬。

GloVe通过等价转换 Word2Vec 模型的条件概率公式,得到一个全局的损失函数表达,并在此基础上进一步优化模型。

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

哦哦哦,我突然有个想法,就是说,上面的负采样,其实是一种对于不能进行全局采样的弥补措施,对不对!!!

想想看,要是我知道了全局的信息,我还考虑个屁的负样本啊,因为正样本都被我考虑进来啦,剩下来的不就都是负样本吗,这个时候上面那种偏差认知就不存在啦!!!

介绍这个GloVe模型的优化函数的变形那边我尚且还可以看得懂,贴一下内容:

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

−∑t=1T∑−m≤j≤m,j≠0log⁡P(w(t+j)∣w(t))-\sum_{t=1}^T\sum_{-m\le j\le m,j\ne 0} \log P(w^{(t+j)}\mid w^{(t)}) t=1Tmjm,j=0logP(w(t+j)w(t))

其中

P(wj∣wi)=exp⁡(uj⊤vi)∑k∈Vexp⁡(uk⊤vi)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)} P(wjwi)=kVexp(ukvi)exp(ujvi)

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

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

−∑i∈V∑j∈Vxijlog⁡qij-\sum_{i\in\mathcal{V}}\sum_{j\in\mathcal{V}} x_{ij}\log q_{ij} iVjVxijlogqij

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

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

−∑i∈Vxi∑j∈Vpijlog⁡qij-\sum_{i\in\mathcal{V}}x_i\sum_{j\in\mathcal{V}}p_{ij} \log q_{ij} iVxijVpijlogqij

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

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

但是后面那个改进的4点,为什么那么做,讲者是只字未提,不过是念了一遍。。。。。。我特么咋知道为什么这么做,我怀疑她也不知道。。。。贴一下内容吧:

而在 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 模型的损失函数表达式:

∑i∈V∑j∈Vh(xij)(uj⊤vi+bi+cj−log⁡xij)2\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 iVjVh(xij)(ujvi+bi+cjlogxij)2

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

那么实际上呢,这个训练咱们也没得办法进行,那么大的语料库。所以我们就只能用人家放出来给咱们用的,即载入与训练的GloVe向量,然后在此基础上做一些应用。怎么载入呢?

import torch
import torchtext.vocab as vocabprint([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])

能做些什么应用呢,这里官方给了两个例子,求近义词和类比词,近义词我就不想说了,跟上面的一毛一样。

啥叫类比词呢?

除了求近义词以外,我们还可以使用预训练词向量求词与词之间的类比关系,例如“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 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)def get_analogy(token_a, token_b, token_c, embed):'''@params:token_a: 词atoken_b: 词btoken_c: 词cembed: 预训练词向量@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 resget_analogy('man', 'woman', 'son', glove)

以上。

伯禹公益AI《动手学深度学习PyTorch版》Task 07 学习笔记相关推荐

  1. 伯禹公益AI《动手学深度学习PyTorch版》Task 03 学习笔记

    伯禹公益AI<动手学深度学习PyTorch版>Task 03 学习笔记 Task 03:过拟合.欠拟合及其解决方案:梯度消失.梯度爆炸:循环神经网络进阶 微信昵称:WarmIce 过拟合. ...

  2. 伯禹公益AI《动手学深度学习PyTorch版》Task 05 学习笔记

    伯禹公益AI<动手学深度学习PyTorch版>Task 05 学习笔记 Task 05:卷积神经网络基础:LeNet:卷积神经网络进阶 微信昵称:WarmIce 昨天打了一天的<大革 ...

  3. 伯禹公益AI《动手学深度学习PyTorch版》Task 06 学习笔记

    伯禹公益AI<动手学深度学习PyTorch版>Task 06 学习笔记 Task 06:批量归一化和残差网络:凸优化:梯度下降 微信昵称:WarmIce 批量归一化和残差网络 BN和Res ...

  4. 伯禹公益AI《动手学深度学习PyTorch版》Task 04 学习笔记

    伯禹公益AI<动手学深度学习PyTorch版>Task 04 学习笔记 Task 04:机器翻译及相关技术:注意力机制与Seq2seq模型:Transformer 微信昵称:WarmIce ...

  5. 伯禹-公益AI学习打卡 Task02

    1.文本预处理 记录一哈文本数据的常见预处理步骤: (1)读入文本 (2)分词 (3)建立字典,将每个词映射到一个唯一的索引(index) (4)将文本从词的序列转换为索引的序列,方便输入模型 2.基 ...

  6. 过拟合欠拟合模拟 || 深度学习 || Pytorch || 动手学深度学习11 || 跟李沐学AI

    昔我往矣,杨柳依依.今我来思,雨雪霏霏. ---<采薇> 本文是对于跟李沐学AI--动手学深度学习第11节:模型选择 + 过拟合和欠拟合的代码实现.主要是通过使用线性回归模型在自己生成的数 ...

  7. 动手学深度学习在线课程-跟着李沐学AI

    动手学深度学习在线课程-跟着李沐学AI http://courses.d2l.ai/zh-v2/ 李宏毅<机器学习>中文课程(2022) https://hub.baai.ac.cn/vi ...

  8. 动手学深度学习: 图像分类案例2,GAN,DCGAN

    动手学深度学习: 图像分类案例2,GAN,DCGAN 内容摘自伯禹人工智能AI公益课程 图像分类案例2 1.关于整理数据集后得到的train.valid.train_valid和test数据集: 1) ...

  9. 动手学深度学习Pytorch Task07

    本节课内容目标检测基础.图像风格迁移.图像分类案例1 一.目标检测基础 锚框 目标检测算法通常会在输入图像中采样大量的区域,然后判断这些区域中是否包含我们感兴趣的目标,并调整区域边缘从而更准确地预测目 ...

最新文章

  1. linux卸载hadoop版本,centos6.5 安装hadoop1.2.1的教程详解【亲测版】
  2. R语言dplyr包if_else条件判断选择函数实战
  3. 【Linux部署】NTP时间服务器搭建及Linux+Windows客户端使用(一篇学会使用NTP服务)
  4. oc基础-self关键字的使用
  5. python归并排序 分词_python实现归并排序,归并排序的详细分析
  6. UE4 异步资源加载
  7. html数字自动滚动代码怎么写,你可能需要这样的大屏数字滚动效果
  8. string与wstring互转
  9. ssl协议,openssl,创建私有CA
  10. 英文教材《FPGA-Prototyping-By-Verilog-Examples》下载
  11. tcpdump如何判断丢包_亿级规模的高可用微服务系统,如何轻松设计?
  12. flash 林度_flash怎么制作呢 ?
  13. dhcp select global与interface配置过程
  14. oracle user_source表
  15. vue-3d-model vue 实现3D 图像显示
  16. 一位外包女程序员的心酸史和无奈
  17. HTML 表格与表单 个人简历
  18. 在ssd上win10和linux双系统,windows 10 ssd ubuntu hdd双系统
  19. EF 正在运行转换: System.Reflection.TargetInvocationException: 调用的目标发生了异常。
  20. linux输入法源,kali linux更新源问题 加 输入法安装(示例代码)

热门文章

  1. linux+显卡驱动下载官网下载地址,下载:NVIDIA显卡Linux驱动256.44正式版
  2. CCW:浪潮云+智能协同云平台获评“用户首选品牌”
  3. RMAN备份归档日志时的not backed up与catalog数据库结合时的问题
  4. 深拷⻉浅拷⻉的区别?什么是深拷⻉浅拷⻉
  5. 错误:ssh_exchange_identification: read: connection reset by peer
  6. linux段错误core dumped,段错误 (core dumped) 之 core文件
  7. 邮储银行计算机岗位笔试题,中国邮政储蓄银行各类岗位笔试经验汇总
  8. Monkeys [POI 2003,Bzoj 2610]
  9. HTML5之 Microdata微数据
  10. CAD在画线的过程中显示长度和角度