Jiahong 的个人博客

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

CA——Google-Ad-Click-Prediction

  • 参考文献:Google KDD 2013,Ad Click Prediction: a View from the Trenches(引用量500+)
  • 介绍了截止到2013年之前的点击率预估常用算法,FTRL是Google三年的结晶(2010-2013)
  • 在线学习优化算法的发展历程:SGD->TG->FOBOS->RDA->FTRL-Proximal

核心贡献

  • 基于传统的逻辑回归算法(Regularized Logistic Regression,正则化的逻辑回归)在点击率预估时的不足,提出一种在线逻辑回归算法,FTRL(Follow The Regularized Leader)
  • per-coordinate learning rate

一些模型的比较和介绍

  • 传统逻辑回归算法中使用OGD(Online Gradient Descent)是非常高效的,使用很小的计算资源就能得到较好的精确度.
  • 但是OGD在生成稀疏模型方面表现不好(OGD + L1)
  • 其他在稀疏性方面表现良好的方法有FOBOS, 截断梯度(Truncated Gradient)和 RDA(Regularized Dual Averaging)
    • RDA在精确度和稀疏性方面做tradeoff, 效果好于FOBOS
  • FTRL-Proximal号称可以同时获得RDA的稀疏性和OGD的精确度
  • RDA模型是微软提出的一种在线优化算法,与OGD完全不同,能得到更加稀疏的模型,但是精确度不如OGD

FTRL-Proximal Learning (Online Learning And Sparsity)

  • 也是通过L1正则化控制模型的稀疏度

推导过程

FTL(Follow The Leader)的介绍
OGD的更新方式
  • 更新规则:
    $$
    \begin{align}
    \mathbf{w}_{t+1} &= \mathbf{w}_t-\eta_t\mathbf{g}_t
    \end{align}
    $$
FTLR(Follow The Regularized Leader)

加上正则项的FTL

  • 更新规则:
    $$
    \begin{align}
    \mathbf{w}_{t+1} &= \mathop{\arg\min}_{\mathbf{w}}\left ( \mathbf{g}_{1:t}\cdot \mathbf{w} + \frac{1}{2}\sum_{s=1}^t \sigma_s || \mathbf{w} - \mathbf{w}_s||_2^2 + \lambda_1||\mathbf{w}||_1 \right ) \\
    \mathbf{g}_{1:t} &= \sum_{i=1}^t\mathbf{g}_i
    \end{align}
    $$
    • 第一项是对损失函数梯度的贡献的一个估计
    • 第二项是控制参数 \(\mathbf{w}\) 在每次迭代中变化不要太大
    • 第三项是L1正则化,用于使模型变得稀疏(除了L1正则化项以外,也可以再加上L2正则化)
    • 去掉正则化项就是FTL(Follow The Leader)
    • \(\sigma_s\) 是学习速率
    • 这个学习速率可以用Per-Coordinate Learning Rate:
      $$
      \begin{align}
      \eta_{t, i} &= \frac{\alpha}{\beta + \sqrt{\sum_{s=1}^t g_{s,i}^2}} \\
      \mathbf{g}_s &= \nabla l_s(\mathbf{w})
      \end{align}
      $$

Per-Coordinate Learning Rates

  • 对参数的每一维度分开训练,每个维度有自己的学习率
  • 某个特征出现的次数越多,说明当前该特征对应的参数值越可信,学习率就应该越小
  • 考虑了数据在每个特征上的分布不均匀性
    • 参数某个维度上的样本数越少,这些样本就会得到越大的利用(具体表现就是该特征的学习率会比较大)

一个思考

  • 问题:为什么机器学习中的学习率都是越来越小?
  • 答案:因为刚开始训练时,参数的值不太可信(也就是说最终参数与当前参数的置信度比较低),所以更新时应该更新的步骤大一些,让当前的参数变化大一些,训练到后来,随着参数的值越来越可信(当前参数的置信度比较高),更新的步骤就应该小一些,让当前的变化小一些

一些工程上的Trick

Saving Memory at Massive Scale

Probabilistic Feature Inclusion
  • 在高维数据中,大量的特征是出现频率非常低的(rare),半数的唯一特征甚至只出现一次
  • 统计这些特征的代价是非常昂贵的,有些特征可能永远不会被实际使用(这里如何理解昂贵?也就是说训练了也没用?)
  • 额外的读写数据是昂贵的,如果能丢弃一部分出现评论特别低的特征(比如出现频率低于k次)
  • 实现稀疏可以使用L1正则化,也可以使用Probabilistic Feature Inclusion
  • 关于Probabilistic Feature Inclusion的做法
    • 当一个特征第一次出现时,以一定的概率接受这个新特征
    • 效果作用于数据预处理阶段,但是可以在在线执行中设置
Possion Inclusion
  • 对于新的特征,以概率p添加入模型
  • 对于已经存在模型中的特征,正常更新其系数
Bloom Filter Inclusion
  • 用布隆过滤器仅仅保留出现次数在n次以上的特征
Encoding Values with Fewer Bits
  • 常用的OGD使用32或者64位浮点数编码来存储模型的系数
  • Google提出可以使用16位浮点数来存储系数,同时加上一些策略
  • 实验结果:将64位的浮点值换为为系数存储节省了75%的RAM内存空间(这还用实验?直接计算就得到了啊)
Training Many Similar Models
  • 同时训练多个模型是超参数选择常用的方法
  • 将可以共享的东西共享
  • 在节省内存的同时,还可以节约网络带宽(存储一份Per-coordinate学习率,节省内存和带宽等),CPU(用同一个hash表检索特征,节省CPU)和硬盘空间
A Single Value Structure
  • 有时候我们希望评估大量的模型在只有少量的特征组(groups of features)添加或者删除时的变化
  • 对于每一维度(coordinate),仅仅存储一个系数值而不是多个(正常应该为每个模型存储一个)
  • 存储该维度对应特征组的模型共享该系数
  • 对每个特征,训练时每个模型都会计算自己的值,然后所有模型的取平均作为所有包含该维度特征模型的共享
Computing Learning Rates with Counts
  • 对于每个特征来说,梯度和以及梯度平方和是必须计算的
  • 梯度的计算必须准确,但是计算学习率却是可以粗糙计算的
  • 仅仅统计样本出现次数(Counts)就能大概计算学习率
推导
  • 假设包含一个给定特征的所有事件(events)具有有相同的概率(一般来说,这个近似是不可能的,但是在这个目标里面是可行的)
    • 如何理解这个假设的意义呢?*对于具有某个特征的所有样本,其取值为(正负例)是的概率是相等的,正例(click)概率都为 \(p\) *
  • 进一步假设模型已经精确地学习到了概率
  • 如果有分别有 \(P,N\) 个正负样本(events),则有 \(p=\frac{P}{N+P}\)
  • 则有对于逻辑回归(\(p(1-p)\))来说,正例的梯度为 \(p-1\),负例的梯度为 \(p\)
    $$
    \begin{align}
    \sum g_{t,i}^2 &= \sum_\text{positive events}(1-p_t)^2 + \sum_\text{negative events}p_t^2 \\
    &\approx P(1-\frac{P}{N+P})^2 + N(\frac{P}{N+P})^2 \\
    &= \frac{PN}{N+P}
    \end{align}
    $$
  • 也就是说,为了近似计算 \(\sum g_{t,i}^2\),我们仅需要记录 \(P,N\) 即可
Subsampling Traning Data
  • 典型的CTRs是远远低于50%,数据偏差很大,正例(点击)的样本很稀疏
  • 在模型训练中正例样本相对而言更有价值
  • 使用下采样:很大程度上降低数据量,同时保证对精度最小程度的影响
采样方法:
  • 保留所有至少被点击过一次的请求(Query,也就是样本)
  • 以一定比例 \(r\in(0, 1]\) 采样没有被点击过的请求
  • 因为包含通用的特征(Feature Phase)计算,所以这种方法是合理的,但是需要纠偏(直接用采样后的样本训练会造成预测偏差)
  • 加入一个重要性权重 \(w_t\)
    • \(w_t = 1\) for clicked queries
    • \(w_t = \frac{1}{r}\) for queries with no clicks
    • 本质上可以理解为这里是将采样时造成的负例比例偏差补齐

模型性能评估

Progressive Validation

  • Progressive验证又称为在线损失(online loss)
  • 与交叉验证(cross-validation)或留出法(hold out)验证是不同的
  • 在服务查询中,在线损失能很好的代表我们的精度,因为在训练模型前,它仅仅在最新数据上评估其性能(因为这准确的模拟了当模型进行服务查询时发生了什么)
  • 由于用了100%的数据作为训练和测试,在线损失比留出法验证有更好的统计数据
  • 绝对指标往往会带来误导
    • 即使预测是完美的,对数损失和其他指标的差异也依赖着问题的困难程度
    • 不同的国家,不同请求的点击率不同(同为对数损失的最佳实践,50%的点击率会好于2%的点击率)
  • 所以使用相对变化,看指标相对于base line改变了多少
  • 从经验来看,相对指标在整个时间段内都很稳定
  • 相同的指标计算应该对应在完全相同的数据(比如不同时段的损失比较是没有意义的)

置信度评估(Confidence Estimates)

  • 对很多应用来说,除了评估广告的CTR,还要量化预测的期望精确度(the expected accuracy of the prediction)

