一样的主体思路
求A相对B的注意力,这里就还以 编码序列解码序列为例 求 编码序列 相对 解码序列 的注意力 编码序列.shape = [N句话batch_size,一句话中的单词个数seq_len,单词维度embedding_dim] 解码序列.shape = [N句话batch_size,一句话中的单词个数seq_len,单词维度embedding_dim] 一样的主体思路: 解码序列与编码序列计算,经softmax出一个得分,该得分再与编码序列算一次 这就是注意力,在NLP中也叫上下文向量
seq to seq 注意力计算方法
解码序列与编码序列计算 实际上解码序列一次计算一个单词,即一次计算序列中的一个位置 step1:位乘取共有信息 sum(解码序列[batch_size,seq_len=1,embedding_dim] * 编码序列[batch_size,seq_len,embedding_dim] ,dim=2) 矩阵运算时会自动扩展,即解码序列的这个单词会与编码序列的每个单词运算, 相当于先将解码序列的一个单词复制seq_len份 两个矩阵按位相乘,就是普通乘法的作用,有点取交集的意思(在值小于1时越乘越小) [batch_size,1,embedding_dim] * [batch_size,seq_len,embedding_dim] = Vec1[batch_size,seq_len,embedding_dim] shape没有变,但embedding_dim这个维度的数值比原来小了很多, 表示的含义是:一句话上相同索引位置的向量 与另外一句话相同位置的向量 进行一个位乘运算, 得到的结果即有自己的信息,又有对方的信息,同时由于都是小于1的数,得到的信息比各自本身都小 最初那个[embedding_dim]表示的是一个单词, 计算后的结果表示两个单词共有的信息,虽然向量的维数(特征数/层)没有变 思考: 接下来为什么不直接将Vec1[batch_size,seq_len,embedding_dim]直接与编码序列[batch_size,seq_len,embedding_dim]计算呢? 换作本人,就直接去算了(并且,有时间的话,等我闲了,我真会去这么算...关键是我不知道什么时候能闲下来做这个-_-) 所以本人只能算个普通人... 这里面有两大问题: 1. 怎么对矩阵的shape,矩阵的计算方法常用的就两个,线性运算,向量内积(矩阵乘法),还有其他计算方法,但不常用,结果合理即可 2. 有何数学依据?拿什么来支撑这个计算?即便妙手偶得之,也不妨碍人们探索一下背后的原理... 就这两个问题就把普通人的我问蒙圈了... 默默低下头,认真看自己的书,做自己的实验 ...
step2: Vec1[batch_size,seq_len,embedding_dim] 一句话上相同索引位置的向量 与另外一句话相同位置的向量 共有的信息, 一个单词对一个单词,解码矩阵是一个单词被复制了seq_len份,编码矩阵是不同的seq_len个单词 解码序列的一个单词对应整个编码序列 这个单词与整个序列进行交融计算,交融计算目前公认的方法并不多: 就是相乘再相加,不同的叫法只是细节上不一样,有的带个平方再开方,有的带上减法等... Vec2 = func(Vec1) Vec3 = func(Vec2, 编码序列[batch_size,seq_len,embedding_dim]) = 期望结果[...] 现有Vec1,求得这么一个中间结果Vec2,Vec2可以与编码序列计算,得到一个期望结果, 期望结果应该长什么样? 首先,批次这个维度必须保留,一个样本一个结果,这点不能变 其次,应该有什么,就得回归,回忆一下最初的目的是什么? 是要计算一个向量相对一个序列的 .?.?. 这里有叫注意力的,有叫上下文向量的,这些都是一些代名词,
最初的困难点
最初的seq to seq,是根据已有的单词序列生成下一个单词,比如 也不用比如了,就拿这句话“最初的seq to seq,是根据已有的单词序列生成下一个单词”来说, 编码序列:最初的seq to seq 解码序列是这样生成的 最初的seq to seq-- 是 最初的seq to seq,是 -- 根据 最初的seq to seq,是根据 -- 已有 最初的seq to seq,是根据已有 -- 的 最初的seq to seq,是根据已有的 -- 单词 最初的seq to seq,是根据已有的单词 -- 序列 最初的seq to seq,是根据已有的单词序列 -- 生成 最初的seq to seq,是根据已有的单词序列生成 -- 下一个 最初的seq to seq,是根据已有的单词序列生成下一个 -- 单词 最初的seq to seq,是根据已有的单词序列生成下一个单词 -- END 差不多 就是就是这么一个过程,中间某个词预测错了,大概率就完了... 提取特征的时候,用的是RNN,RNN的缺点是一个单词一个单词地输入, 这导致最近几个单词的信息成为每个结果的主要信息, 离当前距离远的单词的信息不断丢失, 最好别超过20个单词,20个单词以外的信息严重丢失
这时候,张三就提出了一个问题: 能不能让解码序列的每个单词都带上一些编码序列的信息?这样一定程序上缓解了信息丢失的程度 李四接着说: 关键是要带上相对这个单词重要的信息 王五问: 那怎么求解码序列一个单词相对整个编码序列的重要信息呢? 无数人开始讨论,提出想法,然后验证,这个过程反复反复进行了不知道多少次...
通用建模方法
1. 划定范围,转化为集合 2. 寻找集合之间的关系 如果关系难以寻找,那说明集合划分不精细,或不合理; 重新划分集合 或 将大集合拆分为小集合,然后再次去寻找集合之间的关系 3. 不断重复这个过程
这些具体讨论过程都是很珍贵的信息/资源了,能接触的人都是幸运的,普通人的我不在此列,仅猜测如下: 平常人怎么判断一个事物,一件事,一个人 重要不重要? 如果这世界只有你一个人,也不用判断了,那如果有很多人呢? 首先要定下 谁 对 谁,就是“划定范围”,双方的个体是什么,有多少个,有哪些? 一方是“自己”, 自己接触的人,是另外一方,形成一个集合{人1,人2,...,人n} 人1比人2相对自己来更重要,这实际上是在比差异, 差异化对比的方法就是百分比, 如果一个人对你最重要,比重超过了50%,那么就没有其他人比得过他 集合元素值总和为1,概率的概念呼之欲出...
信息重要度(概率/百分比 )
一个向量各个元素相对另外一个向量的 信息重要度(概率/百分比 )是关键因素 有了这个百分比后,再与编码序列相乘,就得到了最终的期望结果 (初学者的我,有个疑问,这个百分比不就直接表示了一些内容吗,可以直接拿百分比用吗?等验证... ) 甚至这个 期望结果 的 维数都不那么重要,比如得到的结果无法与后面的矩阵进行运算, 一个线性变换 就可以把信息重要度 的shape转换过去,这是细节处理技巧 重要的是 概率/百分比 这个思想 !!! 用概率/百分比 去衡量一个事物相对另外一个事物的重要程序 这个期望结果向量的维数不重要,统一即可(个数的一致比个数是多少更重要), 可以直接拼接到解码单词的向量后面,即个每个解码单词带上相对编码序列的重要信息
后续补充,随着不断学习,现在的我来回答之前的我提出的那个疑问, 层归一化就是这个地方的一种应用,只不过层归一是试图将特征分布拉面标准正态分布
期望结果
期望结果应该长什么样? 首先,批次这个维度必须保留,一个样本一个结果,这点不能变 其次,应该得到一个特征向量,这个向量中有编码序列相对解码序列一个单词的重要信息
逆推得到的中间矩阵:得分矩阵
得分矩阵就是中间矩阵Vec2,它的关键在于: 编码序列的每个单词,相对解码序列的一个单词,有一个公共的信息 从概率/百分比的角度转化一下,就是得分向量 那么得分向量维数就是seq_len, 加上批次维度就是[batch_size,seq_len], 这是相对一个单词的:[解码batch_size,一个解码单词,编码单词个数seq_len]=[batch_size,1,seq_len]
序列注意力概述
单词维度:embedding_dim 句子:seq_len个单词 批次:N句话 编码序列:[batch_size,seq_len,embedding_dim] 解码序列:[batch_size,1,embedding_dim],也按批次计算,但一次计算一个单词, 也可以不是一个单词,这里不是重点,要体会整个过程 注意力计算: [batch_size,1,embedding_dim]*[batch_size,seq_len,embedding_dim],位乘 embedding_dim维度上sum -- [batch_size,seq_len] 做softmax [batch_size,seq_len] 增加一个维度 [batch_size,1,seq_len] 再乘回去 [batch_size,1,seq_len] @ [batch_size,seq_len,embedding_dim] -- [batch_size,1,embedding_dim] 去1个维度 [batch_size,embedding_dim] ,这个向量就是上下文向量,也是一个单词相对编码序列的注意力
多头注意力概述
简化一下,先不说批次的维度, 将单词特征维度拆分成四份,这里 seq_len1==seq_len2,为了 视觉区分 才写成不一样的 编码序列:[seq_len1,embedding_dim] -- [4, seq_len1, embedding_dim/4 ] 解码序列:[seq_len2,embedding_dim] -- [4, seq_len2, embedding_dim/4 ] 每一小份特征与对应的一小份特征计算,相同段位进行计算,不跨段 [4, seq_len2, embedding_dim/4 ] @ [4, embedding_dim/4, seq_len1, ] -- [4, seq_len2, seq_len1] [4, seq_len2, seq_len1] 首先记住,是一个单词四分, 解码序列有seq_len2个单词,每个单词的四分之一相对编码序列的seq_len1个单词的对应的四分之一都有一个数值 [4, seq_len2, seq_len1] 做softmax 再乘回去 [4, seq_len2, seq_len1] @ [4, seq_len1, embedding_dim/4 ] -- [4, seq_len2, embedding_dim/4 ] 维度再变换回来,解码序列的每个单词相对编码序列都有一个上下文向量 [4, seq_len2, embedding_dim/4 ] -- 最终的[seq_len2, embedding_dim] 短接,这一步提供了多层深度计算的可能 最终的[seq_len2, embedding_dim] + 最初的[seq_len2,embedding_dim] 多头注意力与序列注意力shape变换的方式不一样,各有其巧妙之处
短接还是拼接
多头注意力使用的是短接 序列到序列中的注意力使用的是拼接,使用全连接线性变换将维度变换回原来的样子
多头注意力的前期处理
序列到序列没有对特征进行特殊处理 在多头注意力中,在QKV计算之前,对单个元素的特征进行了层归一化处理 结果,所有元素/单词的特征值都被拉到了以0为中心标准差为1的正态分布上了
编码序列.shape = [N句话batch_size,一句话中的单词个数seq_len,单词维度embedding_dim] 解码序列.shape = [N句话batch_size,一句话中的单词个数seq_len,单词维度embedding_dim] 计算的最小单元还是单词的特征之间的计算, 比如,这里有三个维度:[批次,单词个数,单词维度] 序列之间的位乘,按位相乘是 单词维度embedding_dim 与 单词维度embedding_dim 相乘再相加 多头注意力使用了矩阵相乘,这就需要先做一个维度转换 [4, seq_len2, embedding_dim/4 ] @ [4, embedding_dim/4, seq_len1, ] -- [4, seq_len2, seq_len1] 将编码序列矩阵的 序列维度与特征维度交换一下, 这样编码序列的第一个维度是特征维度,与前面解码序列最后一个维度特征维度对应
交融后的结果是什么,即 [4, seq_len2, seq_len1]在此处的业务含义是什么? seq_len2这个维度代表的是解码序列中的每个单词, seq_len1转化为每个单词相对 对应位置上(编码序列与解码序列一对一)的编码序列的 特征 也就是说seq_len1在这里不再表示编码序列中单词的个数了,而是一个单词特征的维数了
位乘 与 矩阵乘法 的区别
以下内容纯属个人猜测,无任何依据,就当看个寂寞... 按位相乘再相加,就是两个维数相同的向量的各个元素相乘再相加, 至于最后加还是不加看需求 矩阵乘法,最小单位是向量内积,实际上也是 两个维数相同的向量的各个元素相乘再相加
区别: 矩阵乘法,如果是同类事物的话,你需要对其中一个矩阵调整一下维度, 因为维数相同才能计算 同类事物 位乘 的话,不需要交换维度 但换不换维度不就是 语法/计算方法的 一个设计吗? 在本人看来,这不算什么区别,或者不是那个深层面的区别
位乘 不会改变事物特征的维数, 各个元素之间相乘,是相同维度的相互作用,不改变事物的整体结构 如果从空间的角度看,是空间内的计算,不管如何计算,其结果还在这个空间维度内 矩阵不一样, 矩阵的计算结果中的每一个值,都是两个向量相乘相加的结果,涉及两双向量所有的特征 (而位乘,不相关的维度之间没有计算,没有融合) 这样一来,矩阵的计算结果就可以突破原有事物的维度, 跳出了原事物的特征空间,同时又有原来事物所有的特征信息, 有点青出蓝却胜于蓝的味道... 所以,矩阵乘法可以用于特征维度变换,这是位乘做不到的, 但若只是提取特征的话,二者都可以