校正预测(Calibrating Predictions)

  • 系统偏差(Systematic Bias)指平均预测CTR(Average Predicted CTR)和观测CTR(Observed CTR)的差异
  • 造成系统偏差的原因包括:
    • 不精确的模型假设
    • 学习算法的缺陷
    • 在训练或者服务(预测)时不可用的隐藏特征
  • 解决方案:
    • 添加一个校正层将预测CTRs与观测CTR做匹配(match predicted CTRSs to observed click-through rates)
    • 暂时不能提供有效的保证,保证校正影响的有效
    • 校正的本质是找到(拟合)一个函数映射 \(g\),使得模型输出值与真实概率值一一对应
    • 逻辑回归中的sigmoid函数可以看做是一个校正预测的函数吗?
  • 更多参考
    • 风险模型 - 概率校准
    • 机器学习:概率校准, 文中有代码示例
    • 概率校准 Probability Calibration

一些说明

参考博客:https://blog.csdn.net/fzcoolbaby/article/details/99174601?utm_source=distribute.pc_relevant.none-task

  • 概率模型的搭建过程中,由于抽样与正则化等原因,导致模型的输出概率值明显偏离真实概率值
    • [待更新:为什么抽样和正则化会影响模型的输出概率发生变化?]
  • 此时的模型输出概率值仅仅有排序的意义,其绝对值没有意义(定序值,而非定距数值)
  • 校正预测的过程就是把模型输出概率值的校正成真实的概率的过程,使得校正后的概率有绝对值意义

自动特征管理(Automated Feature Management)

  • 将特征空间描绘成上下文和语义信号,每个信号都可以被翻译成一个在学习时有真实值的特征集合
  • [待更新]

一些不成功的实验记录(Unsuccessful Experiments)

本节记录google的一些失败的尝试方向(有些可能会让人很惊讶),这些方向模型没有明显收益

Aggressive Feature Hashing

关于特征哈希(Feature Hashing)的相关知识可参考Feature-Hashing

  • Feature Hashing for Large Scale Multitask Learning论文指出,Feature Hashing方法效果显著
    • 报告显示使用特征hashing技巧,可以能将学习一个垃圾邮件过滤模型的特征空间映射成包含 \(2^24\) 个特征的特征空间(疑问:这里的特征原来不止 \(2^24\) 个吗?)
  • 但是实验证明,使用 Feature Hashing 方法并不能提升我们的方法,所以建议保留 interpretable(non-hashed)的特征向量

Dropout

  • Google用网格搜索的方法测试了从0.1到0.5的Dropout特征丢弃概率
  • 所有情况均没有带来任何收益(包括精度指标和泛化能力),还往往给模型带来损害(detriment)
  • Google给出的一个解释是:Dropout在图像领域取得较好收益的原因是因为图像领域的数据特征分布与计算广告领域不同
    • 图像领域:稠密特征,此时Dropout把结果(effect)从强相关的特征中分离开来,从而得到泛化效果更好的分类器
    • 计算广告:稀疏特征,且有噪音

Feature Bagging

  • 对特征进行overlapping采样(注意,样本Bagging和特征Bagging不同),然后训练多个独立的模型,最后取平均值
  • 实验证明模型的的AucLoss降低了0.1%-0.6%

Feature Vector Normalization

  • \(\mathbf{x} = \frac{\mathbf{x}}{||\mathbf{x}||}\)
  • 开始有一点精度上的收益,但是后面也出现了一定程度的detriment
  • Google的解释是可能是由于per-coordinate learning rates和正则化的影响

Hexo——Next主题搜索窗口无法弹出

  • 参考博客: https://www.sqlsec.com/2017/12/hexosearch.html

问题描述

  • 有时候会遇到在Mac上Next主题窗口无法弹出的问题
  • 问题分为两类
    • 一类为找不到 search.xml 文件:加载 search.xml 时错误为404
    • 另一类为文章中有特殊字符:加载 search.xml 检查时错误为304

解决方案

404类

  • 修改最外层配置文件./_config.yml,添加以下语句(实测这一步非必须)

    1
    2
    3
    4
    5
    search:
    path: search.xml
    field: post
    format: html
    limit: 10000
  • 安装搜索插件

    1
    npm install hexo-generator-searchdb --save

304类

  • 304状态说明是加载文件存在,但是无法正常解析文件
  • 直接用浏览器访问 search.xml 文件链接,然后查看文件解析异常出现在哪个地方,然后删除特殊字符即可
    • 个人理解,从哪个文件不能搜索,特殊字符就出现在哪个文件中
    • 说明:目前还没遇到过这种情况,后面遇到会再补充

NLP——困惑度-Perplexity-PPL

本文主要介绍困惑度(Perplexity,简称为 PPL)在语言模型评估中的作用


困惑度的整体说明

  • 困惑度(Perplexity,PPL)本质是交叉熵损失 \(H(p|q)\) 的一个等价指标
    $$\text{Perplexity} = e^{H(p|q)}$$

    • 其中 \(H(p|q)\) 衡量真实标签和模型输出概率之间的差异
  • 语言模型中损失函数即交叉熵损失,此时有 \(loss = H(p|q)\),即:
    $$\text{Perplexity} = e^{\text{loss}}$$

    • 特别是预训练和 SFT 阶段,一般符合这个情况,RL 阶段则根据不同的损失归一化方式有所不同
    • 注:一般语言模型中是先计算单个样本的困惑度(对应指数上是单个样本的 Loss),然后再平均后上报
    • 参考链接:How to calculate perplexity for a language model using Pytorch, StackOverflow
      1
      perplexity = torch.exp(loss)
  • 给测试集的句子赋予较高概率值的语言模型较好,在测试集上困惑度越小,模型越好

  • 当一个语言模型训练完成后,测试集中的句子(一般是正常的自然语言句子)出现概率越高越好,概率越大,困惑度越小

  • 在当前 LLM 的强化学习场景中,因为样本 Response 是从当前策略采样得到的,此时直接计算无偏估计即可得到熵的近似:

    • 注:原本计算单个 Token 的熵是需要遍历所有词表的:
      $$ H_t(\pi) = - \sum_{v \in \mathcal{V}}\log \pi(v|x,y_{< t}) $$
    • 由于 \(y_t\) 是从当前策略 \(\pi\) 采样得到的,于是这里可以使用单个 Token 的熵的无偏估计
      $$ H_t^{\text{sampled}}(\pi) = - \log \pi(y_t|x,y_{< t}) $$
      • 可以看到这个值就是交叉熵损失函数的定义(当然,RL 中没有交叉熵损失,这里只是说形式一样)
    • 由于 PPL 此时等于:
      $$ \text{Perplexity} = e^{H(p|q)} = e^(H_t^{\text{sampled}}(\pi)) $$
    • 故而进一步有,这里的 \(H_t^{\text{sampled}}(\pi)\) 和 PPL 的对数一致,即:
      $$ \log(\text{Perplexity}) = H(p|q) = H_t^{\text{sampled}}(\pi) $$
      • 在 RL 训练时的上报动态中可以看到,在使用 \(H_t^{\text{sampled}}(\pi)\) 作为熵的近似估计时,\(\log(\text{Perplexity})\) 和 \(H_t^{\text{sampled}}(\pi)\) 的上报值完全相等

单文档困惑度的定义

  • 给定模型 \(\mathcal{M}\),测试文档 \(d = (w_{1}, w_{2},\cdots,w_{N})\),则模型 \(\mathcal{M}\) 下评估测试文档 \(d\) 的困惑度 \(\text{Perplexity}_\mathcal{M}(d)\) 原始定义为:
    $$
    \begin{align}
    \text{Perplexity}_\mathcal{M}(d) &= P_\mathcal{M}(d)^{-\frac{1}{N}} \\
    &= P_\mathcal{M}(w_{1}, w_{2},\cdots,w_{N})^{-\frac{1}{N}}
    \end{align}
    $$
  • 常常会使用以下形式:
    $$ \text{Perplexity}_\mathcal{M}(d) = \exp\left (-\frac{1}{N}\log P_\mathcal{M}(w_{1}, w_{2},\cdots,w_{N})\right ) $$
  • 进一步有:
    $$
    \begin{align}
    \text{Perplexity}_\mathcal{M}(d) &= \exp\left (-\frac{1}{N}\log P_\mathcal{M}(w_{1}, w_{2},\cdots,w_{N})\right ) \\
    &= \exp\left (-\frac{1}{N}\log\prod_{n=1}^{N}P_\mathcal{M}(w_{n}|w_1,\cdots,w_{n-1})\right ) \\
    &= \exp\left (-\frac{1}{N}\sum_{n=1}^{N}\log P_\mathcal{M}(w_{n}|w_1,\cdots,w_{n-1})\right ) \\
    \end{align}
    $$
    • 计算时,常常使用上面加法的形式
  • 以上多种形式等价,两边取对数在变成自然数的指数即可证明:
    • 取对数:
      $$
      \begin{align}
      Log(\text{Perplexity}_\mathcal{M}(d) ) &= -\frac{1}{N}\log P_\mathcal{M}(w_{1}, w_{2},\cdots,w_{N}) \\
      \end{align}
      $$
    • 变指数:
      $$
      \begin{align}
      \text{Perplexity}_\mathcal{M}(d) &= e^{-\frac{1}{N}\log P_\mathcal{M}(w_{1}, w_{2},\cdots,w_{N})} \\
      &= \exp\left (-\frac{1}{N}\log P_\mathcal{M}(w_{1}, w_{2},\cdots,w_{N})\right ) \\
      \end{align}
      $$
    • 特别说明,一些地方会将对数和指数的底同时换成 2,实际上从推导上看,可以换成任意底数

多文档困惑度的定义

  • 基本思路:将多文档拼接起来,视作单个文档,然后计算困惑度即可(但需要考虑文档间词概率相互独立)
  • 以上是一个文档的表述,对于多个文档 \(D = (d_{1}, d_{2},\cdots,d_{M})\) ,其中 \(d_m = (w_{1}^{m}, w_{2}^{m},\cdots,w_{N_{m}})\),则多文档的困惑度定义为:
    $$
    \begin{align}
    \text{Perplexity}_\mathcal{M}(D) &= P_\mathcal{M}(D)^{-\frac{1}{\sum_{m=1}^{M}N_{m}}} \\
    &= \left(\prod_{m=1}^{M} P_\mathcal{M}(d_{m})\right)^{-\frac{1}{\sum_{m=1}^{M}N_{m}}}
    \end{align}
    $$
    • 文档之间互相独立,所以能推出上面的结论
  • 也常常写作如下形式
    $$
    \begin{align}
    \text{Perplexity}_\mathcal{M}(D) &= \exp\left(-\frac{1}{\sum_{m=1}^{M}N_{m}}\sum_{m=1}^{M}\log P_\mathcal{M}(w_{1}^{m}, w_{2}^{m},\cdots,w_{N_{m}}^{m})\right) \\
    \end{align}
    $$
  • 进一步可得
    $$
    \begin{align}
    \text{Perplexity}_\mathcal{M}(D) &= \exp \left ( -\frac{1}{\sum_{m=1}^{M}N_{m}}\sum_{m=1}^{M}\log P_\mathcal{M}(w_{1}^{m}, w_{2}^{m},\cdots,w_{N_{m}}^{m}) \right ) \\
    &= \exp \left ( -\frac{1}{\sum_{m=1}^{M}N_{m}}\sum_{m=1}^{M}\log\prod_{n=1}^{N_{m}}P_\mathcal{M}(w_{n}^{m}|w_{1}^{m},\cdots,w_{n-1}^{m}) \right ) \\
    &= \exp \left ( -\frac{1}{\sum_{m=1}^{M}N_{m}}\sum_{m=1}^{M}\sum_{n=1}^{N_{m}}\log P_\mathcal{M}(w_{n}^{m}|w_{1}^{m},\cdots,w_{n-1}^{m}) \right ) \\
    \end{align}
    $$
  • 注意:多个文档的困惑度不是简单的等于所有文档困惑度的积或和 ,而是等于把所有文档合并成一个大文档,大文档的困惑度则是最终所有文档的困惑度
  • 特别说明:一些地方也会使用平均法,使用每个文档的平均困惑度代表示多文档的困惑度

LDA 的困惑度

  • LDA中 \(w_{1},w_{2},\cdots,w_{n}\) 在参数已知的情况下是互相独立的,则有
    $$
    \begin{align}
    \text{Perplexity}_\mathcal{M}(d) &= e^{-\frac{1}{\sum_{m=1}^{M}N_{m}}\sum_{m=1}^{M}\log P_\mathcal{M}(w_{1}^{m}, w_{2}^{m},\cdots,w_{N_{m}}^{m})} \\
    &= \exp \left ( -\frac{1}{\sum_{m=1}^{M}N_{m}}\sum_{m=1}^{M}\log P_\mathcal{M}(w_{1}^{m}, w_{2}^{m},\cdots,w_{N_{m}}^{m}) \right ) \\
    &= \exp \left ( -\frac{1}{\sum_{m=1}^{M}N_{m}}\sum_{m=1}^{M}\log\prod_{n=1}^{N_{m}}P_\mathcal{M}(w_{n}) \right ) \\
    &= \exp \left ( -\frac{1}{\sum_{m=1}^{M}N_{m}}\sum_{m=1}^{M}\log\prod_{n=1}^{N_{m}}\sum_{k=1}^{K}P_\mathcal{M}(w_{n}=t|z_{n}=k;\mathcal{M})P_\mathcal{M}(z_{n}=k|d=d_{m};\mathcal{M})\right ) \\
    &= \exp \left ( -\frac{1}{\sum_{m=1}^{M}N_{m}}\sum_{m=1}^{M}\log\prod_{n=1}^{N_{m}}\sum_{k=1}^{K}\theta_{m,k}\phi_{k,t}\right ) \\
    &= \exp \left ( -\frac{1}{\sum_{m=1}^{M}N_{m}}\sum_{m=1}^{M}\log\prod_{n=1}^{N_{m}}\theta_{m,:}\phi_{:,t}\right ) \\
    &= \exp \left ( -\frac{1}{\sum_{m=1}^{M}N_{m}}\sum_{m=1}^{M}\sum_{n=1}^{N_{m}}\log\theta_{m,:}\phi_{:,t}\right ) \\
    \end{align}
    $$
  • 其中 \(\phi_{k,t}\) 表示单词t在主题k中出现的概率,\(\theta_{m,k}\) 表示主题k在文档m中出现的概率
  • \(\sum_{k=1}^{K}\theta_{m,k}\phi_{k,t} = (\theta_{m,:}\phi_{:,t})\) 就是单词t出现在文档m中的概率(对隐变量主题k积分)
  • 上面式子中 \((\theta_{m,:}\phi_{:,t})\) 就是两个向量的内积,在这里: \(\theta_{m,:}\) 代表行向量,表示当前文档 \(d_{m}\) 的主题分布, \(\phi_{:,t}\) 代表列向量,表示当前每个主题生成词 \(w_{t}\) 的概率
  • 计算公式的代码可参考L-LDA模型的实现GitHub仓库: Labeled-LDA-Python 中的perplexity函数和log_perplexity函数

CA——Feature-Hashing

  • 参考文献: Feature Hashing for Large Scale Multitask Learning, 2009, Yahoo! Research
  • 特征哈希(Feature Hashing)常用于数据特征降维,同时尽量保留原始特征的表达能力

论文贡献

  • 给出了一种高维数据降维方法,特征哈希(Feature Hashing)
  • 严格证明了特征哈希的可用性

Background

  • 一种构造组合特征的方法是笛卡尔乘积
  • 计算广告领域往往有数十亿的高维特征
  • 一种表达方式是使用词表法,对每个特征从词表里面进行查询
    • 缺陷一:拓展问题,每次拓展词表时难度较大(难以进行模型升级,因为特征维度在变化)
    • 缺陷二:无法处理词表中不存在的特征(Unknown特征)
  • 一般的降维方法容易带来特征表达能力的损失

特征哈希

  • 哈希函数定义(参考自博客:https://blog.csdn.net/qjf42/article/details/82387559)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    def feature_hashing(features, m):
    """
    Args:
    features: 输入特征序列,可以是数字,字符串(本质还是数字)
    m: 输出的特征维度,通常是2**26(vw),2**20(scikit-learn)
    Returns:
    m维的(稀疏)向量
    """
    # 这里的x一般是稀疏表示的(如:scipy.sparse.csr_matrix),这里是为了方便说明
    x = np.zeros(m)
    for feature in features:
    # hash_func_1保证散列尽量平均且散列速度快
    idx = hash_func_1(feature) % m
    # 这里在原始论文中
    sign = hash_func_2(feature) % 2
    if sign == 1:
    x[idx] += 1
    else:
    x[idx] -= 1
    return x
    • 输出特征维度一般为 \(m = 2^24\) 等,是一个认为给定的数字
  • 与词表法比较:

    • 解决了模型升级时的特征拓展问题(增加新特征时,特征的维度不会变化)
    • 解决了Unknown特征问题(个人理解:因为hash函数不管是什么特征,都可以一视同仁)
    • 无需查表,节省了查表的时间(个人理解:其实查表时一般实现方式也是用哈希表构建索引,所以这里不能算是优势)
    • 完成了降维(这里是把字典法里面对邮件或者文档的id也算作一个特征,这个特征one hot表示一下将会造成数据维度变得非常大?但是id算做特征有什么意义吗?)

一些说明

  • 冲突总会发生,也就是说不同一个特征总有一定的概率被映射到同一个维度(即两个不同特征的idx值可能相等)上
  • Paper中的垃圾邮件过滤模型实验证明:冲突造成的损失漏召率在 \(m=2^22\) 时影响约为1%,接近不做hash时的效果(特征维度在不做hash约为 \(2^{25}\))且在 \(m=2^{18}\) 时为1.12%,也只升高了一点点
  • 另外:无论如何,总有特殊情况,比如重要的特征如用户的性别特征“男”和“女”二者可能被映射到同一个维度上
    • 这里编码时是男:(1, 0), 女(0, 1)这样的,所以如果二者映射到同一个维度上,那么这两个特征丢失了原本的表达能力
    • 真实环境中如果遇到这些问题将会很难调试,如果找到了相关的问题,可以通过修改映射函数的输入参数字符串等方式来错开两个特征
  • 特征哈希本身可以类比于机器学习中的核技巧,所以特征哈希也称为哈希技巧

一些理解

知乎用户

  • 参考Ainika Peng的博客:https://www.zhihu.com/question/264165760/answer/279676705
  • 一般而言这类技术是为了解决两个问题:
    • 一是将categorical的特征编码为模型可理解的格式 , 这是个基础问题。One-Hot Serializing就可以达到这个效果,例如将训练样本中出现过的的每个deviceid按顺序递增编号(比如deviceid@xxx:1 -> feature@10000:1)
      • 缺点1:这个映射表需要传递给引擎端,在prediction的时候照着再查一遍,数据量大且数据结构设计不好的时候很费时间;
      • 缺点2:有些频次很低的特征置信度不高,单独出现对模型无益(甚至over-fitting)。这时候可以使用按频次截断技术进行降维。比如微软在deep crossing中提到的特征工程方法:只保留曝光最多的10k个campaign id编码为0-9999,其余的id全部编码为10000,但辅以poCTR等连续特征作为辅助。事实上这是一种手工的Hashing方法
    • 二是尽可能在保留有效信息的基础上降低训练和预测过程的时间复杂度
  • 自动Hashing方法的好处是:
    • 只要训练和预测时使用的hashing方法一致,对同一个特征的编码结果即一致,因此引擎预测或提供数据dump的时候无需查找编码表。所以其最大优点在于数据一致性和速度提升,这在极大规模特征和在线学习中至关重要
  • 我们说的Hashing算法一般而言均特意设计为低碰撞率
    • 因此一般hashing算法本身不会大幅降低特征维度,自然也不会大幅损失特征信息。真正可能存在问题的是hashing之后的降维过程
    • 一个非常常见的陷阱是string哈希到int64后取模m,试图将特征压缩至m维one-hot空间。在这种情况下,对于不知情的随机hashing过程,不同特征的碰撞率为1/m。举个例子,对于“性别”特征,将male哈希为一个int64,female哈希为另一个int64,很难发生碰撞;但如果你试图使用mod2将其压缩,如果你的算法哈希出的这两个int64奇偶性相同,则会导致特征失效。在你有很多feature需要哈希而又不清楚hashing算法细节的情况下,这在概率意义上是很容易发生的
      • 这里的mod2是极端举例,其实m的取值小于原始维度的情况下都有一定概率造成冲突
  • 因此我们会更倾向于所谓的embedding算法
    • 例如将70万维的userid通过weight embedding到32维的连续值特征上(不同于传统hashing的低维离散值特征)。这意味着训练过程更加复杂(有更多的weight需要optimize);但对于预测过程,其特征性能十分良好且时间复杂度得以降低。同时,由于连续值特征空间的表达能力大幅高于离散值特征空间,整个模型的表达能力并不会明显下降,也基本不会发生离散hashing的碰撞问题
    • 当然,如果是FM这类倾向于接受离散值的模型,手工serializing+精心设计的hashing是较好的选择
  • 优点:
    • 训练和预测的时间复杂度大幅降低;
    • 数据的一致性强,不存在同一个特征今天编码成这个、明天编码成那个的情况,便于跟踪单特征效果;
    • 对new feature可以直接编码并加入训练,无需等待编码表统计并反馈;
    • 降低feature space大小,(精心设计可以)降低over-fitting的几率
  • 缺点
    • 在不清楚hashing function细节的情况下,容易导致特征碰撞失效,且难以排查;
    • 难以通过hashing出的特征反推源特征;
    • embedding会降低模型的可解释性。

General——各种包的管理总结


编程语言相关

  • 参考链接: https://help.github.com/en/github/managing-packages-with-github-packages/about-github-packages#supported-clients-and-formats
Package client Language Package format Description
npm JavaScript package.json Node package manager
gem Ruby Gemfile RubyGems package manager
mvn Java pom.xml Apache Maven project management and comprehension tool
gradle Java build.gradle or build.gradle.kts Gradle build automation tool for Java
docker N/A Dockerfile Docker container management platform
nuget .NET nupkg NuGet package management for .NET
pip Python requirements.txt use pip install -r requirements.txt

操作系统

  • 参考链接: https://www.iteye.com/blog/justcoding-1937171
软件管理方式 线下安装命令 线上安装命令 distribution 操作系统
RPM rpm, rpmbuild yum Red Hat/Fedora
DPKG dpkg apt, apt-get Debian/Ubuntu

rpm和dpkg常用命令总结

  • 参考链接: http://cha.homeip.net/blog/archives/2005/08/rpm_vs_dpkg.html
操作描述 rpm dpkg
安装指定套件 rpm -i pkgfile.rpm dpkg -i pkgfile.deb
显示所有已安装的套件名称 rpm -qa dpkg -l
显示套件包含的所有档案 rpm -ql [softwarename] dpkg -L [softwarename]
显示特定档案所属套件名称 rpm -qf [/path/to/file] dpkg -S [/path/to/file]
显示制定套件是否安装 rpm -q [softwarename] dpkg -l [softwarename], -s或-p显示详细咨询, -l只列出简洁咨询
移除指定套件 rpm -e [softwarename] dpkg -r softwarename, -r 留下套件设定, -P完全移除

apt和yum常用命令总结

  • 参考博客: https://cnblogs.com/lanbosm/p/9130211.html
操作描述 yum apt
软件源配置文件路径 /etc/yum.conf /etc/apt/sources.list
安装软件包 yum install [package] apt-get install [package]
删除软件包 yum uninstall [package] apt-get remove [package]
删除有依赖关系的软件包和配置文件 yum uninstall [package] apt-get autoremove [package] –purge
查看安装包信息 yum info [package] apt-cache show [package]
更新软件包列表 yum update apt-get update
清空缓存 yum clean apt-get clean
搜索包名 yum apt-cahce search

一些特殊命令

apt

  • 列出所有可用包名

    1
    apt-cache pkgnames
  • 通过描述列出包名

    1
    apt-cache search [keys]
  • 指定包的版本号

    1
    apt-get install [package]=[version]

yum

  • 搜索包的可用版本

    1
    apt --showduplicates list [package] | expand
    • expand命令用于将文件的制表符tab转换成空格符space
      • 默认一个tab对应8个space
      • 若不指定文件名(或者文件名为-), 则expand会从标准输入读取数据
    • unexpand命令与expand相反
  • 安装时指定包的版本号

    1
    apt install [package]-[version]

yum和apt安装的常用参数

  • -y: 指定在询问是否安装时均选择yes
  • -q: quiet,安装途中不打印log信息

General——各种压缩方式总结


.tar.gz

.tar.bz2

.tar.xz

.tgz

Centos——clamav安装与杀毒

clam是一款Linux上免费的杀毒软件


安装与配置

命令行安装

  • 命令行安装clamav会自动创建clamav用户和clamav组

  • Centos(笔者在Centos7亲测)

    1
    apt install –y clamav clamav-update
  • ubuntu(笔者在Ubuntu16.04上亲测)

    1
    apt-get install clamav

源码安装

  • 如果因为某些原因无法从命令行安装,可以尝试用源码安装,此时需要首先手动创建相关组和用户
安装前的配置
  • 创建clamav组

    1
    groupadd clamav
  • 创建用户并添加到clamav组

    1
    useradd -g clamav clamav
源码下载和安装
  • 下载流程:

    • 下载链接: http://www.clamav.net/downloads, 因为版本可能会有更新,这里我们直接给出网站下载地址,可以随时查看版本信息
    • 找到软件包下载链接后,使用wget下载即可,比如
      1
      wget http://www.clamav.net/downloads/production/clamav-0.102.0.tar.gz
  • 安装流程:

    • 解压

      1
      tar -xf clamav-0.102.0.tar.gz
    • 切换目录

      1
      cd clamav-0.102.0
    • 安装依赖

      1
      yum/apt install gcc openssl openssl-devel

使用

升级病毒库

  • 升级命令

    • 命令行安装后更新命令

      1
      freshclam
    • 源码安装后更新命令, 也可以建立软连接后直接使用上面的命令行启动

      1
      /usr/local/clamav/bin/freshclam
    • 建立链接指令

      1
      ln -s [source path] [target path]

查找病毒文件

  • 常用命令

    1
    nohup clamscan / -r --infected -l clamscan.log > clamscan.out &
    • -r 指明递归查杀
    • --infected 表示仅仅输出被感染的部分文件, 否则没有被感染的文件会输出文件名: OK这样无用的信息
    • -l 指明日志文件路径
    • / 是查找的目标目录,如果是整个机器查找则使用/
    • 由于查杀病毒需要很长时间,所以建议使用后台进程进行, 如果是远程, 建议使用nohup
    • 由于输出非常多,所以一般我们使用clamscan.out和clamscan.log分别存储输出和日志
  • 列出被感染文件

    1
    cat clamscan.out | grep FOUND

DL——Transformer

本文主要介绍Transformer和Attention相关内容

  • 由于LaTex中矩阵的黑体表示过于复杂,在不会引起混淆的情况下,本文中有些地方会被简写为非黑体

相关论文介绍

  • Transformer原始文章:
    • Google Brain, NIPS 2017: Attention Is All You Need
    • 文章中介绍了一种应用Attention机制的新型特征提取器,命名为Transformer, 实验证明Transformer优于RNN(LSTM),CNN等常规的特征提取器
  • Transformer的使用:
    • GPT: Improving Language Understanding by Generative Pre-Training
    • BERT: BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding
    • 以上两个工作都使用了Transformer作为特征提取器, 使用两阶段训练的方式实现迁移学习(Pre-Training and Fine-Training)

相关博客介绍

  • 强烈推荐看看jalammar的博客: illustrated-transformer
  • 另一篇不错的Attention和Transformer讲解自然语言处理中的自注意力机制(Self-Attention Mechanism)
  • 一篇很多个人理解的博客 《Attention is All You Need》浅读

Transformer讲解

  • 最直观的动态图理解
  • 本文讲解主要按照Google Brain, NIPS 2017: Attention Is All You Need的思路走,该论文的亮点在于:
    • 不同于以往主流机器翻译使用基于 RNN 的 Seq2Seq 模型框架,该论文用 Attention 机制代替了 RNN 搭建了整个模型框架, 这是一个从换自行车零件到把自行车换成汽车的突破
    • 提出了多头注意力(Multi-Head Attention)机制方法,在编码器和解码器中大量的使用了多头自注意力机制(Multi-Head self-attention)
    • 在WMT2014语料库的英德和英法语言翻译任务上取得了先进结果

Transformer是什么?

  • Transformer 是个序列转换器 :
  • 进一步讲,是个 Encoder-Decoder 模型的序列转换器 :
  • 更进一步的讲,是个 6层Encoder + 6层Decoder 结构的序列转换器:
  • 上面的图中,每个 Encoder 是:
  • 详细的讲, 每个Encoder是
  • 展开看里面 Encoder 中的数据流向
  • 更进一步的展开看 Encoder 中的数据流向
  • 两层 Encoder + 两层Decoder (其中一个Decoder没有完全画出来) 的数据流向
  • 带细节动图查看数据流向
  • 最后,我们给出Transformer的结构图(来自原文中)

Transformer中的Attention

Transformer中使用了 Multi-Head Attention, 同时也是一种 Self Attention

  • 由于Transformer的Multi_Head Attention中 Query == Key == Query , 所以也是一种 Self Attention
    • 即
      $$\boldsymbol{Y_{AttentionOutput}} = Self Attention(\boldsymbol{Q},\boldsymbol{K},\boldsymbol{V}) = Attention(\boldsymbol{X},\boldsymbol{X},\boldsymbol{X})$$
  • 更多关于广义Attention的理解请参考: DL——Attention

Multi-Head Attention

  • Muti-Head Attention,也称为多头Attention,由 \(h\) 个 Scaled Dot-Product Attention和其他线性层和Concat操作等组成
  • Scaled Dot Product Attention中Mask操作是可选的
  • Scaled Dot Product Attention数学定义为(没有Mask操作)
    $$
    \begin{align}
    Attention(\boldsymbol{Q},\boldsymbol{K},\boldsymbol{V}) = softmax\left(\frac{\boldsymbol{Q}\boldsymbol{K}^{\top}}{\sqrt{d_k}}\right)\boldsymbol{V}
    \end{align}
    $$
    • Softmax前除以 \(\sqrt{d_k}\) 的原因是防止梯度消失问题,基本思想是(原始论文脚注中有提到):假设 \(\boldsymbol{Q},\boldsymbol{K}\) 中每个元素是服从均值为0,方差为1的正太分布( \(\sim N(0,1)\) ),那么他们任意取两个列向量 \(\boldsymbol{q}_i,\boldsymbol{k}_i\) 的内积服从均值为0,方差为 \(d_k\) 的正太分布( \(\sim N(0,d_k)\) ),具体证明可参考没有比这更详细的推导 attention为什么除以根号dk——深入理解Bert系列文章,过大的方差会导致softmax后梯度消失
  • Multi-Head Attention的某个输出的数学定义为
    $$
    \begin{align}
    MultiHead(\boldsymbol{Q}, \boldsymbol{K}, \boldsymbol{V}) &= Concat(head_1,\dots,head_h)\boldsymbol{W}^{O} \\
    where \quad head_i &= Attention(\boldsymbol{Q}\boldsymbol{W}_i^Q,\boldsymbol{K}\boldsymbol{W}_i^K,\boldsymbol{V}\boldsymbol{W}_i^V)
    \end{align}
    $$
    • 注意,在一般的Attention中,没有 \(\boldsymbol{W}^{O}\) 这个参数,这个是用于多头Attention中,将多头的输出Concat后映射一下再输出
      • 理解:若不是多头,其实加不加这个参数,本质都是一样的,因为连续的两个权重矩阵线性相乘,本质就是一个权重矩阵而已
      • 补充:在实现时,这里的 \(\boldsymbol{W}^{O}\) 实际上是一个线性层 nn.Linear 的含义,就是一个简单的矩阵,不包含非线性信息的
    • 一般来说, \(head_i\) 的维度是 \(\frac{d_{model}}{N_{head}}=\frac{d_{model}}{h}=d_v = d_k\),所以Multi-Head Attention的参数数量与head的数量无关,且无论多少个头,其的输出结果还是 \(d_{model} = d_v * h\) 维
    • 原始论文中常用 \(d_{model} = h * d_k = h * d_v\),且base模型的参数设置为 \(512 = 8 * 64\)
有关Multi-Head Attention的理解
  • 原论文的描述:

    Multi-head attention allows the model to jointly attend to information from different representation subspaces at different positions,

  • 理解:

    • 所谓多头,就是多做几次(\(h\) 次)同样的事情(参数 \((W_i^Q, W_i^K, W_i^V)\) 不共享, 即当 \(i \neq j \) 时, \((W_i^Q, W_i^K, W_i^V) \neq (W_j^Q, W_j^K, W_j^V)\)),然后把结果拼接
    • Multi-Head Attention中, 每个头(Scaled Dot-Product Attention)负责不同的子空间(subspaces at differect positions)
    • 每个头权重不同, 所以他们的关注点也会不同,注意, 初始化时他们的参数不能相同, 否则会造成他们的参数永远相同, 因为他们是同构的
    • 个人理解: 多头的作用可以类比于CNN中的卷积层, 负责从不同的角度提取原始数据的特征

Self Attention

  • Self Attention是只 Key和Query相同的 Attention, 这里因为 Key 和 Value 也相同,所以有 Query == Key == Query
  • 即$$ \boldsymbol{Y_{AttentionOutput}} = Self Attention(\boldsymbol{Q},\boldsymbol{K},\boldsymbol{V}) = Attention(\boldsymbol{X},\boldsymbol{X},\boldsymbol{X})$$

Transformer中的Attention

  • 既是Multi-Head Attention, 也是 Self Attention
  • 所以有$$\boldsymbol{Y_{AttentionOutput}} = MultiHead(\boldsymbol{X},\boldsymbol{X},\boldsymbol{X})$$

Masked Multi-Head Attetion

  • MaskedMHA,掩码多头Attention,用于Decoder中防止前面的token看到后面的token,Encoder中不需要MaskedMHA
  • 一般性的,Masked Self-Attention是更一般的实现,不一定非要和Multi-Head绑定
  • 代码实现时,主要是在计算Softmax前,按照掩码将看不到的token对应的q,k内积替换为一个大负数,比如 \(-1e9\)

Cross Multi-Head Attention

  • CrossMHA不是Self-Attention,CrossMHA的Q,K是Encoder的输出,V来自Decoder

Transformer 输入层

  • Transformer的输入层使用了 Word Embedding + Position Embedding
  • 由于Transformer去除RNN的Attention机制完全不考虑词的顺序, 也就是说, 随机打乱句子中词的顺序 (也就是将键值对 \((\boldsymbol{K}, \boldsymbol{V})\) 对随机打乱), Transformer中Attention的结果不变
  • 实际上, 目前为止, Transformer中的Attention模型顶多是个非常精妙的”词袋模型” (这句话来自博客:https://kexue.fm/archives/4765)

Word Embedding

  • 和之前的词嵌入一样, 将One-Hot值映射成词向量嵌入模型中
  • Tie Embedding :嵌入层(Embedding Layer)和输出投影层(Unembedding Layer / Output Projection Layer)绑定(即共享权重)
    • 基本思路:一个是 token 到 Embedding 映射,另一个是 Embedding 到 token 映射,绑定方式是W_out = W_embed^T
    • 这种绑定的优势是节约存储、训练稳定;缺点是表达能力受限、梯度冲突可能严重(比如输入和输入的词分布差异大)
  • 原始论文中关于参数绑定(Weight-Tying)的说明在3.4节:

    In our model, we share the same weight matrix between the two embedding layers and the pre-softmax linear transformation, similar to [24]. In the embedding layers, we multiply those weights by \(\sqrt{d_{\text{model}}}\)

Position Embedding

FaceBook的《Convolutional Sequence to Sequence Learning》中曾经用过Position Embedding

  • 在不使用RNN的情况下建模词的顺序, 弥补”词袋模型”的不足
  • 用 Position Embedding来为每个位置一个向量化表示
    • 将每个位置编号,然后每个编号对应一个向量
    • 通过结合位置向量和词向量,就给每个词都引入了一定的位置信息,这样Attention就可以分辨出不同位置的词了
  • 原始论文中, 作者提出了一种周期性位置编码的表示, 数学公式如下:
    $$
    \begin{align}
    PE(pos,2i) &= sin(pos/10000^{2i/d_{\text{model}}}) \\
    PE(pos, 2i+1) &= cos(pos/10000^{2i/d_{\text{model}}})
    \end{align}
    $$
  • 我觉得上述公式太丑了,转换一下写法可能更容易理解
    $$
    \begin{align}
    PE(pos,2i) &= sin\left (\frac{pos}{10000^{\frac{2i}{d_{\text{model}}}}}\right) \\
    PE(pos, 2i+1) &= cos\left (\frac{pos}{10000^{\frac{2i}{d_{\text{model}}}}}\right)
    \end{align}
    $$
    • \(pos\) 是位置编号
    • \(i\) 表示位置向量的第 \(i\) 维
    • 从公式来看,为什么选择 \(10000^{\frac{2i}{d_{\text{model}}}}\) ?
      • \(i\) 表示频率随模型embedding维度变动(模型embedding不同维度频率不同,低维度高频,高维度低频)
      • \(pos\) 表示周期,随着位置变化,每个维度的值呈现周期变化,但是不同维度的变化周期(频率)不同
      • 10000是一个放缩因子,理论上可以换,在transformer原始论文实现中用了这个,且效果不错
    • 选择正弦函数的原因是假设这将允许模型学到相对位置信息
      • 因为对于固定的 \(k\), \(PE_{pos+k} = LinearFuction(PE_{pos})\),所以这给模型提供了表达相对位置的可能性
与之前的Position Embedding的区别
  • Position Embedding对模型的意义不同:
    • 以前在RNN、CNN模型中Position Embedding是锦上添花的辅助手段,也就是“有它会更好、没它也就差一点点”的情况,因为RNN、CNN本身就能捕捉到位置信息
    • 在Transformer这个纯Attention模型中,Position Embedding是位置信息的唯一来源,因此它是模型的核心成分之一,并非仅仅是简单的辅助手段
  • Position Embedding的向量构造方式不同
    • 在以往的Position Embedding中,基本都是根据任务训练出来的向量
    • 而Google直接给出了一个构造Position Embedding的公式:
      $$
      \begin{align}
      PE(pos,2i) &= sin\left (\frac{pos}{10000^{\frac{2i}{d_{\text{model}}}}}\right) \\
      PE(pos, 2i+1) &= cos\left (\frac{pos}{10000^{\frac{2i}{d_{\text{model}}}}}\right)
      \end{align}
      $$
    • Google经过实验, 学到的位置嵌入和这种计算得到的位置嵌入结果很相近
    • Google选用这种嵌入方式的原因是这种方式允许模型以后可以扩展到比训练时遇到的序列长度更长的句子

输入层的输出(Attention的输入)

  • 综合词嵌入和位置嵌入信息,我们可以得到下面的公式
    $$
    \begin{align}
    \boldsymbol{x} = \boldsymbol{x}_{WE} + \boldsymbol{x}_{PE}
    \end{align}
    $$
    • \(\boldsymbol{x}\) 为输入层经过词嵌入和位置嵌入后的 输出, 也就是Attention的输入
    • \(\boldsymbol{x}_{WE}\) 指词嵌入的结果
    • \(\boldsymbol{x}_{PE}\) 指位置嵌入的结果

FFN

  • FFN,Feed Forward Network,前馈网络层
    $$
    FFN(\mathbf{X}) = ReLU(\mathbf{X}\mathbf{W}^U + \mathbf{b}_1)\mathbf{W}^D + \mathbf{b}_2
    $$
  • 原始 Transformer 使用的是 ReLU 作为激活函数,现在很多时候也会选用 sigmoid
  • 可以看到前馈神经网络包含了两层
  • 注:原始 Transformer 论文中,使用的 \(H = d_\text{model} = 512\),\(d_{ff} = 2048\),FFN 中间隐藏维度是 \(d_\text{model} = 512\) 的 4 倍
    • 即 FFN 的参数量为:\(2 \times 4H \times H = 8H^2\)

Layer Normalization

  • 层归一化,是Transformer特有的一种归一化方法
  • Batch Normalization(BN)不适用与Transformer中,至少有以下原因:
    • Transformer训练样本通常(特别是模型很大时)可能会比较小,在Batch较小时BN不再适用
    • BN是按照token维度(特征维度)来归一化的,不利于处理变长输入序列
      $$
      LayerNorm(\mathbf{x}) = \frac{\mathbf{x}-\mathbf{\mu}}{\mathbf{\sigma}}\cdot \mathbf{\gamma} + \mathbf{\beta} \\
      \mathbf{\mu} = \frac{1}{H}\sum_{i=1}^H x_i, \quad \mathbf{\sigma} = \sqrt{\frac{1}{H}\sum_{i=1}^H(x_i-\mathbf{\mu})^2} \\
      $$
  • 代码实现是会在分母的更号内增加一个极小量 \(\epsilon\),防止出现除0的情况
  • 显然,LayerNorm是基于token来归一化的,当前token的归一化结果与其他token无关,不受其他token影响
  • 可能出现不同的token向量LayerNorm归一化以后输出相同的值,比如[1,2,3,4]、[2,3,4,5]和[2,4,6,8]的输出结果都相同
    • 可以看到是因为这些向量维度之间的分布很相似,或者呈现倍数关系,才导致输出值为0,实际模型中,维度一般是64维或者128维等,而且是小数,几乎不会出现两个不同的token经过LayerNorm后输出相同的值

LN是token维度的

  • 按照Transformer源码实现来看,LayerNorm是Token维度的,不是Seq维度,也就是说,token向量LayerNorm的结果只与token向量自身相关,与所在序列的其他token无关

    • 这一点是Decoder可以增量解码的关键,这一点保证了Decoder的前序词不会受到后续词的影响
    • 增量解码是指:Decoder中输出下一个词时,可以使用前序词的缓存结果,由于前面的词看不到后面的词,所以增加词前后Transformer-Decoder中前序每个词的输出在每一层都不会受到影响
  • 一个LayerNorm的示例如下,Transformer源码中实现与这个类似

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import torch.nn as nn
    import torch

    # 假设d_model=4,d_model在有些实现中为hidden_size
    layer_norm = nn.LayerNorm(4) # 这里使用LayerNorm,也可以使用RMSNorm,两者作用维度相同,只是公式不同
    # test case 1:
    input_tensor = torch.Tensor([[[1, 2, 3, 4],
    [2, 3, 4, 5]]])
    output_tensor = layer_norm(input_tensor)
    print(output_tensor)
    # output:
    # tensor([[[-1.3416, -0.4472, 0.4472, 1.3416],
    # [-1.3416, -0.4472, 0.4472, 1.3416]]],
    # grad_fn=<NativeLayerNormBackward0>)

    # test case 2:
    input_tensor = torch.Tensor([[[1, 2, 3, 4],
    [200, 3, 4, 5]]])
    output_tensor = layer_norm(input_tensor)
    print(output_tensor)

    # tensor([[[-1.3416, -0.4472, 0.4472, 1.3416],
    # [ 1.7320, -0.5891, -0.5773, -0.5655]]],
    # grad_fn=<NativeLayerNormBackward0>)
  • 从示例中可以看出:

    • 修改第二个token的某个元素值,只影响第二个token的LN输出,不影响第一个token

Transformer改进-LN

原始LN参见论文之前的内容

LN的改进——RMSNorm

  • RMSNorm 的公式如下:
    $$
    \begin{align}
    RMSNorm(\mathbf{x}) &= \frac{\mathbf{x}-\mathbf{\mu}}{RMS(\mathbf{x})}\cdot \mathbf{\gamma} \\
    RMS(\mathbf{x}) &= \sqrt{\frac{1}{H}\sum_{i=1}^H x_i^2} \\
    \end{align}
    $$
    • 代码实现是会在分母的更号内增加一个极小量 \(\epsilon\),防止出现除 0 的情况
    • 其中 \(\mu\) 和 \(\gamma\) 是可学习参数,但一般的实现中没有 \(\mu\),只有一个参数 \(\gamma\)
  • 截止到24年,PyTorch 官方还没有提供标准的 RMSNorm 实现,下面是 HuggingFace Transformers 中的实现:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @use_kernel_forward_from_hub("RMSNorm")
    class LlamaRMSNorm(nn.Module):
    def __init__(self, hidden_size, eps=1e-6):
    """
    LlamaRMSNorm is equivalent to T5LayerNorm
    """
    super().__init__()
    self.weight = nn.Parameter(torch.ones(hidden_size))
    self.variance_epsilon = eps

    def forward(self, hidden_states):
    input_dtype = hidden_states.dtype
    hidden_states = hidden_states.to(torch.float32)
    variance = hidden_states.pow(2).mean(-1, keepdim=True)
    hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
    return self.weight * hidden_states.to(input_dtype)

    def extra_repr(self):
    return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"

LN的改进——DeepNorm

$$
DeepNorm(\mathbf{x}) = LayerNorm(\alpha\cdot \mathbf{x} + \text{Sublayer}(\mathbf{x})) \\
$$

  • 这里的 \(\text{Sublayer}(\mathbf{x})\) 是指Transformer中的前馈神经网络层或自注意力模块(两者都会作为LN的输入)
  • 实际上,原始的Transformer中,每次LN的内容都是加上残差的,这里根据归一化位置的不同还有会有不同的实现
  • 原始的Transformer中,相当于 \(\alpha=1\) 的DeepNorm
  • 这里叫做DeepNorm的原因是因为缩放残差 \(\mathbf{x}\) 可以扩展Transformer的深度,有论文提到利用该方法可将深度提升到1000层(DeepNet: Scaling Transformers to 1,000 Layers)

归一化的位置

  • 归一化的位置包括Post-Norm、Pre-Norm和Sandwich-Norm等
  • Post-Norm
    • 原始Transformer使用的方法
    • 将归一化模块使用到加法(需要把残差加到FFN/MHA的输出上)之后,详细公式是:
      $$
      \text{Post-Norm}(\mathbf{x}) = Norm(\mathbf{x} + \text{Sublayer}(\mathbf{x}))
      $$
    • \(\text{Sublayer}\)
  • Pre-Norm
    • 归一化模块放到FFN/MHA之前,详细公式是:
      $$
      \text{Pre-Norm}(\mathbf{x}) = \mathbf{x} + \text{Sublayer}(Norm(\mathbf{x}))
      $$
  • Sandwich_Norm
    • 三明治归一化,从字面意思可以知道,是两个Norm将某个层夹起来,实际上,该层是前馈神经网络层或自注意力模块
      $$
      \text{Sandwish-Norm}(\mathbf{x}) = \mathbf{x} + Norm(\text{Sublayer}(Norm(\mathbf{x})))
      $$

归一化位置的比较

DeepNet: Scaling Transformers to 1,000 Layers中也有有关Pre-Norm和Post-Norm的探讨

  • 一般来说,使用Post-Norm比较多,效果也更好
  • Pre-Norm在深层Transformer中容易训练(容易训练不代表效果好,Pre-Norm的拟合能力一般不如Post-Norm)
    • 所以有些模型还是会使用Pre-Norm,因为它更稳定
  • 浅层中建议使用Post-Norm
  • 详情参考苏神的回答为什么Pre Norm的效果不如Post Norm?,其中引用到了 如何评价微软亚研院提出的把 Transformer 提升到了 1000 层的 DeepNet? - 唐翔昊的回答 - 知乎

    Pre Norm的深度有“水分”!也就是说,一个 L 层的Pre Norm模型,其实际等效层数不如 L 层的Post Norm模型,而层数少了导致效果变差了
    Pre Norm结构无形地增加了模型的宽度而降低了模型的深度,而我们知道深度通常比宽度更重要,所以是无形之中的降低深度导致最终效果变差了

  • 直观上来说就是:
    • Pre-Norm 是(不严谨的变换)近似将深度转换成了宽度
      $$
      \begin{align}
      \mathbf{x}_{t+1} &= \mathbf{x}_t + f(Norm(\mathbf{x}_t)) \\
      &= \mathbf{x}_{t-1} + f(Norm(\mathbf{x}_{t-1})) + f(Norm(\mathbf{x}_t)) \\
      &\approx \mathbf{x}_{t-1} + 2f(Norm(\mathbf{x}_{t}))
      \end{align}
      $$
      • 注:这里大家认为可假定在 \(t\) 较大时, \(\mathbf{x}_t\) 和 \(\mathbf{x}_{t-1}\) 是基本相似的
      • 我们称 \(x_1,x_2,\cdots,x_t\) 为主干,在 Pre-Norm 中,主干一直在加入新的东西,所以方差越来越大,且单层网络对主干的影响越来越小
      • 此外,如何评价微软亚研院提出的把 Transformer 提升到了 1000 层的 DeepNet? - 唐翔昊的回答 - 知乎中还认为不同层的 \(f\) (MHA 或 FFN)在统计上是相似的
        • TODO:这一点有待考证和理解
    • Post-Norm 则是保持深度
      $$
      \begin{align}
      \mathbf{x}_{t+1} &= Norm(\mathbf{x}_t + f(\mathbf{x}_t)) \\
      &= Norm(Norm(\mathbf{x}_{t-1} + f(\mathbf{x}_{t-1})) + f(\mathbf{x}_t))
      \end{align}
      $$
      • Post-Norm 中,保证了主干方差是恒定的

Transformer改进-激活函数

原始激活函数是ReLU(Rectified Linear Unit)

Swish(SiLU)

  • 参考文章:Sigmoid-Weighted Linear Units for Neural Network Function Approximation in Reinforcement Learning
  • Swish 的全称为 “Scaled Exponential Linear Unit with Squishing Hyperbolic Tangent”,直译为“带有压缩的缩放指数线性单元”
    $$
    \text{Swish}_\beta(x) = x \cdot sigmoid(\beta x)
    $$
    • 许多实现中常常设置 \(\beta=1\)
  • 注:Swish, 简称为 Sigmoid-weighted Linear Unit,所以一些文章中也称为 SiLU,

GELU

  • GELU, Gaussion Error Linear Unit,有时候也写作GeLU
    $$
    \text{GELU}(x) = 0.5x \cdot [1+\text{erf}(\frac{x}{\sqrt{2}})], \quad \text{erf}(x) = \frac{2}{\sqrt{\pi}}\int_1^x e^{-t^2} dt
    $$
    • erf 是 Gauss Error function 的缩写,在很 Torch 和 TensorFlow 中都是定义好的
  • 从公式可以看出GELU的本质是对一个正太分布的概率密度函数进行积分,实际上就是累积分布函数
  • GELU和ReLU的比较如下(图片来自简单理解GELU 激活函数):

FastGELU

  • FastGELU, Fast Gaussion Error Linear Unit,出自论文 CodeGeeX: A Pre-Trained Model for Code Generation with Multilingual Benchmarking on HumanEval-X, KDD 2024, THU & Huawei,该方案作为 GELU 的近似,在升腾 910 芯片上能加速
    $$ \text{FastGELU}(X_i) = \frac{X_i}{1+\exp(-1.702*|X_i|) \cdot \exp(0.851\cdot (X_i - |X_i|))} $$
  • FastGELU vs GELU图像对比如下:
  • 从图中可以看出:\(x\) 在 0 附近,以及 \(x \geq 0\) 时,FastGELU 和 GELU 的曲线几乎一致(未完全重合)

补充:GLU及其变换

  • GLU,Gated Linear Units,是一种利用门的思想实现的激活函数,该激活函数可以理解为对输入进行门控选择,一些维度的值可以通过门,一些则不可以,门一般是一个基础的非线性激活函数
  • 原始GLU形式如下:
    $$
    GLU = \sigma(\mathbf{W}_1\mathbf{x} + \mathbf{b}_1) \odot (\mathbf{W}_2\mathbf{x} + \mathbf{b}_2)
    $$
  • \(\sigma\) 可以替换成其他非线性激活函数
    • 注意整个公式中始终只有一个非线性激活函数,其他部分都是线性映射(线性激活函数)
  • \(\odot\) 表示矩阵按照元素相乘, \(W_1,W_2,b_1,b_2\) 是可学习的参数
  • 该激活函数非常特殊,首先使用两个权重矩阵对输入数据进行线性变换,然后通过sigmoid激活函数进行非线性变换。这种设计使得GLU在前馈传播过程中能够更好地捕捉输入数据的非线性特征,从而提高模型的表达能力和泛化能力
  • 原始论文GLU Variants Improve Transformer中也写作下面的形式(其中 \(W,V,b,c\) 是可学习的参数):
    $$
    GLU(\mathbf{x,W,V,b,c}) = \sigma(\mathbf{W}\mathbf{x} + \mathbf{b}) \odot (\mathbf{V}\mathbf{x} + \mathbf{c})
    $$
  • 去掉激活函数的版本也叫作Bilinear,写作
    $$
    Bilinear(\mathbf{x,W,V,b,c}) = (\mathbf{W}\mathbf{x} + \mathbf{b}) \odot (\mathbf{V}\mathbf{x} + \mathbf{c})
    $$
  • 其他相关形式
    $$
    ReGLU(x, W, V, b, c) = max(0, xW + b) \odot (xV + c) \\
    GEGLU(x, W, V, b, c) = GELU(xW + b) \odot (xV + c) \\
    SwiGLU(x, W, V, b, c, \beta) = Swish_\beta(xW + b) \odot (xV + c) \\
    $$

补充:FFN激活函数形式

  • FFN的ReLU激活函数形式
    $$
    \text{FFN}(x, W_1, W_2, b_1, b_2) = max(0, xW_1 + b_1)W_2 + b_2
    $$
  • 为了表示方便,也因为在一些文章中使用了简化,后续该形式会被简化成没有偏置项(bias)的形式:
    $$
    \text{FFN}_{ReLU}(x, W_1, W_2) = max(xW_1, 0)W_2
    $$

FFN各种激活函数形式

  • 常用FFN的激活函数改进有,GLU,Bilinear,ReGLU,GEGLU(GeGLU),SwiGLU等
    $$
    \begin{align}
    \text{FFN}_{GLU}(x, W, V, W_2) &= (\sigma(xW) \odot xV )W_2 \\
    \text{FFN}_{Bilinear}(x, W, V, W_2) &= (xW \odot xV )W_2 \\
    \text{FFN}_{ReGLU}(x, W, V, W_2) &= (max(0, xW) \odot xV )W_2 \\
    \text{FFN}_{GEGLU}(x, W, V, W_2) &= (GELU(xW) \odot xV )W_2 \\
    \text{FFN}_{SwiGLU}(x, W, V, W_2) &= (Swish_1(xW) \odot xV )W_2 \\
    \end{align}
    $$
  • 可以理解为 \(\mathbf{W}^G,\mathbf{W}^U\) 中包含了偏置项 \(\mathbf{b}\),有些文章/模型中则会将偏置项 \(\mathbf{b}\) 去掉
  • 最常用的是SwiGLU
  • 从形式上看,可以知道相对原始FFN激活函数形式,SwiGLU等改进增加了一个参数矩阵,为了保证原始参数数量不变,原始论文GLU Variants Improve Transformer中提出了一种方法,通过将矩阵设置为如下的大小来保证参数数量相等
    • 原始FFN层参数为(下面 \(d = d_{model}\) 是模型的隐藏层大小,注意,同一层的不同token是共享FFN的):
      $$
      W_1^{d\times d} + W_2^{d\times d}
      $$
    • 使用SwiGLU且对齐参数数量后
      $$
      W^{r\times d} + V^{d\times r} + W_{2}^{r\times d}
      $$
    • 显然,当 \(r=\frac{2}{3}d\) 时,使用 SwiGLU 前后FFN层参数数量相同,都等于 \(2d^2\)
  • 一个疑问:原始的SwiGLU函数会引入两个参数矩阵 \(W,V\),原始的FFN包含两个参数矩阵 \(W_1, W_2\),为什么两者结合以后只剩下 \(\text{FFN}_{SwiGLU}\) 只剩三个参数 \(W,V,W_2\) 呢?
    • 回答:因为两个线性矩阵相乘,可以合并为 \(W = WV\),虽然还叫做 \(W\),但实际上是多了一个矩阵乘进去的,线上训练时也只需要训练这一个矩阵即可

Transformer总结

  • Transformer是一个特征提取能力非常强(超越LSTM)的特征提取器
  • 一些讨论
    • Transformer与CNN没关系,但是Transformer中使用多个 Scaled Dot-Product Attention 来最后拼接的方法(Multi-Head Attention), 就是CNN的多个卷积核的思想
    • Transformer论文原文中提到的残差结构也来源于CNN
    • 无法对位置信息进行很好地建模,这是硬伤。尽管可以引入Position Embedding,但我认为这只是一个缓解方案,并没有根本解决问题。举个例子,用这种纯Attention机制训练一个文本分类模型或者是机器翻译模型,效果应该都还不错,但是用来训练一个序列标注模型(分词、实体识别等),效果就不怎么好了。那为什么在机器翻译任务上好?我觉得原因是机器翻译这个任务并不特别强调语序,因此Position Embedding 所带来的位置信息已经足够了,此外翻译任务的评测指标BLEU也并不特别强调语序
    • Attention如果作为一个和CNN,RNN平级的组件来使用,可能会集成到各自的优点, 而不是”口气”很大的 “Attention is All You Need”

附录:关于 Dropout

  • 在 Transformer 原始论文中,在每个 sub-layer 的输出上使用了 Dropout,即 输出被加到 sub-layer 的输入和 归一化前进行 Dropout,称为 Residual Dropout
  • 同时,原始论文还在 Embedding 和 位置编码的和 上使用了 Dropout(包括 Encoder 和 Decoder)
  • \(P_\text{drop} = 0.1\)

附录:熵、损失和概率的关系图

  • 关键词:entropy curve;loss curve;熵和概率;概率和熵;熵和损失;损失和熵;损失和概率;概率和损失;曲线图;

  • 展示三者关系的代码如下(正确类别分配指定概率(横轴),其余均匀分配剩余概率):

    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
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    import numpy as np
    import matplotlib.pyplot as plt

    plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]

    def entropy(prob_dist):
    """计算概率分布的熵"""
    # 避免log(0)的情况
    prob_dist = np.clip(prob_dist, 1e-10, 1.0)
    return -np.sum(prob_dist * np.log(prob_dist))

    def cross_entropy(true_dist, pred_dist):
    """计算两个概率分布之间的交叉熵"""
    # 避免log(0)的情况
    pred_dist = np.clip(pred_dist, 1e-10, 1.0)
    return -np.sum(true_dist * np.log(pred_dist))

    num_classes = 100000 # 分类问题数

    # 创建一个真实分布(one-hot编码,表示第500类为正确类别)
    true_dist = np.zeros(num_classes)
    true_dist[499] = 1.0 # 索引从0开始,第500类

    # 计算真实分布的熵(应该为0,因为是确定的)
    true_entropy = entropy(true_dist)
    print(f"真实分布的熵: {true_entropy:.6f}")

    # 生成一系列预测分布,从非常不准确到非常准确
    num_steps = 100
    accuracies = np.linspace(0.01, 0.99, num_steps)
    entropies = []
    cross_entropies = []

    for acc in accuracies:
    # 创建预测分布:正确类别分配acc的概率,其余均匀分配剩余概率
    pred_dist = np.ones(num_classes) * ((1.0 - acc) / (num_classes - 1))
    pred_dist[499] = acc

    # 计算熵和交叉熵
    entropies.append(entropy(pred_dist))
    cross_entropies.append(cross_entropy(true_dist, pred_dist))

    # 绘制结果
    plt.figure(figsize=(12, 6))

    plt.subplot(1, 2, 1)
    plt.plot(accuracies, entropies, 'b-')
    plt.title('预测分布的熵随准确率的变化')
    plt.xlabel('正确类别的概率')
    plt.ylabel('熵')
    plt.grid(True, alpha=0.3)

    plt.subplot(1, 2, 2)
    plt.plot(accuracies, cross_entropies, 'r-')
    plt.title('交叉熵损失随准确率的变化')
    plt.xlabel('正确类别的概率')
    plt.ylabel('交叉熵损失')
    plt.grid(True, alpha=0.3)

    plt.tight_layout()
    plt.show()

    print("\nExample:")
    example_acc = 0.8
    pred_dist = np.ones(num_classes) * ((1.0 - example_acc) / (num_classes - 1))
    pred_dist[499] = example_acc

    print(f"当正确类别的概率为 {example_acc} 时:")
    print(f"预测分布的熵: {entropy(pred_dist):.6f}")
    print(f"交叉熵损失: {cross_entropy(true_dist, pred_dist):.6f}")
  • 当类别为 1000 时,图像如下

  • 当类别为 10000 时,图像如下

  • 当类别为 100000 时,图像如下

  • 当类别为 150000 时,图像如下

CA——COEC特征

  • 参考: http://d0evi1.com/positionbias/
  • COEC(Click on Expected Click):点击与期望点击的比值

Background

  • 在评价一个广告的质量好坏时,单纯使用广告的点击率作为指标是不可行的
  • 广告的点击率与广告的曝光位置相关【包括slot或position引起的问题】
    • 从position上来讲,越靠前的广告越容易被点击

COEC

  • 为了抵消广告曝光位置对广告的点击率的影响,我们引入期望点击
  • 在计算商家的点击率时,使用点击与期望点击的比值作为商家点击率质量的衡量指标
    $$
    \begin{align}
    COEC = \frac{\sum_{i=1}^N isClick_i}{\sum_{i=1}^N expCTR_i}
    \end{align}
    $$
    • \(isClick_i\) 为0或1,表示真实点击情况
    • \(expCTR_i\) 为广告真实曝光位置的期望点击率,不同slot和position对应的期望点击率不同

COEC特征

  • 在广告相关指标预估模型中,使用COEC作为广告的特征可提升模型的效果
  • 由于COEC的分母上考虑了位置因素,使得COEC更能真实的反应广告实际点击率的真实质量

NLP——UNILM论文阅读笔记

本文介绍了MSRA今年的一篇文章: UNILM: Unified Language Model Pre-training for Natural Language Understanding and Generation


UNILM 基于三个预训练目标

  • Unidirectional LM:
  • Bidirectional LM:
  • Sequence2Sequence LM:

贡献

和 BERT 对比

  • BERT 是双向模型,所以自然语言的生成(NLG)任务上不适用
  • UNILM 采用三种(无监督的)LM目标,其中的 Sequence2Sequence LM 能够解决文本生成问题

其他一些优点

  • 仅使用一个 LM (由 Transformer 构成), 在三个不同的预训练任务上共享参数, 泛化能力强
    • 参数共享: 不需要在不同的语言模型(对应不同的预训练任务)上使用不同的模型参数
    • 泛化能力强: 多个训练目标同时优化模型,使得模型能够避开因为训练目标引起的过拟合问题

模型结构介绍

结构图

  • 输入向量为 \(\vec{x} = (x_1, x_2,,,x_{|x|}\))
  • 输入向量表征与 BERT 相同,由 Embedding 层(三个 Embedding 层之和, 和 BERT 一样):
    • Token Embedding,WordPiece
    • Position Embedding
    • Segment Embedding,由于 UNILM 会使用多个 LM 任务训练,所以 Segment Embedding 在模型中也扮演着 LM 标识的作用(对不同的 LM 目标使用不同的Segment Embeddings)
  • 主要网络(Backbone Network)为多层Transformer:
    • 每两个Transformer Blocks之间的Self-Attention Masks: For Each Transformer Block, 使用多头 Self-Attention 来聚合之前层出现的输入向量.(这里的 Self-Attention 使用了)
    • Transformer 之间的 Self-Attention 机制决定了模型什么语言模型(任务)
    • 如图所示:
      • Bidirectional LM 对应的 Self-Attention Masks 为全0的矩阵,表示所有的 Attention 都不屏蔽 (attend to all tokens)
      • Left-to-Right LM 对应的 Self-Attention Masks 为拼屏蔽右上三角的矩阵(左下三角全为0)
      • Right-to-Left LM 对应的 Self-Attention Masks 为拼屏蔽左下三角的矩阵(右上三角全为0)
      • Seq-to-Seq LM 对应的 Self-Attention 为
        • \(S_1\) - \(S_1\) 为全0(0表示开放), 对应为Bidirectional
        • \(S_1\) - \(S_2\) 为负无穷(负无穷表示屏蔽)
        • \(S_2\) - \(S_1\) 为全0
        • \(S_2\) - \(S_2\) 为屏蔽右上三角的矩阵(这里与Left-to-Right的情况相同)

按照结构图分析数据流

  • 对于一组输入向量 \(\{\vec{x}\}_{i=1}^{|x|} \),初始编码为 \(\mathbf{H}^0 = [\vec{x}_1, \vec{x}_2,,,\vec{x}_{|\vec{x}|}]\)
  • 第一层后编码为上下文表征 \(\mathbf{H}^1 = [\vec{h}_1^1, \vec{h}_2^1,,,\vec{h}_{|\vec{x}|}^1]\)
  • 第 \(l\) 层后编码为上下文表征 \(\mathbf{H}^l = [\vec{h}_1^l, \vec{h}_2^l,,,\vec{h}_{|\vec{x}|}^l]\)
  • 每一层的 Transformer Block, 使用 multiple self-attention heads去聚合上一层的输出: L-layer的 Transformer对应的数学表达式为 \(\mathbf{H}^l = Transformer_l(\mathbf{H}^{l-1}), l \in [1, L]\)
  • Self-Attention Head \(\mathbf{A}_l\) 的详细计算公式如下:
    $$
    \begin{align}
    \mathbf{H} = \mathbf{H}^{l-1}\mathbf{W}_l^Q \\
    \mathbf{K} = \mathbf{H}^{l-1}\mathbf{W}_l^K \\
    \mathbf{V} = \mathbf{H}^{l-1}\mathbf{W}_l^V \\
    \mathbf{A} = softmax(\frac{\mathbf{Q}\mathbf{K}^T}{\sqrt{d_k}} + \mathbf{M})\mathbf{V}_l \\
    where, \quad \mathbf{M}_{ij} = 0 \ or \ -\infty
    \end{align}
    $$
    • \(\mathbf{M}\) 中的值
      • \(-\infty\) 表示屏蔽Attention (prevent from Attention)
      • 0 表示允许 Attention (allow to Attention)
1…394041…67
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

662 posts
53 tags
GitHub E-Mail
© 2026 Joe Zhou
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4