一期2.自然语言处理基础

第一章 自然语言处理入门

1 自然语言处理入门

学习目标¶

了解什么是自然语言处理.
了解自然语言处理的发展简史.
了解自然语言处理的应用场景.
了解本教程中的自然语言处理.

1 什么是自然语言处理¶

自然语言处理(Natural Language Processing, 简称NLP)是计算机科学与语言学中关注于计算机与人类语言间转换的领域.

2 自然语言处理的发展简史¶





3 自然语言处理的应用场景¶

语音助手
机器翻译
搜索引擎
智能问答
...

3.1 语音助手¶

科大讯飞语音识别技术访谈!

3.2 机器翻译¶

CCTV上的机器翻译系统, 让世界聊得来!

第二章 文本预处理

1 认识文本预处理

学习目标¶

了解文本预处理相关内容

1 文本预处理及其作用¶

文本语料在输送给模型前一般需要一系列的预处理工作, 才能符合模型输入的要求, 如: 将文本转化成模型需要的张量, 规范张量的尺寸等, 而且科学的文本预处理环节还将有效指导模型超参数的选择, 提升模型的评估指标.

2 文本预处理中包含的主要环节¶

文本处理的基本方法
文本张量表示方法
文本语料的数据分析
文本特征处理
数据增强方法

2.1 文本处理的基本方法¶

分词
词性标注
命名实体识别

2.2 文本张量表示方法¶

one-hot编码
Word2vec
Word Embedding

2.3 文本语料的数据分析¶

标签数量分布
句子长度分布
词频统计与关键词词云

2.4 数据增强方法¶

回译数据增强法

2.5 重要说明¶

在实际生产应用中, 我们最常使用的两种语言是中文和英文,因此文本预处理部分的内容都将针对这两种语言进行讲解.


2 文本处理的基本方法

学习目标¶

了解什么是分词, 词性标注, 命名实体识别及其它们的作用.
掌握分词, 词性标注, 命名实体识别流行工具的使用方法.

1 什么是分词¶

分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。我们知道,在英文的行文中,单词之间是以空格作为自然分界符的,而中文只是字、句和段能通过明显的分界符来简单划界,唯独词没有一个形式上的分界符, 分词过程就是找到这样分界符的过程.
举个例子:

无线电法国别研究

['无线电法', '国别', '研究']
  • 分词的作用:

    • 词作为语言语义理解的最小单元, 是人类理解文本语言的基础. 因此也是AI解决NLP领域高阶任务, 如自动问答, 机器翻译, 文本生成的重要基础环节.
  • 流行中文分词工具jieba:

    • 愿景: “结巴”中文分词, 做最好的 Python 中文分词组件.
  • jieba的特性:

    • 支持多种分词模式
      • 精确模式
      • 全模式
      • 搜索引擎模式
    • 支持中文繁体分词
    • 支持用户自定义词典
  • jieba的安装:

  pip install jieba
  • jieba的使用:
    精确模式分词:
    试图将句子最精确地切开,适合文本分析.
import jieba
content = "无线电法国别研究"
jieba.cut(content, cut_all=False)  # cut_all默认为False

# 将返回一个生成器对象
<generator object Tokenizer.cut at 0x7f065c19e318>

# 若需直接返回列表内容, 使用jieba.lcut即可
jieba.lcut(content, cut_all=False)
['无线电', '法国', '别', '研究']

全模式分词:
把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能消除歧义。

# 若需直接返回列表内容, 使用jieba.lcut即可
jieba.lcut(content, cut_all=True)

['无线', '无线电', '法国', '国别', '研究']

搜索引擎模式分词:
在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。

import jieba
content = "无线电法国别研究"
jieba.cut_for_search(content)

# 将返回一个生成器对象
<generator object Tokenizer.cut at 0x7f065c19e318>

# 若需直接返回列表内容, 使用jieba.lcut_for_search即可
jieba.lcut_for_search(content)
['无线', '无线电', '法国', '别', '研究']

# 对'无线电'等较长词汇都进行了再次分词.

中文繁体分词:
针对中国香港, 台湾地区的繁体文本进行分词。

import jieba
content = "煩惱即是菩提,我暫且不提"
jieba.lcut(content)
['煩惱', '即', '是', '菩提', ',', '我', '暫且', '不', '提']

使用用户自定义词典:
添加自定义词典后, jieba能够准确识别词典中出现的词汇,提升整体的识别准确率。
词典格式: 每一行分三部分:词语、词频(可省略)、词性(可省略),用空格隔开,顺序不可颠倒。
词典样式如下, 具体词性含义请参照
[7 jieba词性对照表](7 jieba词性对照表.html)
, 将该词典存为userdict.txt, 方便之后加载使用。

云计算 5 n
李小福 2 nr
easy_install 3 eng
好用 300
韩玉赏鉴 3 nz
八一双鹿 3 nz
import jieba
jieba.lcut("八一双鹿更名为八一南昌篮球队!")
# 没有使用用户自定义词典前的结果:
['八', '一双', '鹿', '更名', '为', '八一', '南昌', '篮球队', '!']


jieba.load_userdict("./userdict.txt")
# 使用了用户自定义词典后的结果:
['八一双鹿', '更名', '为', '八一', '南昌', '篮球队', '!']

2 什么是命名实体识别¶

命名实体: 通常我们将人名, 地名, 机构名等专有名词统称命名实体. 如: 周杰伦, 黑山县, 孔子学院, 24辊方钢矫直机.
顾名思义, 命名实体识别(Named Entity Recognition,简称NER)就是识别出一段文本中可能存在的命名实体.
举个例子:

鲁迅, 浙江绍兴人, 五四新文化运动的重要参与者, 代表作朝花夕拾.

==>

鲁迅(人名) / 浙江绍兴(地名)人 / 五四新文化运动(专有名词) / 重要参与者 / 代表作 / 朝花夕拾(专有名词)

命名实体识别的作用:
同词汇一样, 命名实体也是人类理解文本的基础单元, 因此也是AI解决NLP领域高阶任务的重要基础环节.

3 什么是词性标注¶

词性: 语言中对词的一种分类方法,以语法特征为主要依据、兼顾词汇意义对词进行划分的结果, 常见的词性有14种, 如: 名词, 动词, 形容词等.
顾名思义, 词性标注(Part-Of-Speech tagging, 简称POS)就是标注出一段文本中每个词汇的词性.
举个例子:

我爱自然语言处理

==>

我/rr, 爱/v, 自然语言/n, 处理/vn

rr: 人称代词
v: 动词
n: 名词
vn: 动名词

词性标注的作用:
词性标注以分词为基础, 是对文本语言的另一个角度的理解, 因此也常常成为AI解决NLP领域高阶任务的重要基础环节.
使用jieba进行中文词性标注:

import jieba.posseg as pseg
pseg.lcut("我爱北京天安门") 
[pair('我', 'r'), pair('爱', 'v'), pair('北京', 'ns'), pair('天安门', 'ns')]

# 结果返回一个装有pair元组的列表, 每个pair元组中分别是词汇及其对应的词性, 具体词性含义请参照[附录: jieba词性对照表]()

4 小结¶

  • 学习了什么是分词:

    • 分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。我们知道,在英文的行文中,单词之间是以空格作为自然分界符的,而中文只是字、句和段能通过明显的分界符来简单划界,唯独词没有一个形式上的分界符, 分词过程就是找到这样分界符的过程.
  • 学习了分词的作用:

    • 词作为语言语义理解的最小单元, 是人类理解文本语言的基础. 因此也是AI解决NLP领域高阶任务, 如自动问答, 机器翻译, 文本生成的重要基础环节.
  • 学习了流行中文分词工具jieba:

    • 支持多种分词模式: 精确模式, 全模式, 搜索引擎模式
    • 支持中文繁体分词
    • 支持用户自定义词典
  • 学习了jieba工具的安装和分词使用.

  • 学习了什么是命名实体识别:

    • 命名实体: 通常我们将人名, 地名, 机构名等专有名词统称命名实体. 如: 周杰伦, 黑山县, 孔子学院, 24辊方钢矫直机.
    • 顾名思义, 命名实体识别(Named Entity Recognition,简称NER)就是识别出一段文本中可能存在的命名实体.
  • 命名实体识别的作用:

    • 同词汇一样, 命名实体也是人类理解文本的基础单元, 因此也是AI解决NLP领域高阶任务的重要基础环节.
  • 学习了什么是词性标注:

    • 词性: 语言中对词的一种分类方法,以语法特征为主要依据、兼顾词汇意义对词进行划分的结果, 常见的词性有14种, 如: 名词, 动词, 形容词等.
    • 顾名思义, 词性标注(Part-Of-Speech tagging, 简称POS)就是标注出一段文本中每个词汇的词性.
  • 学习了词性标注的作用:

    • 词性标注以分词为基础, 是对文本语言的另一个角度的理解, 因此也常常成为AI解决NLP领域高阶任务的重要基础环节.
  • 学习了使用jieba进行词性标注.


3 文本张量表示方法

学习目标¶

了解什么是文本张量表示及其作用.
掌握文本张量表示的几种方法及其实现.

1 文本张量表示¶

将一段文本使用张量进行表示,其中一般将词汇为表示成向量,称作词向量,再由各个词向量按顺序组成矩阵形成文本表示.
举个例子:

["人生", "该", "如何", "起头"]

==>

# 每个词对应矩阵中的一个向量
[[1.32, 4,32, 0,32, 5.2],
 [3.1, 5.43, 0.34, 3.2],
 [3.21, 5.32, 2, 4.32],
 [2.54, 7.32, 5.12, 9.54]]
  • 文本张量表示的作用:
  • 将文本表示成张量(矩阵)形式,能够使语言文本可以作为计算机处理程序的输入,进行接下来一系列的解析工作.
  • 文本张量表示的方法:
    • one-hot编码
    • Word2vec
    • Word Embedding

2 one-hot词向量表示¶

又称独热编码,将每个词表示成具有n个元素的向量,这个词向量中只有一个元素是1,其他元素都是0,不同词汇元素为0的位置不同,其中n的大小是整个语料中不同词汇的总数.
举个例子:

["改变", "要", "如何", "起手"]`
==>

[[1, 0, 0, 0],
 [0, 1, 0, 0],
 [0, 0, 1, 0],
 [0, 0, 0, 1]]

onehot编码实现:
进行onehot编码:

# 导入用于对象保存与加载的joblib
import joblib
# 导入keras中的词汇映射器Tokenizer
from keras.preprocessing.text import Tokenizer
# 假定vocab为语料集所有不同词汇集合
vocab = {"周杰伦", "陈奕迅", "王力宏", "李宗盛", "吴亦凡", "鹿晗"}
# 实例化一个词汇映射器对象
t = Tokenizer(num_words=None, char_level=False)
# 使用映射器拟合现有文本数据
t.fit_on_texts(vocab)

for token in vocab:
    zero_list = [0]*len(vocab)
    # 使用映射器转化现有文本数据, 每个词汇对应从1开始的自然数
    # 返回样式如: [[2]], 取出其中的数字需要使用[0][0]
    token_index = t.texts_to_sequences([token])[0][0] - 1
    zero_list[token_index] = 1
    print(token, "的one-hot编码为:", zero_list)

# 使用joblib工具保存映射器, 以便之后使用
tokenizer_path = "./Tokenizer"
joblib.dump(t, tokenizer_path)

输出效果:

鹿晗 的one-hot编码为: [1, 0, 0, 0, 0, 0]
王力宏 的one-hot编码为: [0, 1, 0, 0, 0, 0]
李宗盛 的one-hot编码为: [0, 0, 1, 0, 0, 0]
陈奕迅 的one-hot编码为: [0, 0, 0, 1, 0, 0]
周杰伦 的one-hot编码为: [0, 0, 0, 0, 1, 0]
吴亦凡 的one-hot编码为: [0, 0, 0, 0, 0, 1]

# 同时在当前目录生成Tokenizer文件, 以便之后使用

onehot编码器的使用:

# 加载之前保存的Tokenizer, 实例化一个t对象
t = joblib.load(tokenizer_path)

# 编码token为"李宗盛"
token = "李宗盛"
# 使用t获得token_index
token_index = t.texts_to_sequences([token])[0][0] - 1
# 初始化一个zero_list
zero_list = [0]*len(vocab)
# 令zero_List的对应索引为1
zero_list[token_index] = 1
print(token, "的one-hot编码为:", zero_list) 

输出效果:

李宗盛 的one-hot编码为: [1, 0, 0, 0, 0, 0]

one-hot编码的优劣势:
优势: 操作简单, 容易理解.
劣势: 完全割裂了词与词之间的联系, 而且在大语料集下, 每个向量的长度过大, 占据大量内存.
正因为one-hot编码明显的劣势, 这种编码方式被应用的地方越来越少, 取而代之的是接下来我们要学习的稠密向量的表示方法word2vec和word embedding.

3 word2vec模型¶

3.1 模型介绍¶

word2vec是一种流行的将词汇表示成向量的无监督训练方法, 该过程将构建神经网络模型, 将网络参数作为词汇的向量表示, 它包含CBOW和skipgram两种训练模式.
CBOW(Continuous bag of words)模式:
给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用上下文词汇预测目标词汇.
avatar
分析:
图中窗口大小为9, 使用前后4个词汇对目标词汇进行预测.
CBOW模式下的word2vec过程说明:
假设我们给定的训练语料只有一句话: Hope can set you free (愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope can set,因为是CBOW模式,所以将使用Hope和set作为输入,can作为输出,在模型训练时, Hope,can,set等词汇都使用它们的one-hot编码. 如图所示: 每个one-hot编码的单词与各自的变换矩阵(即参数矩阵3x5, 这里的3是指最后得到的词向量维度)相乘之后再相加, 得到上下文表示矩阵(3x1).
avatar
接着, 将上下文表示矩阵与变换矩阵(参数矩阵5x3, 所有的变换矩阵共享参数)相乘, 得到5x1的结果矩阵, 它将与我们真正的目标矩阵即can的one-hot编码矩阵(5x1)进行损失的计算, 然后更新网络参数完成一次模型迭代.
avatar
最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵(3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的3x1的矩阵就是该词汇的word2vec张量表示.
skipgram模式:
给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用目标词汇预测上下文词汇.
avatar
分析:
图中窗口大小为9, 使用目标词汇对前后四个词汇进行预测.
skipgram模式下的word2vec过程说明:
假设我们给定的训练语料只有一句话: Hope can set you free (愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope can set,因为是skipgram模式,所以将使用can作为输入
,Hope和set作为输出,在模型训练时, Hope,can,set等词汇都使用它们的one-hot编码. 如图所示: 将can的one-hot编码与变换矩阵(即参数矩阵3x5, 这里的3是指最后得到的词向量维度)相乘, 得到目标词汇表示矩阵(3x1).
接着, 将目标词汇表示矩阵与多个变换矩阵(参数矩阵5x3)相乘, 得到多个5x1的结果矩阵, 它将与我们Hope和set对应的one-hot编码矩阵(5x1)进行损失的计算, 然后更新网络参数完成一次模
型迭代.
avatar
最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵即参数矩阵(3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的3x1的矩阵就是该词汇的word2vec张量表示.

3.2 word2vec的训练和使用¶

第一步: 获取训练数据
第二步: 训练词向量
第三步: 模型超参数设定
第四步: 模型效果检验
第五步: 模型的保存与重加载

1 获取训练数据¶

数据来源:http://mattmahoney.net/dc/enwik9.zip
在这里, 我们将研究英语维基百科的部分网页信息, 它的大小在300M左右。这些语料已经被准备好, 我们可以通过Matt Mahoney的网站下载。
注意:原始数据集enwik9.zip, 解压后数据为enwik9, 预处理后的数据为fil9
查看原始数据:

$ head -10 data/enwik9

# 原始数据将输出很多包含XML/HTML格式的内容, 这些内容并不是我们需要的
<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.3/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.3/ http://www.mediawiki.org/xml/export-0.3.xsd" version="0.3" xml:lang="en">
  <siteinfo>
    <sitename>Wikipedia</sitename>
    <base>http://en.wikipedia.org/wiki/Main_Page</base>
    <generator>MediaWiki 1.6alpha</generator>
    <case>first-letter</case>
      <namespaces>
      <namespace key="-2">Media</namespace>
      <namespace key="-1">Special</namespace>
      <namespace key="0" />

原始数据处理:

# 使用wikifil.pl文件处理脚本来清除XML/HTML格式的内容
# perl wikifil.pl data/enwik9 > data/fil9 #该命令已经执行

查看预处理后的数据:

# 查看前80个字符
head -c 80 data/fil9

# 输出结果为由空格分割的单词
 anarchism originated as a term of abuse first used against early working class

2 训练词向量¶

# 代码运行在python解释器中
# 导入fasttext
>>> import fasttext
# 使用fasttext的train_unsupervised(无监督训练方法)进行词向量的训练
# 它的参数是数据集的持久化文件路径'data/fil9'

# 注意,该行代码执行耗时很长
>>> model1 = fasttext.train_unsupervised('data/fil9') 

# 可以使用以下代码加载已经训练好的模型
>>> model = fasttext.load_model("data/fil9.bin")


# 有效训练词汇量为124M, 共218316个单词
Read 124M words
Number of words:  218316
Number of labels: 0
Progress: 100.0% words/sec/thread:   53996 lr:  0.000000 loss:  0.734999 ETA:   0h 0m

3 查看单词对应的词向量¶

# 通过get_word_vector方法来获得指定词汇的词向量
>>> model.get_word_vector("the")

array([-0.03087516,  0.09221972,  0.17660329,  0.17308897,  0.12863874,
        0.13912526, -0.09851588,  0.00739991,  0.37038437, -0.00845221,
        ...
       -0.21184735, -0.05048715, -0.34571868,  0.23765688,  0.23726143],
      dtype=float32)

4 模型超参数设定¶

# 在训练词向量过程中, 我们可以设定很多常用超参数来调节我们的模型效果, 如:
# 无监督训练模式: 'skipgram' 或者 'cbow', 默认为'skipgram', 在实践中,skipgram模式在利用子词方面比cbow更好.
# 词嵌入维度dim: 默认为100, 但随着语料库的增大, 词嵌入的维度往往也要更大.
# 数据循环次数epoch: 默认为5, 但当你的数据集足够大, 可能不需要那么多次.
# 学习率lr: 默认为0.05, 根据经验, 建议选择[0.01,1]范围内.
# 使用的线程数thread: 默认为12个线程, 一般建议和你的cpu核数相同.

>>> model = fasttext.train_unsupervised('data/fil9', "cbow", dim=300, epoch=1, lr=0.1, thread=8)

Read 124M words
Number of words:  218316
Number of labels: 0
Progress: 100.0% words/sec/thread:   49523 lr:  0.000000 avg.loss:  1.777205 ETA:   0h 0m 0s

5 模型效果检验¶

# 检查单词向量质量的一种简单方法就是查看其邻近单词, 通过我们主观来判断这些邻近单词是否与目标单词相关来粗略评定模型效果好坏.

# 查找"运动"的邻近单词, 我们可以发现"体育网", "运动汽车", "运动服"等. 
>>> model.get_nearest_neighbors('sports')

[(0.8414610624313354, 'sportsnet'), (0.8134572505950928, 'sport'), (0.8100415468215942, 'sportscars'), (0.8021156787872314, 'sportsground'), (0.7889881134033203, 'sportswomen'), (0.7863013744354248, 'sportsplex'), (0.7786710262298584, 'sporty'), (0.7696356177330017, 'sportscar'), (0.7619683146476746, 'sportswear'), (0.7600985765457153, 'sportin')]


# 查找"音乐"的邻近单词, 我们可以发现与音乐有关的词汇.
>>> model.get_nearest_neighbors('music')

[(0.8908010125160217, 'emusic'), (0.8464668393135071, 'musicmoz'), (0.8444250822067261, 'musics'), (0.8113634586334229, 'allmusic'), (0.8106718063354492, 'musices'), (0.8049437999725342, 'musicam'), (0.8004694581031799, 'musicom'), (0.7952923774719238, 'muchmusic'), (0.7852965593338013, 'musicweb'), (0.7767147421836853, 'musico')]

# 查找"小狗"的邻近单词, 我们可以发现与小狗有关的词汇.
>>> model.get_nearest_neighbors('dog')

[(0.8456876873970032, 'catdog'), (0.7480780482292175, 'dogcow'), (0.7289096117019653, 'sleddog'), (0.7269964218139648, 'hotdog'), (0.7114801406860352, 'sheepdog'), (0.6947550773620605, 'dogo'), (0.6897546648979187, 'bodog'), (0.6621081829071045, 'maddog'), (0.6605004072189331, 'dogs'), (0.6398137211799622, 'dogpile')]

6 模型的保存与重加载¶

# 使用save_model保存模型
>>> model.save_model("fil9.bin")

# 使用fasttext.load_model加载模型
>>> model = fasttext.load_model("fil9.bin")
>>> model.get_word_vector("the")

array([-0.03087516,  0.09221972,  0.17660329,  0.17308897,  0.12863874,
        0.13912526, -0.09851588,  0.00739991,  0.37038437, -0.00845221,
        ...
       -0.21184735, -0.05048715, -0.34571868,  0.23765688,  0.23726143],
      dtype=float32)

4 词嵌入word embedding介绍¶

通过一定的方式将词汇映射到指定维度(一般是更高维度)的空间.
广义的word embedding包括所有密集词汇向量的表示方法,如之前学习的word2vec, 即可认为是word embedding的一种.
狭义的word embedding是指在神经网络中加入的embedding层, 对整个网络进行训练的同时产生的embedding矩阵(embedding层的参数), 这个embedding矩阵就是训练过程中所有输入词汇的向量表示组成的矩阵.

5 小结¶

学习了什么是文本张量表示:

将一段文本使用张量进行表示,其中一般将词汇为表示成向量,称作词向量,再由各个词向量按顺序组成矩阵形成文本表示.

学习了文本张量表示的作用:

将文本表示成张量(矩阵)形式,能够使语言文本可以作为计算机处理程序的输入,进行接下来一系列的解析工作.

学习了文本张量表示的方法:

one-hot编码

Word2vec

Word Embedding

什么是one-hot词向量表示:

又称独热编码,将每个词表示成具有n个元素的向量,这个词向量中只有一个元素是1,其他元素都是0,不同词汇元素为0的位置不同,其中n的大小是整个语料中不同词汇的总数.

学习了onehot编码实现.

学习了one-hot编码的优劣势:

优势:操作简单,容易理解.

劣势:完全割裂了词与词之间的联系,而且在大语料集下,每个向量的长度过大,占据大量内存.

学习了什么是word2vec:

是一种流行的将词汇表示成向量的无监督训练方法, 该过程将构建神经网络模型, 将网络参数作为词汇的向量表示, 它包含CBOW和skipgram两种训练模式.

学习了CBOW(Continuous bag of words)模式:

给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用上下文词汇预测目标词汇.

学习了CBOW模式下的word2vec过程说明:

假设我们给定的训练语料只有一句话: Hope can set you free (愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope you set,因为是CBOW模式,所以将使用Hope和set作为输入,you作为输出,在模型训练时, Hope,set,you等词汇都使用它们的one-hot编码. 如图所示: 每个one-hot编码的单词与各自的变换矩阵(即参数矩阵3x5, 这里的3是指最后得到的词向量维度)相乘之后再相加, 得到上下文表示矩阵(3x1).

接着, 将上下文表示矩阵与变换矩阵(参数矩阵5x3, 所有的变换矩阵共享参数)相乘, 得到5x1的结果矩阵, 它将与我们真正的目标矩阵即you的one-hot编码矩阵(5x1)进行损失的计算, 然后更新网络参数完成一次模型迭代.

最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵(3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的3x1的矩阵就是该词汇的word2vec张量表示.

学习了skipgram模式:

给定一段用于训练的文本语料, 再选定某段长度(窗口)作为研究对象, 使用目标词汇预测上下文词汇.

学习了skipgram模式下的word2vec过程说明:

假设我们给定的训练语料只有一句话: Hope can set you free (愿你自由成长),窗口大小为3,因此模型的第一个训练样本来自Hope you set,因为是skipgram模式,所以将使用you作为输入 ,hope和set作为输出,在模型训练时, Hope,set,you等词汇都使用它们的one-hot编码. 如图所示: 将you的one-hot编码与变换矩阵(即参数矩阵3x5, 这里的3是指最后得到的词向量维度)相乘, 得到目标词汇表示矩阵(3x1).

接着, 将目标词汇表示矩阵与多个变换矩阵(参数矩阵5x3)相乘, 得到多个5x1的结果矩阵, 它将与我们hope和set对应的one-hot编码矩阵(5x1)进行损失的计算, 然后更新网络参数完成一次模 型迭代.

最后窗口按序向后移动,重新更新参数,直到所有语料被遍历完成,得到最终的变换矩阵即参数矩阵(3x5),这个变换矩阵与每个词汇的one-hot编码(5x1)相乘,得到的3x1的矩阵就是该词汇的word2vec张量表示.

学习了使用fasttext工具实现word2vec的训练和使用:

第一步: 获取训练数据

第二步: 训练词向量

第三步: 模型超参数设定

第四步: 模型效果检验

第五步: 模型的保存与重加载

学习了什么是word embedding(词嵌入):

通过一定的方式将词汇映射到指定维度(一般是更高维度)的空间.

广义的word embedding包括所有密集词汇向量的表示方法,如之前学习的word2vec, 即可认为是word embedding的一种.

狭义的word embedding是指在神经网络中加入的embedding层, 对整个网络进行训练的同时产生的embedding矩阵(embedding层的参数), 这个embedding矩阵就是训练过程中所有输入词汇的向量表示组成的矩阵.


4 文本数据分析

学习目标¶

了解文本数据分析的作用.
掌握常用的几种文本数据分析方法.

1 文件数据分析介绍¶

文本数据分析的作用:
文本数据分析能够有效帮助我们理解数据语料, 快速检查出语料可能存在的问题, 并指导之后模型训练过程中一些超参数的选择.
常用的几种文本数据分析方法:
标签数量分布
句子长度分布
词频统计与关键词词云

2 数据集说明¶

我们将基于真实的中文酒店评论语料来讲解常用的几种文本数据分析方法.
中文酒店评论语料:
属于二分类的中文情感分析语料, 该语料存放在"./cn_data"目录下.
其中train.tsv代表训练集, dev.tsv代表验证集, 二者数据样式相同.
train.tsv数据样式:

代码
train.tsv数据样式说明:
train.tsv中的数据内容共分为2列, 第一列数据代表具有感情色彩的评论文本; 第二列数据, 0或1, 代表每条文本数据是积极或者消极的评论, 0代表消极, 1代表积极.

3 获取标签数量分布¶

# 导入必备工具包
import seaborn as sns
import pandas as pd
import matplotlib.pyplot as plt
# 设置显示风格
plt.style.use('fivethirtyeight') 

# 分别读取训练tsv和验证tsv
train_data = pd.read_csv("train.tsv", sep="\t")
valid_data = pd.read_csv("dev.tsv", sep="\t")


# 获得训练数据标签数量分布
sns.countplot("label", data=train_data)
plt.title("train_data")
plt.show()


# 获取验证数据标签数量分布
sns.countplot("label", data=valid_data)
plt.title("valid_data")
plt.show()

训练集标签数量分布:
avatar
验证集标签数量分布:
avatar
分析:
在深度学习模型评估中, 我们一般使用ACC作为评估指标, 若想将ACC的基线定义在50%左右, 则需要我们的正负样本比例维持在1:1左右, 否则就要进行必要的数据增强或数据删减. 上图中训练和验证集正负样本都稍有不均衡, 可以进行一些数据增强.

4 获取句子长度分布¶

# 在训练数据中添加新的句子长度列, 每个元素的值都是对应的句子列的长度
train_data["sentence_length"] = list(map(lambda x: len(x), train_data["sentence"]))

# 绘制句子长度列的数量分布图
sns.countplot("sentence_length", data=train_data)
# 主要关注count长度分布的纵坐标, 不需要绘制横坐标, 横坐标范围通过dist图进行查看
plt.xticks([])
plt.show()

# 绘制dist长度分布图
sns.distplot(train_data["sentence_length"])

# 主要关注dist长度分布横坐标, 不需要绘制纵坐标
plt.yticks([])
plt.show()


# 在验证数据中添加新的句子长度列, 每个元素的值都是对应的句子列的长度
valid_data["sentence_length"] = list(map(lambda x: len(x), valid_data["sentence"]))

# 绘制句子长度列的数量分布图
sns.countplot("sentence_length", data=valid_data)

# 主要关注count长度分布的纵坐标, 不需要绘制横坐标, 横坐标范围通过dist图进行查看
plt.xticks([])
plt.show()

# 绘制dist长度分布图
sns.distplot(valid_data["sentence_length"])

# 主要关注dist长度分布横坐标, 不需要绘制纵坐标
plt.yticks([])
plt.show()

训练集句子长度分布:
avatar
avatar
验证集句子长度分布:
avatar
avatar
分析:
通过绘制句子长度分布图, 可以得知我们的语料中大部分句子长度的分布范围, 因为模型的输入要求为固定尺寸的张量,合理的长度范围对之后进行句子截断补齐(规范长度)起到关键的指导作用. 上图中大部分句子长度的范围大致为20-250之间.

5 获取正负样本长度散点分布¶

# 绘制训练集长度分布的散点图
sns.stripplot(y='sentence_length',x='label',data=train_data)
plt.show()

# 绘制验证集长度分布的散点图
sns.stripplot(y='sentence_length',x='label',data=valid_data)
plt.show()

训练集上正负样本的长度散点分布:
avatar
验证集上正负样本的长度散点分布:
avatar
分析:
通过查看正负样本长度散点图, 可以有效定位异常点的出现位置, 帮助我们更准确进行人工语料审查. 上图中在训练集正样本中出现了异常点, 它的句子长度近3500左右, 需要我们人工审查.

6 获取不同词汇总数统计¶

# 导入jieba用于分词
# 导入chain方法用于扁平化列表
import jieba
from itertools import chain

# 进行训练集的句子进行分词, 并统计出不同词汇的总数
train_vocab = set(chain(*map(lambda x: jieba.lcut(x), train_data["sentence"])))
print("训练集共包含不同词汇总数为:", len(train_vocab))

# 进行验证集的句子进行分词, 并统计出不同词汇的总数
valid_vocab = set(chain(*map(lambda x: jieba.lcut(x), valid_data["sentence"])))
print("训练集共包含不同词汇总数为:", len(valid_vocab))

输出效果:

训练集共包含不同词汇总数为: 12147
训练集共包含不同词汇总数为: 6857

7 小结¶

学习了文本数据分析的作用:
文本数据分析能够有效帮助我们理解数据语料, 快速检查出语料可能存在的问题, 并指导之后模型训练过程中一些超参数的选择.
学习了常用的几种文本数据分析方法:
标签数量分布
句子长度分布
词频统计
学习了基于真实的中文酒店评论语料进行几种文本数据分析方法.
获得训练集和验证集的标签数量分布
获取训练集和验证集的句子长度分布
获取训练集和验证集的正负样本长度散点分布
获得训练集与验证集不同词汇总数统计


5 jieba词性对照表

jieba词性对照表:

- a 形容词  
    - ad 副形词  
    - ag 形容词性语素  
    - an 名形词  
- b 区别词  
- c 连词  
- d 副词  
    - df   
    - dg 副语素  
- e 叹词  
- f 方位词  
- g 语素  
- h 前接成分  
- i 成语 
- j 简称略称  
- k 后接成分  
- l 习用语  
- m 数词  
    - mg 
    - mq 数量词  
- n 名词  
    - ng 名词性语素  
    - nr 人名  
    - nrfg  
    - nrt  
    - ns 地名  
    - nt 机构团体名  
    - nz 其他专名  
- o 拟声词  
- p 介词  
- q 量词  
- r 代词  
    - rg 代词性语素  
    - rr 人称代词  
    - rz 指示代词  
- s 处所词  
- t 时间词  
    - tg 时语素  
- u 助词  
    - ud 结构助词 得
    - ug 时态助词
    - uj 结构助词 的
    - ul 时态助词 了
    - uv 结构助词 地
    - uz 时态助词 着
- v 动词  
    - vd 副动词
    - vg 动词性语素  
    - vi 不及物动词  
    - vn 名动词  
    - vq 
- x 非语素词  
- y 语气词  
- z 状态词  
    - zg 

hanlp词性对照表:

【Proper Noun——NR,专有名词】

【Temporal Noun——NT,时间名词】

【Localizer——LC,定位词】如“内”,“左右”

【Pronoun——PN,代词】

【Determiner——DT,限定词】如“这”,“全体”

【Cardinal Number——CD,量词】

【Ordinal Number——OD,次序词】如“第三十一”

【Measure word——M,单位词】如“杯”

【Verb:VA,VC,VE,VV,动词】

【Adverb:AD,副词】如“近”,“极大”

【Preposition:P,介词】如“随着”

【Subordinating conjunctions:CS,从属连词】

【Conjuctions:CC,连词】如“和”

【Particle:DEC,DEG,DEV,DER,AS,SP,ETC,MSP,小品词】如“的话”

【Interjections:IJ,感叹词】如“哈”

【onomatopoeia:ON,拟声词】如“哗啦啦”

【Other Noun-modifier:JJ】如“发稿/JJ 时间/NN”

【Punctuation:PU,标点符号】

【Foreign word:FW,外国词语】如“OK

第三章 RNN及其变体

1 认识RNN模型

学习目标¶

了解什么是RNN模型.
了解RNN模型的作用.
了解RNN模型的分类.

1 什么是RNN模型¶

RNN(Recurrent Neural Network), 中文称作循环神经网络, 它一般以序列数据为输入, 通过网络内部的结构设计有效捕捉序列之间的关系特征, 一般也是以序列形式进行输出.
一般单层神经网络结构:

RNN单层网络结构:

以时间步对RNN进行展开后的单层网络结构:

RNN的循环机制使模型隐层上一时间步产生的结果, 能够作为当下时间步输入的一部分(当下时间步的输入除了正常的输入外还包括上一步的隐层输出)对当下时间步的输出产生影响.

2 RNN模型的作用¶

因为RNN结构能够很好利用序列之间的关系, 因此针对自然界具有连续性的输入序列, 如人类的语言, 语音等进行很好的处理, 广泛应用于NLP领域的各项任务, 如文本分类, 情感分析, 意图识别, 机器翻译等.
下面我们将以一个用户意图识别的例子进行简单的分析:

第一步: 用户输入了"What time is it ?", 我们首先需要对它进行基本的分词, 因为RNN是按照顺序工作的, 每次只接收一个单词进行处理.

第二步: 首先将单词"What"输送给RNN, 它将产生一个输出O1.

第三步: 继续将单词"time"输送给RNN, 但此时RNN不仅仅利用"time"来产生输出O2, 还会使用来自上一层隐层输出O1作为输入信息.

第四步: 重复这样的步骤, 直到处理完所有的单词.

第五步: 最后,将最终的隐层输出O5进行处理来解析用户意图.

3 RNN模型的分类¶

这里我们将从两个角度对RNN模型进行分类. 第一个角度是输入和输出的结构, 第二个角度是RNN的内部构造.
按照输入和输出的结构进行分类:
N vs N - RNN
N vs 1 - RNN
1 vs N - RNN
N vs M - RNN
按照RNN的内部构造进行分类:
传统RNN
LSTM
Bi-LSTM
GRU
Bi-GRU
N vs N - RNN:
它是RNN最基础的结构形式, 最大的特点就是: 输入和输出序列是等长的. 由于这个限制的存在, 使其适用范围比较小, 可用于生成等长度的合辙诗句.

N vs 1 - RNN:
有时候我们要处理的问题输入是一个序列,而要求输出是一个单独的值而不是序列,应该怎样建模呢?我们只要在最后一个隐层输出h上进行线性变换就可以了,大部分情况下,为了更好的明确结果, 还要使用sigmoid或者softmax进行处理. 这种结构经常被应用在文本分类问题上.

1 vs N - RNN:
如果输入不是序列而输出为序列的情况怎么处理呢?我们最常采用的一种方式就是使该输入作用于每次的输出之上. 这种结构可用于将图片生成文字任务等.

N vs M - RNN:
这是一种不限输入输出长度的RNN结构, 它由编码器和解码器两部分组成, 两者的内部结构都是某类RNN, 它也被称为seq2seq架构. 输入数据首先通过编码器, 最终输出一个隐含变量c, 之后最常用的做法是使用这个隐含变量c作用在解码器进行解码的每一步上, 以保证输入信息被有效利用.

seq2seq架构最早被提出应用于机器翻译, 因为其输入输出不受限制,如今也是应用最广的RNN模型结构. 在机器翻译, 阅读理解, 文本摘要等众多领域都进行了非常多的应用实践.
关于RNN的内部构造进行分类的内容我们将在后面使用单独的小节详细讲解.

4 小结¶

  • 学习了什么是RNN模型:

    • RNN(Recurrent Neural Network), 中文称作循环神经网络, 它一般以序列数据为输入, 通过网络内部的结构设计有效捕捉序列之间的关系特征, 一般也是以序列形式进行输出.
  • RNN的循环机制使模型隐层上一时间步产生的结果, 能够作为当下时间步输入的一部分(当下时间步的输入除了正常的输入外还包括上一步的隐层输出)对当下时间步的输出产生影响.

  • 学习了RNN模型的作用:

    • 因为RNN结构能够很好利用序列之间的关系, 因此针对自然界具有连续性的输入序列, 如人类的语言, 语音等进行很好的处理, 广泛应用于NLP领域的各项任务, 如文本分类, 情感分析, 意图识别, 机器翻译等.
  • 以一个用户意图识别的例子对RNN的运行过程进行简单的分析:

    • 第一步: 用户输入了"What time is it ?", 我们首先需要对它进行基本的分词, 因为RNN是按照顺序工作的, 每次只接收一个单词进行处理.
    • 第二步: 首先将单词"What"输送给RNN, 它将产生一个输出O1.
    • 第三步: 继续将单词"time"输送给RNN, 但此时RNN不仅仅利用"time"来产生输出O2, 还会使用来自上一层隐层输出O1作为输入信息.
    • 第四步: 重复这样的步骤, 直到处理完所有的单词.
    • 第五步: 最后,将最终的隐层输出O5进行处理来解析用户意图.
  • 学习了RNN模型的分类:

    • 这里我们将从两个角度对RNN模型进行分类. 第一个角度是输入和输出的结构, 第二个角度是RNN的内部构造.
  • 按照输入和输出的结构进行分类:

    • N vs N - RNN
    • N vs 1 - RNN
    • 1 vs N - RNN
    • N vs M - RNN
  • N vs N - RNN:

    • 它是RNN最基础的结构形式, 最大的特点就是: 输入和输出序列是等长的. 由于这个限制的存在, 使其适用范围比较小, 可用于生成等长度的合辙诗句.
  • N vs 1 - RNN:

    • 有时候我们要处理的问题输入是一个序列,而要求输出是一个单独的值而不是序列,应该怎样建模呢?我们只要在最后一个隐层输出h上进行线性变换就可以了,大部分情况下,为了更好的明确结果, 还要使用sigmoid或者softmax进行处理. 这种结构经常被应用在文本分类问题上.
  • 1 vs N - RNN:

    • 如果输入不是序列而输出为序列的情况怎么处理呢?我们最常采用的一种方式就是使该输入作用于每次的输出之上. 这种结构可用于将图片生成文字任务等.
  • N vs M - RNN:

    • 这是一种不限输入输出长度的RNN结构, 它由编码器和解码器两部分组成, 两者的内部结构都是某类RNN, 它也被称为seq2seq架构. 输入数据首先通过编码器, 最终输出一个隐含变量c, 之后最常用的做法是使用这个隐含变量c作用在解码器进行解码的每一步上, 以保证输入信息被有效利用.
    • seq2seq架构最早被提出应用于机器翻译, 因为其输入输出不受限制,如今也是应用最广的RNN模型结构. 在机器翻译, 阅读理解, 文本摘要等众多领域都进行了非常多的应用实践.
  • 按照RNN的内部构造进行分类:

    • 传统RNN
    • LSTM
    • Bi-LSTM
    • GRU
    • Bi-GRU
  • 关于RNN的内部构造进行分类的内容我们将在后面使用单独的小节详细讲解.


2 传统RNN模型

学习目标¶

了解传统RNN的内部结构及计算公式.
掌握Pytorch中传统RNN工具的使用.
了解传统RNN的优势与缺点.

1 传统RNN的内部结构图¶

1.1 RNN结构分析¶


结构解释图:

内部结构分析:
我们把目光集中在中间的方块部分, 它的输入有两部分, 分别是h(t-1)以及x(t), 代表上一时间步的隐层输出, 以及此时间步的输入, 它们进入RNN结构体后, 会"融合"到一起, 这种融合我们根据结构解释可知, 是将二者进行拼接, 形成新的张量[x(t), h(t-1)], 之后这个新的张量将通过一个全连接层(线性层), 该层使用tanh作为激活函数, 最终得到该时间步的输出h(t), 它将作为下一个时间步的输入和x(t+1)一起进入结构体. 以此类推.
内部结构过程演示:

根据结构分析得出内部计算公式:

\[h_t = tanh(W_t[X_t, h_{t-1}] + b_t) \]

激活函数tanh的作用:
用于帮助调节流经网络的值, tanh函数将值压缩在-1和1之间.

1.2 使用Pytorch构建RNN模型¶

位置: 在torch.nn工具包之中, 通过torch.nn.RNN可调用
nn.RNN类初始化主要参数解释:
input_size: 输入张量x中特征维度的大小
hidden_size: 隐层张量h中特征维度的大小
num_layers: 隐含层的数量
nonlinearity: 激活函数的选择, 默认是tanh
nn.RNN类实例化对象主要参数解释:
input: 输入张量x
h0: 初始化的隐层张量h
nn.RNN使用示例:

代码

1.3 传统RNN优缺点¶

1 传统RNN的优势:¶

由于内部结构简单, 对计算资源要求低, 相比之后我们要学习的RNN变体:LSTM和GRU模型参数总量少了很多, 在短序列任务上性能和效果都表现优异.

2 传统RNN的缺点:¶

传统RNN在解决长序列之间的关联时, 通过实践,证明经典RNN表现很差, 原因是在进行反向传播的时候, 过长的序列导致梯度的计算异常, 发生梯度消失或爆炸.

3 梯度消失或爆炸介绍¶

根据反向传播算法和链式法则, 梯度的计算可以简化为以下公式
image-20250715192922961

  • 其中sigmoid的导数值域是固定的, 在[0, 0.25]之间, 而一旦公式中的w也小于1, 那么通过这样的公式连乘后, 最终的梯度就会变得非常非常小, 这种现象称作梯度消失. 反之, 如果我们人为的增大w的值, 使其大于1, 那么连乘够就可能造成梯度过大, 称作梯度爆炸.
  • 梯度消失或爆炸的危害:
    • 如果在训练过程中发生了梯度消失,权重无法被更新,最终导致训练失败; 梯度爆炸所带来的梯度过大,大幅度更新网络参数,在极端情况下,结果会溢出(NaN值).

2 小结¶

  • 学习了传统RNN的结构并进行了分析;

    • 它的输入有两部分, 分别是h(t-1)以及x(t), 代表上一时间步的隐层输出, 以及此时间步的输入, 它们进入RNN结构体后, 会"融合"到一起, 这种融合我们根据结构解释可知, 是将二者进行拼接, 形成新的张量[x(t), h(t-1)], 之后这个新的张量将通过一个全连接层(线性层), 该层使用tanh作为激活函数, 最终得到该时间步的输出h(t), 它将作为下一个时间步的输入和x(t+1)一起进入结构体. 以此类推.
  • 根据结构分析得出了传统RNN的计算公式.

  • 学习了激活函数tanh的作用:

    • 用于帮助调节流经网络的值, tanh函数将值压缩在-1和1之间.
  • 学习了Pytorch中传统RNN工具的使用:

    • 位置: 在torch.nn工具包之中, 通过torch.nn.RNN可调用.
  • nn.RNN类初始化主要参数解释:

    • input_size: 输入张量x中特征维度的大小.
    • hidden_size: 隐层张量h中特征维度的大小.
    • num_layers: 隐含层的数量.
    • nonlinearity: 激活函数的选择, 默认是tanh.
  • nn.RNN类实例化对象主要参数解释:

    • input: 输入张量x.
    • h0: 初始化的隐层张量h.
  • 实现了nn.RNN的使用示例, 获得RNN的真实返回结果样式.

  • 学习了传统RNN的优势:

    • 由于内部结构简单, 对计算资源要求低, 相比之后我们要学习的RNN变体:LSTM和GRU模型参数总量少了很多, 在短序列任务上性能和效果都表现优异.
  • 学习了传统RNN的缺点:

    • 传统RNN在解决长序列之间的关联时, 通过实践,证明经典RNN表现很差, 原因是在进行反向传播的时候, 过长的序列导致梯度的计算异常, 发生梯度消失或爆炸.
  • 学习了什么是梯度消失或爆炸:

    • 根据反向传播算法和链式法则, 得到梯度的计算的简化公式:其中sigmoid的导数值域是固定的, 在[0, 0.25]之间, 而一旦公式中的w也小于1, 那么通过这样的公式连乘后, 最终的梯度就会变得非常非常小, 这种现象称作梯度消失. 反之, 如果我们人为的增大w的值, 使其大于1, 那么连乘够就可能造成梯度过大, 称作梯度爆炸.
  • 梯度消失或爆炸的危害:

    • 如果在训练过程中发生了梯度消失,权重无法被更新,最终导致训练失败; 梯度爆炸所带来的梯度过大,大幅度更新网络参数,在极端情况下,结果会溢出(NaN值).

3 LSTM模型

学习目标¶

了解LSTM内部结构及计算公式.
掌握Pytorch中LSTM工具的使用.
了解LSTM的优势与缺点.

1 LSTM介绍¶

LSTM(Long Short-Term Memory)也称长短时记忆结构, 它是传统RNN的变体, 与经典RNN相比能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆炸现象. 同时LSTM的结构更复杂, 它的核心结构可以分为四个部分去解析:
遗忘门
输入门
细胞状态
输出门

2 LSTM的内部结构图¶

2.1 LSTM结构分析¶


结构解释图:

遗忘门部分结构图与计算公式:

遗忘门结构分析:
与传统RNN的内部结构计算非常相似, 首先将当前时间步输入x(t)与上一个时间步隐含状态h(t-1)拼接, 得到[x(t), h(t-1)], 然后通过一个全连接层做变换, 最后通过sigmoid函数进行激活得到f(t), 我们可以将f(t)看作是门值, 好比一扇门开合的大小程度, 门值都将作用在通过该扇门的张量, 遗忘门门值将作用的上一层的细胞状态上, 代表遗忘过去的多少信息, 又因为遗忘门门值是由x(t), h(t-1)计算得来的, 因此整个公式意味着根据当前时间步输入和上一个时间步隐含状态h(t-1)来决定遗忘多少上一层的细胞状态所携带的过往信息.
遗忘门内部结构过程演示:

激活函数sigmiod的作用:
用于帮助调节流经网络的值, sigmoid函数将值压缩在0和1之间.

输入门部分结构图与计算公式:

输入门结构分析:
我们看到输入门的计算公式有两个, 第一个就是产生输入门门值的公式, 它和遗忘门公式几乎相同, 区别只是在于它们之后要作用的目标上. 这个公式意味着输入信息有多少需要进行过滤. 输入门的第二个公式是与传统RNN的内部结构计算相同. 对于LSTM来讲, 它得到的是当前的细胞状态, 而不是像经典RNN一样得到的是隐含状态.
输入门内部结构过程演示:

细胞状态更新图与计算公式:

细胞状态更新分析:
细胞更新的结构与计算公式非常容易理解, 这里没有全连接层, 只是将刚刚得到的遗忘门门值与上一个时间步得到的C(t-1)相乘, 再加上输入门门值与当前时间步得到的未更新C(t)相乘的结果. 最终得到更新后的C(t)作为下一个时间步输入的一部分. 整个细胞状态更新过程就是对遗忘门和输入门的应用.
细胞状态更新过程演示:

输出门部分结构图与计算公式:

输出门结构分析:
输出门部分的公式也是两个, 第一个即是计算输出门的门值, 它和遗忘门,输入门计算方式相同. 第二个即是使用这个门值产生隐含状态h(t), 他将作用在更新后的细胞状态C(t)上, 并做tanh激活, 最终得到h(t)作为下一时间步输入的一部分. 整个输出门的过程, 就是为了产生隐含状态h(t).
输出门内部结构过程演示:

2.2 Bi-LSTM介绍¶

Bi-LSTM即双向LSTM, 它没有改变LSTM本身任何的内部结构, 只是将LSTM应用两次且方向不同, 再将两次得到的LSTM结果进行拼接作为最终输出.
avatar

Bi-LSTM结构分析:
我们看到图中对"我爱中国"这句话或者叫这个输入序列, 进行了从左到右和从右到左两次LSTM处理, 将得到的结果张量进行了拼接作为最终输出. 这种结构能够捕捉语言语法中一些特定的前置或后置特征, 增强语义关联,但是模型参数和计算复杂度也随之增加了一倍, 一般需要对语料和计算资源进行评估后决定是否使用该结构.

2.3 使用Pytorch构建LSTM模型¶

位置: 在torch.nn工具包之中, 通过torch.nn.LSTM可调用.
nn.LSTM类初始化主要参数解释:
input_size: 输入张量x中特征维度的大小.
hidden_size: 隐层张量h中特征维度的大小.
num_layers: 隐含层的数量.
bidirectional: 是否选择使用双向LSTM, 如果为True, 则使用; 默认不使用.
nn.LSTM类实例化对象主要参数解释:
input: 输入张量x.
h0: 初始化的隐层张量h.
c0: 初始化的细胞状态张量c.
nn.LSTM使用示例:

代码

2.4 LSTM优缺点¶

LSTM优势:
LSTM的门结构能够有效减缓长序列问题中可能出现的梯度消失或爆炸, 虽然并不能杜绝这种现象, 但在更长的序列问题上表现优于传统RNN.
LSTM缺点:
由于内部结构相对较复杂, 因此训练效率在同等算力下较传统RNN低很多.

3 小结¶

  • LSTM(Long Short-Term Memory)也称长短时记忆结构, 它是传统RNN的变体, 与经典RNN相比能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆炸现象. 同时LSTM的结构更复杂, 它的核心结构可以分为四个部分去解析:

    • 遗忘门
    • 输入门
    • 输出门
    • 细胞状态
  • 遗忘门结构分析:

    • 与传统RNN的内部结构计算非常相似, 首先将当前时间步输入x(t)与上一个时间步隐含状态h(t-1)拼接, 得到[x(t), h(t-1)], 然后通过一个全连接层做变换, 最后通过sigmoid函数进行激活得到f(t), 我们可以将f(t)看作是门值, 好比一扇门开合的大小程度, 门值都将作用在通过该扇门的张量, 遗忘门门值将作用的上一层的细胞状态上, 代表遗忘过去的多少信息, 又因为遗忘门门值是由x(t), h(t-1)计算得来的, 因此整个公式意味着根据当前时间步输入和上一个时间步隐含状态h(t-1)来决定遗忘多少上一层的细胞状态所携带的过往信息.
  • 输入门结构分析:

    • 我们看到输入门的计算公式有两个, 第一个就是产生输入门门值的公式, 它和遗忘门公式几乎相同, 区别只是在于它们之后要作用的目标上. 这个公式意味着输入信息有多少需要进行过滤. 输入门的第二个公式是与传统RNN的内部结构计算相同. 对于LSTM来讲, 它得到的是当前的细胞状态, 而不是像经典RNN一样得到的是隐含状态.
  • 细胞状态更新分析:

    • 细胞更新的结构与计算公式非常容易理解, 这里没有全连接层, 只是将刚刚得到的遗忘门门值与上一个时间步得到的C(t-1)相乘, 再加上输入门门值与当前时间步得到的未更新C(t)相乘的结果. 最终得到更新后的C(t)作为下一个时间步输入的一部分. 整个细胞状态更新过程就是对遗忘门和输入门的应用.
  • 输出门结构分析:

    • 输出门部分的公式也是两个, 第一个即是计算输出门的门值, 它和遗忘门,输入门计算方式相同. 第二个即是使用这个门值产生隐含状态h(t), 他将作用在更新后的细胞状态C(t)上, 并做tanh激活, 最终得到h(t)作为下一时间步输入的一部分. 整个输出门的过程, 就是为了产生隐含状态h(t).
  • 什么是Bi-LSTM ?

    • Bi-LSTM即双向LSTM, 它没有改变LSTM本身任何的内部结构, 只是将LSTM应用两次且方向不同, 再将两次得到的LSTM结果进行拼接作为最终输出.
  • Pytorch中LSTM工具的使用:

    • 位置: 在torch.nn工具包之中, 通过torch.nn.LSTM可调用.
  • LSTM优势:
    LSTM的门结构能够有效减缓长序列问题中可能出现的梯度消失或爆炸, 虽然并不能杜绝这种现象, 但在更长的序列问题上表现优于传统RNN.

  • LSTM缺点:
    由于内部结构相对较复杂, 因此训练效率在同等算力下较传统RNN低很多.


4 GRU模型

学习目标¶

了解GRU内部结构及计算公式.
掌握Pytorch中GRU工具的使用.
了解GRU的优势与缺点.

1 GRU介绍¶

GRU(Gated Recurrent Unit)也称门控循环单元结构, 它也是传统RNN的变体, 同LSTM一样能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆炸现象. 同时它的结构和计算要比LSTM更简单, 它的核心结构可以分为两个部分去解析:
更新门
重置门

2 GRU的内部结构图¶

2.1 GRU结构分析¶


结构解释图:

GRU的更新门和重置门结构图:

内部结构分析:
和之前分析过的LSTM中的门控一样, 首先计算更新门和重置门的门值, 分别是z(t)和r(t), 计算方法就是使用X(t)与h(t-1)拼接进行线性变换, 再经过sigmoid激活. 之后重置门门值作用在了h(t-1)上, 代表控制上一时间步传来的信息有多少可以被利用. 接着就是使用这个重置后的h(t-1)进行基本的RNN计算, 即与x(t)拼接进行线性变化, 经过tanh激活, 得到新的h(t). 最后更新门的门值会作用在新的h(t),而1-门值会作用在h(t-1)上, 随后将两者的结果相加, 得到最终的隐含状态输出h(t), 这个过程意味着更新门有能力保留之前的结果, 当门值趋于1时, 输出就是新的h(t), 而当门值趋于0时, 输出就是上一时间步的h(t-1).

2.2 Bi-GRU介绍¶

Bi-GRU与Bi-LSTM的逻辑相同, 都是不改变其内部结构, 而是将模型应用两次且方向不同, 再将两次得到的LSTM结果进行拼接作为最终输出. 具体参见上小节中的Bi-LSTM.

2.3 使用Pytorch构建GRU模型¶

位置: 在torch.nn工具包之中, 通过torch.nn.GRU可调用.
nn.GRU类初始化主要参数解释:
input_size: 输入张量x中特征维度的大小.
hidden_size: 隐层张量h中特征维度的大小.
num_layers: 隐含层的数量.
bidirectional: 是否选择使用双向LSTM, 如果为True, 则使用; 默认不使用.
nn.GRU类实例化对象主要参数解释:
input: 输入张量x.
h0: 初始化的隐层张量h.
nn.GRU使用示例:

代码

2.4 GRU优缺点¶

  • GRU的优势:
    GRU和LSTM作用相同, 在捕捉长序列语义关联时, 能有效抑制梯度消失或爆炸, 效果都优于传统RNN且计算复杂度相比LSTM要小.
  • GRU的缺点:
    GRU仍然不能完全解决梯度消失问题, 同时其作用RNN的变体, 有着RNN结构本身的一大弊端, 即不可并行计算, 这在数据量和模型体量逐步增大的未来, 是RNN发展的关键瓶颈.

3 小结¶

  • GRU(Gated Recurrent Unit)也称门控循环单元结构, 它也是传统RNN的变体, 同LSTM一样能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆炸现象. 同时它的结构和计算要比LSTM更简单, 它的核心结构可以分为两个部分去解析:
    • 更新门
    • 重置门
  • 内部结构分析:
    • 和之前分析过的LSTM中的门控一样, 首先计算更新门和重置门的门值, 分别是z(t)和r(t), 计算方法就是使用X(t)与h(t-1)拼接进行线性变换, 再经过sigmoid激活. 之后重置门门值作用在了h(t-1)上, 代表控制上一时间步传来的信息有多少可以被利用. 接着就是使用这个重置后的h(t-1)进行基本的RNN计算, 即与x(t)拼接进行线性变化, 经过tanh激活, 得到新的h(t). 最后更新门的门值会作用在新的h(t),而1-门值会作用在h(t-1)上, 随后将两者的结果相加, 得到最终的隐含状态输出h(t), 这个过程意味着更新门有能力保留之前的结果, 当门值趋于1时, 输出就是新的h(t), 而当门值趋于0时, 输出就是上一时间步的h(t-1).
  • Bi-GRU与Bi-LSTM的逻辑相同, 都是不改变其内部结构, 而是将模型应用两次且方向不同, 再将两次得到的LSTM结果进行拼接作为最终输出. 具体参见上小节中的Bi-LSTM.
  • Pytorch中GRU工具的使用:
    • 位置: 在torch.nn工具包之中, 通过torch.nn.GRU可调用.
  • GRU的优势:
    • GRU和LSTM作用相同, 在捕捉长序列语义关联时, 能有效抑制梯度消失或爆炸, 效果都优于传统RNN且计算复杂度相比LSTM要小.
  • GRU的缺点:
    • GRU仍然不能完全解决梯度消失问题, 同时其作用RNN的变体, 有着RNN结构本身的一大弊端, 即不可并行计算, 这在数据量和模型体量逐步增大的未来, 是RNN发展的关键瓶颈.

5 注意力机制介绍

学习目标¶

了解什么是注意力计算规则以及常见的计算规则.
了解什么是注意力机制及其作用.
掌握注意力机制的实现步骤.

1 注意力机制介绍¶

1.1 注意力概念¶

我们观察事物时,之所以能够快速判断一种事物(当然允许判断是错误的), 是因为我们大脑能够很快把注意力放在事物最具有辨识度的部分从而作出判断,而并非是从头到尾的观察一遍事物后,才能有判断结果. 正是基于这样的理论,就产生了注意力机制.

1.2 注意力计算规则¶

它需要三个指定的输入Q(query), K(key), V(value), 然后通过计算公式得到注意力的结果, 这个结果代表query在key和value作用下的注意力表示. 当输入的Q=K=V时, 称作自注意力计算规则.
Q, K, V的比喻解释:

代码

1.3 常见的注意力计算规则¶

将Q,K进行纵轴拼接, 做一次线性变化, 再使用softmax处理获得结果最后与V做张量乘法.

\[Attention(Q,K,V)=Softmax(Linear([Q,K]))\cdot V \]

将Q,K进行纵轴拼接, 做一次线性变化后再使用tanh函数激活, 然后再进行内部求和, 最后使用softmax处理获得结果再与V做张量乘法.

\[Attention(Q,K,V)=Softmax(sum(tanh(Linear([Q,K]))))\cdot V \]

将Q与K的转置做点积运算, 然后除以一个缩放系数, 再使用softmax处理获得结果最后与V做张量乘法.

\[Attention(Q,K,V)=Softmax(\frac{Q\cdot K^T}{\sqrt{d_{k}}})\cdot V \]

说明:当注意力权重矩阵和V都是三维张量且第一维代表为batch条数时, 则做bmm运算.bmm是一种特殊的张量乘法运算.
bmm运算演示:

代码

2 什么是注意力机制¶

注意力机制是注意力计算规则能够应用的深度学习网络的载体, 同时包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体. 使用自注意力计算规则的注意力机制称为自注意力机制.
说明: NLP领域中, 当前的注意力机制大多数应用于seq2seq架构, 即编码器和解码器模型.

3 注意力机制的作用¶

在解码器端的注意力机制: 能够根据模型目标有效的聚焦编码器的输出结果, 当其作为解码器的输入时提升效果. 改善以往编码器输出是单一定长张量, 无法存储过多信息的情况.
在编码器端的注意力机制: 主要解决表征问题, 相当于特征提取过程, 得到输入的注意力表示. 一般使用自注意力(self-attention).
注意力机制在网络中实现的图形表示:

4 注意力机制实现步骤¶

4.1 步骤¶

第一步: 根据注意力计算规则, 对Q,K,V进行相应的计算.
第二步: 根据第一步采用的计算方法, 如果是拼接方法,则需要将Q与第二步的计算结果再进行拼接, 如果是转置点积, 一般是自注意力, Q与V相同, 则不需要进行与Q的拼接.
第三步: 最后为了使整个attention机制按照指定尺寸输出, 使用线性层作用在第二步的结果上做一个线性变换, 得到最终对Q的注意力表示.

4.2 代码实现¶

常见注意力机制的代码分析:

import torch
import torch.nn as nn
import torch.nn.functional as F

class Attn(nn.Module):
    def __init__(self, query_size, key_size, value_size1, value_size2, output_size):
        """初始化函数中的参数有5个, query_size代表query的最后一维大小
           key_size代表key的最后一维大小, value_size1代表value的导数第二维大小, 
           value = (1, value_size1, value_size2)
           value_size2代表value的倒数第一维大小, output_size输出的最后一维大小"""
        super(Attn, self).__init__()
        # 将以下参数传入类中
        self.query_size = query_size
        self.key_size = key_size
        self.value_size1 = value_size1
        self.value_size2 = value_size2
        self.output_size = output_size

        # 初始化注意力机制实现第一步中需要的线性层.
        self.attn = nn.Linear(self.query_size + self.key_size, value_size1)

        # 初始化注意力机制实现第三步中需要的线性层.
        self.attn_combine = nn.Linear(self.query_size + value_size2, output_size)


    def forward(self, Q, K, V):
        """forward函数的输入参数有三个, 分别是Q, K, V, 根据模型训练常识, 输入给Attion机制的
           张量一般情况都是三维张量, 因此这里也假设Q, K, V都是三维张量"""

        # 第一步, 按照计算规则进行计算, 
        # 我们采用常见的第一种计算规则
        # 将Q,K进行纵轴拼接, 做一次线性变化, 最后使用softmax处理获得结果
        attn_weights = F.softmax(
            self.attn(torch.cat((Q[0], K[0]), 1)), dim=1)

        # 然后进行第一步的后半部分, 将得到的权重矩阵与V做矩阵乘法计算, 
        # 当二者都是三维张量且第一维代表为batch条数时, 则做bmm运算
        attn_applied = torch.bmm(attn_weights.unsqueeze(0), V)

        # 之后进行第二步, 通过取[0]是用来降维, 根据第一步采用的计算方法, 
        # 需要将Q与第一步的计算结果再进行拼接
        output = torch.cat((Q[0], attn_applied[0]), 1)

        # 最后是第三步, 使用线性层作用在第三步的结果上做一个线性变换并扩展维度,得到输出
        # 因为要保证输出也是三维张量, 因此使用unsqueeze(0)扩展维度
        output = self.attn_combine(output).unsqueeze(0)
        return output, attn_weights

调用:

query_size = 32
key_size = 32
value_size1 = 32
value_size2 = 64
output_size = 64
attn = Attn(query_size, key_size, value_size1, value_size2, output_size)
Q = torch.randn(1,1,32)
K = torch.randn(1,1,32)
V = torch.randn(1,32,64)
out = attn(Q, K ,V)
print(out[0])
print(out[1])

输出效果:

tensor([[[ 0.4477, -0.0500, -0.2277, -0.3168, -0.4096, -0.5982,  0.1548,
          -0.0771, -0.0951,  0.1833,  0.3128,  0.1260,  0.4420,  0.0495,
          -0.7774, -0.0995,  0.2629,  0.4957,  1.0922,  0.1428,  0.3024,
          -0.2646, -0.0265,  0.0632,  0.3951,  0.1583,  0.1130,  0.5500,
          -0.1887, -0.2816, -0.3800, -0.5741,  0.1342,  0.0244, -0.2217,
           0.1544,  0.1865, -0.2019,  0.4090, -0.4762,  0.3677, -0.2553,
          -0.5199,  0.2290, -0.4407,  0.0663, -0.0182, -0.2168,  0.0913,
          -0.2340,  0.1924, -0.3687,  0.1508,  0.3618, -0.0113,  0.2864,
          -0.1929, -0.6821,  0.0951,  0.1335,  0.3560, -0.3215,  0.6461,
           0.1532]]], grad_fn=<UnsqueezeBackward0>)


tensor([[0.0395, 0.0342, 0.0200, 0.0471, 0.0177, 0.0209, 0.0244, 0.0465, 0.0346,
         0.0378, 0.0282, 0.0214, 0.0135, 0.0419, 0.0926, 0.0123, 0.0177, 0.0187,
         0.0166, 0.0225, 0.0234, 0.0284, 0.0151, 0.0239, 0.0132, 0.0439, 0.0507,
         0.0419, 0.0352, 0.0392, 0.0546, 0.0224]], grad_fn=<SoftmaxBackward>)

更多有关注意力机制的应用我们将在案例中进行详尽的理解分析.

5 小结¶

  • 学习了什么是注意力计算规则:
    • 它需要三个指定的输入Q(query), K(key), V(value), 然后通过计算公式得到注意力的结果, 这个结果代表query在key和value作用下的注意力表示. 当输入的Q=K=V时, 称作自注意力计算规则.
  • 常见的注意力计算规则:
    • 将Q,K进行纵轴拼接, 做一次线性变化, 再使用softmax处理获得结果最后与V做张量乘法.
    • 将Q,K进行纵轴拼接, 做一次线性变化后再使用tanh函数激活, 然后再进行内部求和, 最后使用softmax处理获得结果再与V做张量乘法.
    • 将Q与K的转置做点积运算, 然后除以一个缩放系数, 再使用softmax处理获得结果最后与V做张量乘法.
  • 学习了什么是注意力机制:
    • 注意力机制是注意力计算规则能够应用的深度学习网络的载体, 同时包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体. 使自注意力计算规则的注意力机制称为自注意力机制.
  • 注意力机制的作用:
    • 在解码器端的注意力机制: 能够根据模型目标有效的聚焦编码器的输出结果, 当其作为解码器的输入时提升效果. 改善以往编码器输出是单一定长张量, 无法存储过多信息的情况.
    • 在编码器端的注意力机制: 主要解决表征问题, 相当于特征提取过程, 得到输入的注意力表示. 一般使用自注意力(self-attention).
  • 注意力机制实现步骤:
    • 第一步: 根据注意力计算规则, 对Q,K,V进行相应的计算.
    • 第二步: 根据第一步采用的计算方法, 如果是拼接方法,则需要将Q与第二步的计算结果再进行拼接, 如果是转置点积, 一般是自注意力, Q与V相同, 则不需要进行与Q的拼接.
    • 第三步: 最后为了使整个attention机制按照指定尺寸输出, 使用线性层作用在第二步的结果上做一个线性变换, 得到最终对Q的注意力表示.
  • 学习并实现了一种常见的注意力机制的类Attn.

第四章 Transformer

1 Transformer背景介绍

学习目标¶

  • 了解Transformer背景

1 Transformer的诞生¶

2018年10月,Google发出一篇论文《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》, BERT模型横空出世, 并横扫NLP领域11项任务的最佳成绩!
论文地址: https://arxiv.org/pdf/1810.04805.pdf
而在BERT中发挥重要作用的结构就是Transformer, 之后又相继出现XLNET,roBERT等模型击败了BERT,但是他们的核心没有变,仍然是:Transformer。

2 Transformer的优势¶

相比之前占领市场的LSTM和GRU模型,Transformer有两个显著的优势:

1、Transformer能够利用分布式GPU进行并行训练,提升模型训练效率.  
2、在分析预测更长的文本时, 捕捉间隔较长的语义关联效果更好.   

下面是一张在测评比较图:
avatar

3 Transformer的市场¶

在著名的SOTA机器翻译榜单上, 几乎所有排名靠前的模型都使用Transformer,
avatar
其基本上可以看作是工业界的风向标, 市场空间自然不必多说!


2 认识Transformer架构

学习目标¶

  • 了解Transformer模型的作用.
  • 了解Transformer总体架构图中各个组成部分的名称.

1 Transformer模型的作用¶

  • 基于seq2seq架构的transformer模型可以完成NLP领域研究的典型任务, 如机器翻译, 文本生成等. 同时又可以构建预训练语言模型,用于不同任务的迁移学习.
  • 在接下来的架构分析中, 我们将假设使用Transformer模型架构处理从一种语言文本到另一种语言文本的翻译工作, 因此很多命名方式遵循NLP中的规则. 比如: Embeddding层将称作文本嵌入层, Embedding层产生的张量称为词嵌入张量, 它的最后一维将称作词向量等.

2 Transformer总体架构图¶

2.1 Transformer总体架构¶

  • 输入部分
  • 输出部分
  • 编码器部分
  • 解码器部分

2.2 输入部分包含¶

  • 源文本嵌入层及其位置编码器
  • 目标文本嵌入层及其位置编码器

2.3 输出部分包含¶

  • 线性层
  • softmax层

2.4 编码器部分¶

  • 由N个编码器层堆叠而成
  • 每个编码器层由两个子层连接结构组成
  • 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
  • 第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接

2.5 解码器部分¶

  • 由N个解码器层堆叠而成
  • 每个解码器层由三个子层连接结构组成
  • 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
  • 第二个子层连接结构包括一个多头注意力子层和规范化层以及一个残差连接
  • 第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接

3 小结¶

  • 学习了Transformer模型的作用: - 基于seq2seq架构的transformer模型可以完成NLP领域研究的典型任务, 如机器翻译, 文本生成等. 同时又可以构建预训练语言模型,用于不同任务的迁移学习.

  • Transformer总体架构可分为四个部分:

    • 输入部分
    • 输出部分
    • 编码器部分
    • 解码器部分
  • 输入部分包含:

    • 源文本嵌入层及其位置编码器
    • 目标文本嵌入层及其位置编码器
  • 输出部分包含:

    • 线性层
    • softmax处理器
  • 编码器部分:

    • 由N个编码器层堆叠而成
    • 每个编码器层由两个子层连接结构组成
    • 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
    • 第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
  • 解码器部分:

    • 由N个解码器层堆叠而成
    • 每个解码器层由三个子层连接结构组成
    • 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
    • 第二个子层连接结构包括一个多头注意力子层和规范化层以及一个残差连接
    • 第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接

3 输入部分实现

学习目标¶

  • 了解文本嵌入层和位置编码的作用.
  • 掌握文本嵌入层和位置编码的实现过程.

1 输入部分介绍¶

输入部分包含:

  • 源文本嵌入层及其位置编码器
  • 目标文本嵌入层及其位置编码器

2 文本嵌入层的作用¶

  • 无论是源文本嵌入还是目标文本嵌入,都是为了将文本中词汇的数字表示转变为向量表示, 希望在这样的高维空间捕捉词汇间的关系.
  • 文本嵌入层的代码分析:
# 导入必备的工具包
import torch

# 预定义的网络层torch.nn, 工具开发者已经帮助我们开发好的一些常用层, 
# 比如,卷积层, lstm层, embedding层等, 不需要我们再重新造轮子.
import torch.nn as nn

# 数学计算工具包
import math

# torch中变量封装函数Variable.
from torch.autograd import Variable

# 定义Embeddings类来实现文本嵌入层,这里s说明代表两个一模一样的嵌入层, 他们共享参数.
# 该类继承nn.Module, 这样就有标准层的一些功能, 这里我们也可以理解为一种模式, 我们自己实现的所有层都会这样去写.
class Embeddings(nn.Module):
    def __init__(self, d_model, vocab):
        """类的初始化函数, 有两个参数, d_model: 指词嵌入的维度, vocab: 指词表的大小."""
        # 接着就是使用super的方式指明继承nn.Module的初始化函数, 我们自己实现的所有层都会这样去写.
        super(Embeddings, self).__init__()
        # 之后就是调用nn中的预定义层Embedding, 获得一个词嵌入对象self.lut
        self.lut = nn.Embedding(vocab, d_model)
        # 最后就是将d_model传入类中
        self.d_model = d_model

    def forward(self, x):
        """可以将其理解为该层的前向传播逻辑,所有层中都会有此函数
           当传给该类的实例化对象参数时, 自动调用该类函数
           参数x: 因为Embedding层是首层, 所以代表输入给模型的文本通过词汇映射后的张量"""

        # 将x传给self.lut并与根号下self.d_model相乘作为结果返回

        # 让 embeddings vector 在增加 之后的 postion encoing 之前相对大一些的操作,
        # 主要是为了让position encoding 相对的小,这样会让原来的 embedding vector 中的信息在和 position encoding 的信息相加时不至于丢失掉
        # 让 embeddings vector 相对大一些
        return self.lut(x) * math.sqrt(self.d_model)
  • nn.Embedding演示:
>>> embedding = nn.Embedding(10, 3)
>>> input = torch.LongTensor([[1,2,4,5],[4,3,2,9]])
>>> embedding(input)
tensor([[[-0.0251, -1.6902,  0.7172],
         [-0.6431,  0.0748,  0.6969],
         [ 1.4970,  1.3448, -0.9685],
         [-0.3677, -2.7265, -0.1685]],

        [[ 1.4970,  1.3448, -0.9685],
         [ 0.4362, -0.4004,  0.9400],
         [-0.6431,  0.0748,  0.6969],
         [ 0.9124, -2.3616,  1.1151]]])


>>> embedding = nn.Embedding(10, 3, padding_idx=0)
>>> input = torch.LongTensor([[0,2,0,5]])
>>> embedding(input)
tensor([[[ 0.0000,  0.0000,  0.0000],
         [ 0.1535, -2.0309,  0.9315],
         [ 0.0000,  0.0000,  0.0000],
         [-0.1655,  0.9897,  0.0635]]])
  • 实例化参数:
# 词嵌入维度是512维
d_model = 512

# 词表大小是1000
vocab = 1000
  • 输入参数:
# 输入x是一个使用Variable封装的长整型张量, 形状是2 x 4
x = Variable(torch.LongTensor([[100,2,421,508],[491,998,1,221]]))
  • 调用:
emb = Embeddings(d_model, vocab)
embr = emb(x)
print("embr:", embr)
  • 输出效果:
embr: Variable containing:
( 0 ,.,.) = 
  35.9321   3.2582 -17.7301  ...    3.4109  13.8832  39.0272
   8.5410  -3.5790 -12.0460  ...   40.1880  36.6009  34.7141
 -17.0650  -1.8705 -20.1807  ...  -12.5556 -34.0739  35.6536
  20.6105   4.4314  14.9912  ...   -0.1342  -9.9270  28.6771

( 1 ,.,.) = 
  27.7016  16.7183  46.6900  ...   17.9840  17.2525  -3.9709
   3.0645  -5.5105  10.8802  ...  -13.0069  30.8834 -38.3209
  33.1378 -32.1435  -3.9369  ...   15.6094 -29.7063  40.1361
 -31.5056   3.3648   1.4726  ...    2.8047  -9.6514 -23.4909
[torch.FloatTensor of size 2x4x512]

3 位置编码器的作用¶

因为在Transformer的编码器结构中, 并没有针对词汇位置信息的处理,因此需要在Embedding层后加入位置编码器,将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中, 以弥补位置信息的缺失.

3.1 位置编码器的代码分析¶

# 定义位置编码器类, 我们同样把它看做一个层, 因此会继承nn.Module  
class PositionalEncoding(nn.Module):
    def __init__(self, d_model, dropout, max_len=5000):
        """位置编码器类的初始化函数, 共有三个参数, 分别是d_model: 词嵌入维度, 
           dropout: 置0比率, max_len: 每个句子的最大长度"""
        super(PositionalEncoding, self).__init__()

        # 实例化nn中预定义的Dropout层, 并将dropout传入其中, 获得对象self.dropout
        self.dropout = nn.Dropout(p=dropout)

        # 初始化一个位置编码矩阵, 它是一个0阵,矩阵的大小是max_len x d_model.
        pe = torch.zeros(max_len, d_model)

        # 初始化一个绝对位置矩阵, 在我们这里,词汇的绝对位置就是用它的索引去表示. 
        # 所以我们首先使用arange方法获得一个连续自然数向量,然后再使用unsqueeze方法拓展向量维度使其成为矩阵, 
        # 又因为参数传的是1,代表矩阵拓展的位置,会使向量变成一个max_len x 1 的矩阵, 
        position = torch.arange(0, max_len).unsqueeze(1)

        # 绝对位置矩阵初始化之后,接下来就是考虑如何将这些位置信息加入到位置编码矩阵中,
        # 最简单思路就是先将max_len x 1的绝对位置矩阵, 变换成max_len x d_model形状,然后覆盖原来的初始位置编码矩阵即可, 
        # 要做这种矩阵变换,就需要一个1xd_model形状的变换矩阵div_term,我们对这个变换矩阵的要求除了形状外,
        # 还希望它能够将自然数的绝对位置编码缩放成足够小的数字,有助于在之后的梯度下降过程中更快的收敛.  这样我们就可以开始初始化这个变换矩阵了.
        # 首先使用arange获得一个自然数矩阵, 但是细心的同学们会发现, 我们这里并没有按照预计的一样初始化一个1xd_model的矩阵, 
        # 而是有了一个跳跃,只初始化了一半即1xd_model/2 的矩阵。 为什么是一半呢,其实这里并不是真正意义上的初始化了一半的矩阵,
        # 我们可以把它看作是初始化了两次,而每次初始化的变换矩阵会做不同的处理,第一次初始化的变换矩阵分布在正弦波上, 第二次初始化的变换矩阵分布在余弦波上, 
        # 并把这两个矩阵分别填充在位置编码矩阵的偶数和奇数位置上,组成最终的位置编码矩阵.
        div_term = torch.exp(torch.arange(0, d_model, 2) *
                             -(math.log(10000.0) / d_model))
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)

        # 这样我们就得到了位置编码矩阵pe, pe现在还只是一个二维矩阵,要想和embedding的输出(一个三维张量)相加,
        # 就必须拓展一个维度,所以这里使用unsqueeze拓展维度.
        pe = pe.unsqueeze(0)

        # 最后把pe位置编码矩阵注册成模型的buffer,什么是buffer呢,
        # 我们把它认为是对模型效果有帮助的,但是却不是模型结构中超参数或者参数,不需要随着优化步骤进行更新的增益对象. 
        # 注册之后我们就可以在模型保存后重加载时和模型结构与参数一同被加载.
        self.register_buffer('pe', pe)

    def forward(self, x):
        """forward函数的参数是x, 表示文本序列的词嵌入表示"""
        # 在相加之前我们对pe做一些适配工作, 将这个三维张量的第二维也就是句子最大长度的那一维将切片到与输入的x的第二维相同即x.size(1),
        # 因为我们默认max_len为5000一般来讲实在太大了,很难有一条句子包含5000个词汇,所以要进行与输入张量的适配. 
        # 最后使用Variable进行封装,使其与x的样式相同,但是它是不需要进行梯度求解的,因此把requires_grad设置成false.
        x = x + Variable(self.pe[:, :x.size(1)], 
                         requires_grad=False)
        # 最后使用self.dropout对象进行'丢弃'操作, 并返回结果.
        return self.dropout(x)
  • nn.Dropout演示:
>>> m = nn.Dropout(p=0.2)
>>> input = torch.randn(4, 5)
>>> output = m(input)
>>> output
Variable containing:
 0.0000 -0.5856 -1.4094  0.0000 -1.0290
 2.0591 -1.3400 -1.7247 -0.9885  0.1286
 0.5099  1.3715  0.0000  2.2079 -0.5497
-0.0000 -0.7839 -1.2434 -0.1222  1.2815
[torch.FloatTensor of size 4x5]
  • torch.unsqueeze演示:
>>> x = torch.tensor([1, 2, 3, 4])
>>> torch.unsqueeze(x, 0)
tensor([[ 1,  2,  3,  4]])
>>> torch.unsqueeze(x, 1)
tensor([[ 1],
        [ 2],
        [ 3],
        [ 4]])
  • 实例化参数:
# 词嵌入维度是512维
d_model = 512

# 置0比率为0.1
dropout = 0.1

# 句子最大长度
max_len=60
  • 输入参数:
# 输入x是Embedding层的输出的张量, 形状是2 x 4 x 512
x = embr
Variable containing:
( 0 ,.,.) = 
  35.9321   3.2582 -17.7301  ...    3.4109  13.8832  39.0272
   8.5410  -3.5790 -12.0460  ...   40.1880  36.6009  34.7141
 -17.0650  -1.8705 -20.1807  ...  -12.5556 -34.0739  35.6536
  20.6105   4.4314  14.9912  ...   -0.1342  -9.9270  28.6771

( 1 ,.,.) = 
  27.7016  16.7183  46.6900  ...   17.9840  17.2525  -3.9709
   3.0645  -5.5105  10.8802  ...  -13.0069  30.8834 -38.3209
  33.1378 -32.1435  -3.9369  ...   15.6094 -29.7063  40.1361
 -31.5056   3.3648   1.4726  ...    2.8047  -9.6514 -23.4909
[torch.FloatTensor of size 2x4x512]
  • 调用:
pe = PositionalEncoding(d_model, dropout, max_len)
pe_result = pe(x)
print("pe_result:", pe_result)
  • 输出效果:
pe_result: Variable containing:
( 0 ,.,.) = 
 -19.7050   0.0000   0.0000  ...  -11.7557  -0.0000  23.4553
  -1.4668 -62.2510  -2.4012  ...   66.5860 -24.4578 -37.7469
   9.8642 -41.6497 -11.4968  ...  -21.1293 -42.0945  50.7943
   0.0000  34.1785 -33.0712  ...   48.5520   3.2540  54.1348

( 1 ,.,.) = 
   7.7598 -21.0359  15.0595  ...  -35.6061  -0.0000   4.1772
 -38.7230   8.6578  34.2935  ...  -43.3556  26.6052   4.3084
  24.6962  37.3626 -26.9271  ...   49.8989   0.0000  44.9158
 -28.8435 -48.5963  -0.9892  ...  -52.5447  -4.1475  -3.0450
[torch.FloatTensor of size 2x4x512]

3.2 绘制词汇向量中特征的分布曲线¶

import matplotlib.pyplot as plt
import numpy as np

# 创建一张15 x 5大小的画布
plt.figure(figsize=(15, 5))

# 实例化PositionalEncoding类得到pe对象, 输入参数是20和0
pe = PositionalEncoding(20, 0)

# 然后向pe传入被Variable封装的tensor, 这样pe会直接执行forward函数, 
# 且这个tensor里的数值都是0, 被处理后相当于位置编码张量
y = pe(Variable(torch.zeros(1, 100, 20)))

# 然后定义画布的横纵坐标, 横坐标到100的长度, 纵坐标是某一个词汇中的某维特征在不同长度下对应的值
# 因为总共有20维之多, 我们这里只查看4,5,6,7维的值.
plt.plot(np.arange(100), y[0, :, 4:8].data.numpy())

# 在画布上填写维度提示信息
plt.legend(["dim %d"%p for p in [4,5,6,7]])
  • 输出效果:
  • 效果分析:
  • 每条颜色的曲线代表某一个词汇中的特征在不同位置的含义.
  • 保证同一词汇随着所在位置不同它对应位置嵌入向量会发生变化.
  • 正弦波和余弦波的值域范围都是1到-1这又很好的控制了嵌入数值的大小, 有助于梯度的快速计算.

4 小结¶

  • 学习了文本嵌入层的作用:

      • 无论是源文本嵌入还是目标文本嵌入,都是为了将文本中词汇的数字表示转变为向量表示, 希望在这样的高维空间捕捉词汇间的关系.
  • 学习并实现了文本嵌入层的类: Embeddings

      • 初始化函数以d_model, 词嵌入维度, 和vocab, 词汇总数为参数, 内部主要使用了nn中的预定层Embedding进行词嵌入.
    • 在forward函数中, 将输入x传入到Embedding的实例化对象中, 然后乘以一个根号下d_model进行缩放, 控制数值大小.
    • 它的输出是文本嵌入后的结果.
  • 学习了位置编码器的作用:

      • 因为在Transformer的编码器结构中, 并没有针对词汇位置信息的处理,因此需要在Embedding层后加入位置编码器,将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中, 以弥补位置信息的缺失.
  • 学习并实现了位置编码器的类: PositionalEncoding

      • 初始化函数以d_model, dropout, max_len为参数, 分别代表d_model: 词嵌入维度, dropout: 置0比率, max_len: 每个句子的最大长度.
    • forward函数中的输入参数为x, 是Embedding层的输出.
    • 最终输出一个加入了位置编码信息的词嵌入张量.
  • 实现了绘制词汇向量中特征的分布曲线:

      • 保证同一词汇随着所在位置不同它对应位置嵌入向量会发生变化.
    • 正弦波和余弦波的值域范围都是1到-1, 这又很好的控制了嵌入数值的大小, 有助于梯度的快速计算.

4 编码器部分实现

学习目标¶

  • 了解编码器中各个组成部分的作用.
  • 掌握编码器中各个组成部分的实现过程.

1 编码器介绍¶

编码器部分:

  • 由N个编码器层堆叠而成
  • 每个编码器层由两个子层连接结构组成
  • 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
  • 第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接

2 掩码张量¶

2.1 掩码张量介绍¶

  • 掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于是0位置被遮掩还是1位置被遮掩可以自定义,因此它的作用就是让另外一个张量中的一些数值被遮掩,也可以说被替换, 它的表现形式是一个张量.

2.2 掩码张量的作用¶

  • 在transformer中, 掩码张量的主要作用在应用attention(将在下一小节讲解)时,有一些生成的attention张量中的值计算有可能已知了未来信息而得到的,未来信息被看到是因为训练时会把整个输出结果都一次性进行Embedding,但是理论上解码器的的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,因此,未来的信息可能被提前利用. 所以,我们会进行遮掩. 关于解码器的有关知识将在后面的章节中讲解.

2.3 生成掩码张量的代码分析¶

def subsequent_mask(size):
    """生成向后遮掩的掩码张量, 参数size是掩码张量最后两个维度的大小, 它的最后两维形成一个方阵"""
    # 在函数中, 首先定义掩码张量的形状
    attn_shape = (1, size, size)

    # 然后使用np.ones方法向这个形状中添加1元素,形成上三角阵, 最后为了节约空间, 
    # 再使其中的数据类型变为无符号8位整形unit8 
    subsequent_mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')

    # 最后将numpy类型转化为torch中的tensor, 内部做一个1 - 的操作, 
    # 在这个其实是做了一个三角阵的反转, subsequent_mask中的每个元素都会被1减, 
    # 如果是0, subsequent_mask中的该位置由0变成1
    # 如果是1, subsequent_mask中的该位置由1变成0 
    return torch.from_numpy(1 - subsequent_mask)
  • np.triu演示:
>>> np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]], k=-1)
array([[ 1,  2,  3],
       [ 4,  5,  6],
       [ 0,  8,  9],
       [ 0,  0, 12]])


>>> np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]], k=0)
array([[ 1,  2,  3],
       [ 0,  5,  6],
       [ 0,  0,  9],
       [ 0,  0, 0]])


>>> np.triu([[1,2,3],[4,5,6],[7,8,9],[10,11,12]], k=1)
array([[ 0,  2,  3],
       [ 0,  0,  6],
       [ 0,  0,  0],
       [ 0,  0, 0]])
  • 输入实例:
# 生成的掩码张量的最后两维的大小
size = 5
  • 调用:
sm = subsequent_mask(size)
print("sm:", sm)
  • 输出效果:
# 最后两维形成一个下三角阵
sm: (0 ,.,.) = 
  1  0  0  0  0
  1  1  0  0  0
  1  1  1  0  0
  1  1  1  1  0
  1  1  1  1  1
[torch.ByteTensor of size 1x5x5]

2.4 掩码张量的可视化¶

plt.figure(figsize=(5,5))
plt.imshow(subsequent_mask(20)[0])
plt.show()
  • 输出效果:
  • 效果分析:
  • 通过观察可视化方阵, 黄色是1的部分, 这里代表被遮掩, 紫色代表没有被遮掩的信息, 横坐标代表目标词汇的位置, 纵坐标代表可查看的位置;
  • 我们看到, 在0的位置我们一看望过去都是黄色的, 都被遮住了,1的位置一眼望过去还是黄色, 说明第一次词还没有产生, 从第二个位置看过去, 就能看到位置1的词, 其他位置看不到, 以此类推.

2.5 掩码张量总结¶

  • 学习了什么是掩码张量:
      • 掩代表遮掩,码就是我们张量中的数值,它的尺寸不定,里面一般只有1和0的元素,代表位置被遮掩或者不被遮掩,至于是0位置被遮掩还是1位置被遮掩可以自定义,因此它的作用就是让另外一个张量中的一些数值被遮掩, 也可以说被替换, 它的表现形式是一个张量.
  • 学习了掩码张量的作用:
      • 在transformer中, 掩码张量的主要作用在应用attention(将在下一小节讲解)时,有一些生成的attetion张量中的值计算有可能已知量未来信息而得到的,未来信息被看到是因为训练时会把整个输出结果都一次性进行Embedding,但是理论上解码器的的输出却不是一次就能产生最终结果的,而是一次次通过上一次结果综合得出的,因此,未来的信息可能被提前利用. 所以,我们会进行遮掩. 关于解码器的有关知识将在后面的章节中讲解.
  • 学习并实现了生成向后遮掩的掩码张量函数: subsequent_mask
      • 它的输入是size, 代表掩码张量的大小.
    • 它的输出是一个最后两维形成1方阵的下三角阵.
    • 最后对生成的掩码张量进行了可视化分析, 更深一步理解了它的用途.

3 注意力机制¶

我们这里使用的注意力的计算规则:

image-20250715194827228

3.1 注意力计算规则的代码分析¶

import torch.nn.functional as F

def attention(query, key, value, mask=None, dropout=None):
    """注意力机制的实现, 输入分别是query, key, value, mask: 掩码张量, 
       dropout是nn.Dropout层的实例化对象, 默认为None"""
    # 在函数中, 首先取query的最后一维的大小, 一般情况下就等同于我们的词嵌入维度, 命名为d_k
    d_k = query.size(-1)
    # 按照注意力公式, 将query与key的转置相乘, 这里面key是将最后两个维度进行转置, 再除以缩放系数根号下d_k, 这种计算方法也称为缩放点积注意力计算.
    # 得到注意力得分张量scores
    scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(d_k)

    # 接着判断是否使用掩码张量
    if mask is not None:
        # 使用tensor的masked_fill方法, 将掩码张量和scores张量每个位置一一比较, 如果掩码张量处为0
        # 则对应的scores张量用-1e9这个值来替换, 如下演示
        scores = scores.masked_fill(mask == 0, -1e9)

    # 对scores的最后一维进行softmax操作, 使用F.softmax方法, 第一个参数是softmax对象, 第二个是目标维度.
    # 这样获得最终的注意力张量
    p_attn = F.softmax(scores, dim = -1)

    # 之后判断是否使用dropout进行随机置0
    if dropout is not None:
        # 将p_attn传入dropout对象中进行'丢弃'处理
        p_attn = dropout(p_attn)

    # 最后, 根据公式将p_attn与value张量相乘获得最终的query注意力表示, 同时返回注意力张量
    return torch.matmul(p_attn, value), p_attn
  • tensor.masked_fill演示:
>>> input = Variable(torch.randn(5, 5))
>>> input 
Variable containing:
 2.0344 -0.5450  0.3365 -0.1888 -2.1803
 1.5221 -0.3823  0.8414  0.7836 -0.8481
-0.0345 -0.8643  0.6476 -0.2713  1.5645
 0.8788 -2.2142  0.4022  0.1997  0.1474
 2.9109  0.6006 -0.6745 -1.7262  0.6977
[torch.FloatTensor of size 5x5]

>>> mask = Variable(torch.zeros(5, 5))
>>> mask
Variable containing:
 0  0  0  0  0
 0  0  0  0  0
 0  0  0  0  0
 0  0  0  0  0
 0  0  0  0  0
[torch.FloatTensor of size 5x5]

>>> input.masked_fill(mask == 0, -1e9)
Variable containing:
-1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
-1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
-1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
-1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
-1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09 -1.0000e+09
[torch.FloatTensor of size 5x5]
  • 输入参数:
# 我们令输入的query, key, value都相同, 位置编码的输出
query = key = value = pe_result
Variable containing:
( 0 ,.,.) = 
  46.5196  16.2057 -41.5581  ...  -16.0242 -17.8929 -43.0405
 -32.6040  16.1096 -29.5228  ...    4.2721  20.6034  -1.2747
 -18.6235  14.5076  -2.0105  ...   15.6462 -24.6081 -30.3391
   0.0000 -66.1486 -11.5123  ...   20.1519  -4.6823   0.4916

( 1 ,.,.) = 
 -24.8681   7.5495  -5.0765  ...   -7.5992 -26.6630  40.9517
  13.1581  -3.1918 -30.9001  ...   25.1187 -26.4621   2.9542
 -49.7690 -42.5019   8.0198  ...   -5.4809  25.9403 -27.4931
 -52.2775  10.4006   0.0000  ...   -1.9985   7.0106  -0.5189
[torch.FloatTensor of size 2x4x512]
  • 调用:
attn, p_attn = attention(query, key, value)
print("attn:", attn)
print("p_attn:", p_attn)
  • 输出效果:
# 将得到两个结果
# query的注意力表示:
attn: Variable containing:
( 0 ,.,.) = 
   12.8269    7.7403   41.2225  ...     1.4603   27.8559  -12.2600
   12.4904    0.0000   24.1575  ...     0.0000    2.5838   18.0647
  -32.5959   -4.6252  -29.1050  ...     0.0000  -22.6409  -11.8341
    8.9921  -33.0114   -0.7393  ...     4.7871   -5.7735    8.3374

( 1 ,.,.) = 
  -25.6705   -4.0860  -36.8226  ...    37.2346  -27.3576    2.5497
  -16.6674   73.9788  -33.3296  ...    28.5028   -5.5488  -13.7564
    0.0000  -29.9039   -3.0405  ...     0.0000   14.4408   14.8579
   30.7819    0.0000   21.3908  ...   -29.0746    0.0000   -5.8475
[torch.FloatTensor of size 2x4x512]

# 注意力张量:
p_attn: Variable containing:
(0 ,.,.) = 
  1  0  0  0
  0  1  0  0
  0  0  1  0
  0  0  0  1

(1 ,.,.) = 
  1  0  0  0
  0  1  0  0
  0  0  1  0
  0  0  0  1
[torch.FloatTensor of size 2x4x4]

3.2 带有mask的输入参数:¶

query = key = value = pe_result

# 令mask为一个2x4x4的零张量
mask = Variable(torch.zeros(2, 4, 4))
  • 调用:
attn, p_attn = attention(query, key, value, mask=mask)
print("attn:", attn)
print("p_attn:", p_attn)
  • 带有mask的输出效果:
# query的注意力表示:
attn: Variable containing:
( 0 ,.,.) = 
   0.4284  -7.4741   8.8839  ...    1.5618   0.5063   0.5770
   0.4284  -7.4741   8.8839  ...    1.5618   0.5063   0.5770
   0.4284  -7.4741   8.8839  ...    1.5618   0.5063   0.5770
   0.4284  -7.4741   8.8839  ...    1.5618   0.5063   0.5770

( 1 ,.,.) = 
  -2.8890   9.9972 -12.9505  ...    9.1657  -4.6164  -0.5491
  -2.8890   9.9972 -12.9505  ...    9.1657  -4.6164  -0.5491
  -2.8890   9.9972 -12.9505  ...    9.1657  -4.6164  -0.5491
  -2.8890   9.9972 -12.9505  ...    9.1657  -4.6164  -0.5491
[torch.FloatTensor of size 2x4x512]

# 注意力张量:
p_attn: Variable containing:
(0 ,.,.) = 
  0.2500  0.2500  0.2500  0.2500
  0.2500  0.2500  0.2500  0.2500
  0.2500  0.2500  0.2500  0.2500
  0.2500  0.2500  0.2500  0.2500

(1 ,.,.) = 
  0.2500  0.2500  0.2500  0.2500
  0.2500  0.2500  0.2500  0.2500
  0.2500  0.2500  0.2500  0.2500
  0.2500  0.2500  0.2500  0.2500
[torch.FloatTensor of size 2x4x4]

3.3 注意力机制总结¶

  • 学习并实现了注意力计算规则的函数: attention - 它的输入就是Q,K,V以及mask和dropout, mask用于掩码, dropout用于随机置0.
    • 它的输出有两个, query的注意力表示以及注意力张量.

4 多头注意力机制¶

4.1 多头注意力机制概念¶

  • 从多头注意力的结构图中,貌似这个所谓的多个头就是指多组线性变换层,其实并不是,我只有使用了一组线性变化层,即三个变换张量对Q,K,V分别进行线性变换,这些变换不会改变原有张量的尺寸,因此每个变换矩阵都是方阵,得到输出结果后,多头的作用才开始显现,每个头开始从词义层面分割输出的张量,也就是每个头都想获得一组Q,K,V进行注意力机制的计算,但是句子中的每个词的表示只获得一部分,也就是只分割了最后一维的词嵌入向量. 这就是所谓的多头,将每个头的获得的输入送到注意力机制中, 就形成多头注意力机制.

4.2 多头注意力机制结构图¶

4.3 多头注意力机制的作用¶

  • 这种结构设计能让每个注意力机制去优化每个词汇的不同特征部分,从而均衡同一种注意力机制可能产生的偏差,让词义拥有来自更多元的表达,实验表明可以从而提升模型效果.

5 前馈全连接层¶

5.1 前馈全连接层¶

  • 在Transformer中前馈全连接层就是具有两层线性层的全连接网络.
  • 前馈全连接层的作用: - 考虑注意力机制可能对复杂过程的拟合程度不够, 通过增加两层网络来增强模型的能力.

5.2 前馈全连接层的代码分析¶

# 通过类PositionwiseFeedForward来实现前馈全连接层
class PositionwiseFeedForward(nn.Module):
    def __init__(self, d_model, d_ff, dropout=0.1):
        """初始化函数有三个输入参数分别是d_model, d_ff,和dropout=0.1,第一个是线性层的输入维度也是第二个线性层的输出维度,
           因为我们希望输入通过前馈全连接层后输入和输出的维度不变. 第二个参数d_ff就是第二个线性层的输入维度和第一个线性层的输出维度. 
           最后一个是dropout置0比率."""
        super(PositionwiseFeedForward, self).__init__()

        # 首先按照我们预期使用nn实例化了两个线性层对象,self.w1和self.w2
        # 它们的参数分别是d_model, d_ff和d_ff, d_model
        self.w1 = nn.Linear(d_model, d_ff)
        self.w2 = nn.Linear(d_ff, d_model)
        # 然后使用nn的Dropout实例化了对象self.dropout
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        """输入参数为x,代表来自上一层的输出"""
        # 首先经过第一个线性层,然后使用Funtional中relu函数进行激活,
        # 之后再使用dropout进行随机置0,最后通过第二个线性层w2,返回最终结果.
        return self.w2(self.dropout(F.relu(self.w1(x))))
  • ReLU函数公式: ReLU(x)=max(0, x)
  • ReLU函数图像:
  • 实例化参数:
d_model = 512

# 线性变化的维度
d_ff = 64

dropout = 0.2
  • 输入参数:
# 输入参数x可以是注意力层的输出
x = attn
tensor([[[-0.3075,  1.5687, -2.5693,  ..., -1.1098,  0.0878, -3.3609],
         [ 3.8065, -2.4538, -0.3708,  ..., -1.5205, -1.1488, -1.3984],
         [ 2.4190,  0.5376, -2.8475,  ...,  1.4218, -0.4488, -0.2984],
         [ 2.9356,  0.3620, -3.8722,  ..., -0.7996,  0.1468,  1.0345]],

        [[ 1.1423,  0.6038,  0.0954,  ...,  2.2679, -5.7749,  1.4132],
         [ 2.4066, -0.2777,  2.8102,  ...,  0.1137, -3.9517, -2.9246],
         [ 5.8201,  1.1534, -1.9191,  ...,  0.1410, -7.6110,  1.0046],
         [ 3.1209,  1.0008, -0.5317,  ...,  2.8619, -6.3204, -1.3435]]],
       grad_fn=<AddBackward0>)
torch.Size([2, 4, 512])
  • 调用:
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
ff_result = ff(x)
print(ff_result)
print(ff_result.shape)
  • 输出效果:
tensor([[[-1.9488e+00, -3.4060e-01, -1.1216e+00,  ...,  1.8203e-01,
          -2.6336e+00,  2.0917e-03],
         [-2.5875e-02,  1.1523e-01, -9.5437e-01,  ..., -2.6257e-01,
          -5.7620e-01, -1.9225e-01],
         [-8.7508e-01,  1.0092e+00, -1.6515e+00,  ...,  3.4446e-02,
          -1.5933e+00, -3.1760e-01],
         [-2.7507e-01,  4.7225e-01, -2.0318e-01,  ...,  1.0530e+00,
          -3.7910e-01, -9.7730e-01]],

        [[-2.2575e+00, -2.0904e+00,  2.9427e+00,  ...,  9.6574e-01,
          -1.9754e+00,  1.2797e+00],
         [-1.5114e+00, -4.7963e-01,  1.2881e+00,  ..., -2.4882e-02,
          -1.5896e+00, -1.0350e+00],
         [ 1.7416e-01, -4.0688e-01,  1.9289e+00,  ..., -4.9754e-01,
          -1.6320e+00, -1.5217e+00],
         [-1.0874e-01, -3.3842e-01,  2.9379e-01,  ..., -5.1276e-01,
          -1.6150e+00, -1.1295e+00]]], grad_fn=<AddBackward0>)
torch.Size([2, 4, 512])

5.3 前馈全连接层总结¶

  • 学习了什么是前馈全连接层: - 在Transformer中前馈全连接层就是具有两层线性层的全连接网络.
  • 学习了前馈全连接层的作用: - 考虑注意力机制可能对复杂过程的拟合程度不够, 通过增加两层网络来增强模型的能力.
  • 学习并实现了前馈全连接层的类: PositionwiseFeedForward - 它的实例化参数为d_model, d_ff, dropout, 分别代表词嵌入维度, 线性变换维度, 和置零比率.
    • 它的输入参数x, 表示上层的输出.
    • 它的输出是经过2层线性网络变换的特征表示.

6 规范化层¶

6.1 规范化层的作用¶

  • 它是所有深层网络模型都需要的标准网络层,因为随着网络层数的增加,通过多层的计算后参数可能开始出现过大或过小的情况,这样可能会导致学习过程出现异常,模型可能收敛非常的慢. 因此都会在一定层数后接规范化层进行数值的规范化,使其特征数值在合理范围内.

6.2 规范化层的代码实现¶

# 通过LayerNorm实现规范化层的类
class LayerNorm(nn.Module):
    def __init__(self, features, eps=1e-6):
        """初始化函数有两个参数, 一个是features, 表示词嵌入的维度,
           另一个是eps它是一个足够小的数, 在规范化公式的分母中出现,
           防止分母为0.默认是1e-6."""
        super(LayerNorm, self).__init__()

        # 根据features的形状初始化两个参数张量a2,和b2,第一个初始化为1张量,
        # 也就是里面的元素都是1,第二个初始化为0张量,也就是里面的元素都是0,这两个张量就是规范化层的参数,
        # 因为直接对上一层得到的结果做规范化公式计算,将改变结果的正常表征,因此就需要有参数作为调节因子,
        # 使其即能满足规范化要求,又能不改变针对目标的表征.最后使用nn.parameter封装,代表他们是模型的参数。
        self.a2 = nn.Parameter(torch.ones(features))
        self.b2 = nn.Parameter(torch.zeros(features))

        # 把eps传到类中
        self.eps = eps

    def forward(self, x):
        """输入参数x代表来自上一层的输出"""
        # 在函数中,首先对输入变量x求其最后一个维度的均值,并保持输出维度与输入维度一致.
        # 接着再求最后一个维度的标准差,然后就是根据规范化公式,用x减去均值除以标准差获得规范化的结果,
        # 最后对结果乘以我们的缩放参数,即a2,*号代表同型点乘,即对应位置进行乘法操作,加上位移参数b2.返回即可.
        mean = x.mean(-1, keepdim=True)
        std = x.std(-1, keepdim=True)
        return self.a2 * (x - mean) / (std + self.eps) + self.b2
  • 实例化参数:
features = d_model = 512
eps = 1e-6
  • 输入参数:
# 输入x来自前馈全连接层的输出
x = ff_result
tensor([[[-1.9488e+00, -3.4060e-01, -1.1216e+00,  ...,  1.8203e-01,
          -2.6336e+00,  2.0917e-03],
         [-2.5875e-02,  1.1523e-01, -9.5437e-01,  ..., -2.6257e-01,
          -5.7620e-01, -1.9225e-01],
         [-8.7508e-01,  1.0092e+00, -1.6515e+00,  ...,  3.4446e-02,
          -1.5933e+00, -3.1760e-01],
         [-2.7507e-01,  4.7225e-01, -2.0318e-01,  ...,  1.0530e+00,
          -3.7910e-01, -9.7730e-01]],

        [[-2.2575e+00, -2.0904e+00,  2.9427e+00,  ...,  9.6574e-01,
          -1.9754e+00,  1.2797e+00],
         [-1.5114e+00, -4.7963e-01,  1.2881e+00,  ..., -2.4882e-02,
          -1.5896e+00, -1.0350e+00],
         [ 1.7416e-01, -4.0688e-01,  1.9289e+00,  ..., -4.9754e-01,
          -1.6320e+00, -1.5217e+00],
         [-1.0874e-01, -3.3842e-01,  2.9379e-01,  ..., -5.1276e-01,
          -1.6150e+00, -1.1295e+00]]], grad_fn=<AddBackward0>)
torch.Size([2, 4, 512])
  • 调用:
ln = LayerNorm(features, eps)
ln_result = ln(x)
print(ln_result)
print(ln_result.shape)
  • 输出效果:
tensor([[[ 2.2697,  1.3911, -0.4417,  ...,  0.9937,  0.6589, -1.1902],
         [ 1.5876,  0.5182,  0.6220,  ...,  0.9836,  0.0338, -1.3393],
         [ 1.8261,  2.0161,  0.2272,  ...,  0.3004,  0.5660, -0.9044],
         [ 1.5429,  1.3221, -0.2933,  ...,  0.0406,  1.0603,  1.4666]],

        [[ 0.2378,  0.9952,  1.2621,  ..., -0.4334, -1.1644,  1.2082],
         [-1.0209,  0.6435,  0.4235,  ..., -0.3448, -1.0560,  1.2347],
         [-0.8158,  0.7118,  0.4110,  ...,  0.0990, -1.4833,  1.9434],
         [ 0.9857,  2.3924,  0.3819,  ...,  0.0157, -1.6300,  1.2251]]],
       grad_fn=<AddBackward0>)
torch.Size([2, 4, 512])

6.3 规范化层总结:¶

  • 学习了规范化层的作用:

      • 它是所有深层网络模型都需要的标准网络层,因为随着网络层数的增加,通过多层的计算后参数可能开始出现过大或过小的情况,这样可能会导致学习过程出现异常,模型可能收敛非常的慢. 因此都会在一定层数后接规范化层进行数值的规范化,使其特征数值在合理范围内.
  • 学习并实现了规范化层的类: LayerNorm

      • 它的实例化参数有两个, features和eps,分别表示词嵌入特征大小,和一个足够小的数.
    • 它的输入参数x代表来自上一层的输出.
    • 它的输出就是经过规范化的特征表示.

5 解码器部分实现

学习目标¶

  • 了解解码器中各个组成部分的作用.
  • 掌握解码器中各个组成部分的实现过程.

1 解码器介绍¶

解码器部分:

  • 由N个解码器层堆叠而成
  • 每个解码器层由三个子层连接结构组成
  • 第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
  • 第二个子层连接结构包括一个多头注意力子层和规范化层以及一个残差连接
  • 第三个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
  • 说明:
  • 解码器层中的各个部分,如,多头注意力机制,规范化层,前馈全连接网络,子层连接结构都与编码器中的实现相同. 因此这里可以直接拿来构建解码器层.

2 解码器层¶

2.1 解码器层的作用:¶

  • 作为解码器的组成单元, 每个解码器层根据给定的输入向目标方向进行特征提取操作,即解码过程.

2.2 解码器层的代码实现¶

# 使用DecoderLayer的类实现解码器层
class DecoderLayer(nn.Module):
    def __init__(self, size, self_attn, src_attn, feed_forward, dropout):
        """初始化函数的参数有5个, 分别是size,代表词嵌入的维度大小, 同时也代表解码器层的尺寸,
            第二个是self_attn,多头自注意力对象,也就是说这个注意力机制需要Q=K=V, 
            第三个是src_attn,多头注意力对象,这里Q!=K=V, 第四个是前馈全连接层对象,最后就是droupout置0比率.
        """
        super(DecoderLayer, self).__init__()
        # 在初始化函数中, 主要就是将这些输入传到类中
        self.size = size
        self.self_attn = self_attn
        self.src_attn = src_attn
        self.feed_forward = feed_forward
        # 按照结构图使用clones函数克隆三个子层连接对象.
        self.sublayer = clones(SublayerConnection(size, dropout), 3)

    def forward(self, x, memory, source_mask, target_mask):
        """forward函数中的参数有4个,分别是来自上一层的输入x,
           来自编码器层的语义存储变量mermory, 以及源数据掩码张量和目标数据掩码张量.
        """
        # 将memory表示成m方便之后使用
        m = memory

        # 将x传入第一个子层结构,第一个子层结构的输入分别是x和self-attn函数,因为是自注意力机制,所以Q,K,V都是x,
        # 最后一个参数是目标数据掩码张量,这时要对目标数据进行遮掩,因为此时模型可能还没有生成任何目标数据,
        # 比如在解码器准备生成第一个字符或词汇时,我们其实已经传入了第一个字符以便计算损失,
        # 但是我们不希望在生成第一个字符时模型能利用这个信息,因此我们会将其遮掩,同样生成第二个字符或词汇时,
        # 模型只能使用第一个字符或词汇信息,第二个字符以及之后的信息都不允许被模型使用.
        x = self.sublayer[0](x, lambda x: self.self_attn(x, x, x, target_mask))

        # 接着进入第二个子层,这个子层中常规的注意力机制,q是输入x; k,v是编码层输出memory, 
        # 同样也传入source_mask,但是进行源数据遮掩的原因并非是抑制信息泄漏,而是遮蔽掉对结果没有意义的字符而产生的注意力值,
        # 以此提升模型效果和训练速度. 这样就完成了第二个子层的处理.
        x = self.sublayer[1](x, lambda x: self.src_attn(x, m, m, source_mask))

        # 最后一个子层就是前馈全连接子层,经过它的处理后就可以返回结果.这就是我们的解码器层结构.
        return self.sublayer[2](x, self.feed_forward)
  • 实例化参数:
# 类的实例化参数与解码器层类似, 相比多出了src_attn, 但是和self_attn是同一个类.
head = 8
size = 512
d_model = 512
d_ff = 64
dropout = 0.2
self_attn = src_attn = MultiHeadedAttention(head, d_model, dropout)

# 前馈全连接层也和之前相同 
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
  • 输入参数:
# x是来自目标数据的词嵌入表示, 但形式和源数据的词嵌入表示相同, 这里使用per充当.
x = pe_result

# memory是来自编码器的输出
memory = en_result

# 实际中source_mask和target_mask并不相同, 这里为了方便计算使他们都为mask
mask = Variable(torch.zeros(8, 4, 4))
source_mask = target_mask = mask
  • 调用:
dl = DecoderLayer(size, self_attn, src_attn, ff, dropout)
dl_result = dl(x, memory, source_mask, target_mask)
print(dl_result)
print(dl_result.shape)
  • 输出效果:
tensor([[[ 1.9604e+00,  3.9288e+01, -5.2422e+01,  ...,  2.1041e-01,
          -5.5063e+01,  1.5233e-01],
         [ 1.0135e-01, -3.7779e-01,  6.5491e+01,  ...,  2.8062e+01,
          -3.7780e+01, -3.9577e+01],
         [ 1.9526e+01, -2.5741e+01,  2.6926e-01,  ..., -1.5316e+01,
           1.4543e+00,  2.7714e+00],
         [-2.1528e+01,  2.0141e+01,  2.1999e+01,  ...,  2.2099e+00,
          -1.7267e+01, -1.6687e+01]],

        [[ 6.7259e+00, -2.6918e+01,  1.1807e+01,  ..., -3.6453e+01,
          -2.9231e+01,  1.1288e+01],
         [ 7.7484e+01, -5.0572e-01, -1.3096e+01,  ...,  3.6302e-01,
           1.9907e+01, -1.2160e+00],
         [ 2.6703e+01,  4.4737e+01, -3.1590e+01,  ...,  4.1540e-03,
           5.2587e+00,  5.2382e+00],
         [ 4.7435e+01, -3.7599e-01,  5.0898e+01,  ...,  5.6361e+00,
           3.5891e+01,  1.5697e+01]]], grad_fn=<AddBackward0>)
torch.Size([2, 4, 512])

2.3 解码器层总结¶

  • 学习了解码器层的作用:
      • 作为解码器的组成单元, 每个解码器层根据给定的输入向目标方向进行特征提取操作,即解码过程.
  • 学习并实现了解码器层的类: DecoderLayer
      • 类的初始化函数的参数有5个, 分别是size,代表词嵌入的维度大小, 同时也代表解码器层的尺寸,第二个是self_attn,多头自注意力对象,也就是说这个注意力机制需要Q=K=V,第三个是src_attn,多头注意力对象,这里Q!=K=V, 第四个是前馈全连接层对象,最后就是droupout置0比率.
    • forward函数的参数有4个,分别是来自上一层的输入x,来自编码器层的语义存储变量mermory, 以及源数据掩码张量和目标数据掩码张量.
    • 最终输出了由编码器输入和目标数据一同作用的特征提取结果.

3 解码器¶

3.1 解码器的作用¶

  • 根据编码器的结果以及上一次预测的结果, 对下一次可能出现的'值'进行特征表示.

3.2 解码器的代码分析¶

# 使用类Decoder来实现解码器
class Decoder(nn.Module):
    def __init__(self, layer, N):
        """初始化函数的参数有两个,第一个就是解码器层layer,第二个是解码器层的个数N."""
        super(Decoder, self).__init__()
        # 首先使用clones方法克隆了N个layer,然后实例化了一个规范化层. 
        # 因为数据走过了所有的解码器层后最后要做规范化处理. 
        self.layers = clones(layer, N)
        self.norm = LayerNorm(layer.size)

    def forward(self, x, memory, source_mask, target_mask):
        """forward函数中的参数有4个,x代表目标数据的嵌入表示,memory是编码器层的输出,
           source_mask, target_mask代表源数据和目标数据的掩码张量"""

        # 然后就是对每个层进行循环,当然这个循环就是变量x通过每一个层的处理,
        # 得出最后的结果,再进行一次规范化返回即可. 
        for layer in self.layers:
            x = layer(x, memory, source_mask, target_mask)
        return self.norm(x)
  • 实例化参数:
# 分别是解码器层layer和解码器层的个数N
size = 512
d_model = 512
head = 8
d_ff = 64
dropout = 0.2
c = copy.deepcopy
attn = MultiHeadedAttention(head, d_model)
ff = PositionwiseFeedForward(d_model, d_ff, dropout)
layer = DecoderLayer(d_model, c(attn), c(attn), c(ff), dropout)
N = 8
  • 输入参数:
# 输入参数与解码器层的输入参数相同
x = pe_result
memory = en_result
mask = Variable(torch.zeros(8, 4, 4))
source_mask = target_mask = mask
  • 调用:
de = Decoder(layer, N)
de_result = de(x, memory, source_mask, target_mask)
print(de_result)
print(de_result.shape)
  • 输出效果:
tensor([[[ 0.9898, -0.3216, -1.2439,  ...,  0.7427, -0.0717, -0.0814],
         [-0.7432,  0.6985,  1.5551,  ...,  0.5232, -0.5685,  1.3387],
         [ 0.2149,  0.5274, -1.6414,  ...,  0.7476,  0.5082, -3.0132],
         [ 0.4408,  0.9416,  0.4522,  ..., -0.1506,  1.5591, -0.6453]],

        [[-0.9027,  0.5874,  0.6981,  ...,  2.2899,  0.2933, -0.7508],
         [ 1.2246, -1.0856, -0.2497,  ..., -1.2377,  0.0847, -0.0221],
         [ 3.4012, -0.4181, -2.0968,  ..., -1.5427,  0.1090, -0.3882],
         [-0.1050, -0.5140, -0.6494,  ..., -0.4358, -1.2173,  0.4161]]],
       grad_fn=<AddBackward0>)
torch.Size([2, 4, 512])

3.3 解码器总结¶

  • 学习了解码器的作用:

    • 根据编码器的结果以及上一次预测的结果, 对下一次可能出现的'值'进行特征表示.
  • 学习并实现了解码器的类: Decoder

    • 类的初始化函数的参数有两个,第一个就是解码器层layer,第二个是解码器层的个数N.
    • forward函数中的参数有4个,x代表目标数据的嵌入表示,memory是编码器层的输出,src_mask, tgt_mask代表源数据和目标数据的掩码张量.
    • 输出解码过程的最终特征表示.

6 输出部分实现

学习目标¶

  • 了解线性层和softmax的作用.
  • 掌握线性层和softmax的实现过程.

1 输出部分介绍¶

  • 输出部分包含: - 线性层
    • softmax层

2 线性层的作用¶

  • 通过对上一步的线性变化得到指定维度的输出, 也就是转换维度的作用.

3 softmax层的作用¶

  • 使最后一维的向量中的数字缩放到0-1的概率值域内, 并满足他们的和为1.

3.1 线性层和softmax层的代码分析¶

# nn.functional工具包装载了网络层中那些只进行计算, 而没有参数的层
import torch.nn.functional as F

# 将线性层和softmax计算层一起实现, 因为二者的共同目标是生成最后的结构
# 因此把类的名字叫做Generator, 生成器类
class Generator(nn.Module):
    def __init__(self, d_model, vocab_size):
        """初始化函数的输入参数有两个, d_model代表词嵌入维度, vocab_size代表词表大小."""
        super(Generator, self).__init__()
        # 首先就是使用nn中的预定义线性层进行实例化, 得到一个对象self.project等待使用, 
        # 这个线性层的参数有两个, 就是初始化函数传进来的两个参数: d_model, vocab_size
        self.project = nn.Linear(d_model, vocab_size)

    def forward(self, x):
        """前向逻辑函数中输入是上一层的输出张量x"""
        # 在函数中, 首先使用上一步得到的self.project对x进行线性变化, 
        # 然后使用F中已经实现的log_softmax进行的softmax处理.
        # 在这里之所以使用log_softmax是因为和我们这个pytorch版本的损失函数实现有关, 在其他版本中将修复.
        # log_softmax就是对softmax的结果又取了对数, 因为对数函数是单调递增函数, 
        # 因此对最终我们取最大的概率值没有影响. 最后返回结果即可.
        return F.log_softmax(self.project(x), dim=-1)
  • nn.Linear演示:
>>> m = nn.Linear(20, 30)
>>> input = torch.randn(128, 20)
>>> output = m(input)
>>> print(output.size())
torch.Size([128, 30])
  • 实例化参数:
# 词嵌入维度是512维
d_model = 512

# 词表大小是1000
vocab_size = 1000
  • 输入参数:
# 输入x是上一层网络的输出, 我们使用来自解码器层的输出
x = de_result
  • 调用:
gen = Generator(d_model, vocab_size)
gen_result = gen(x)
print(gen_result)
print(gen_result.shape)
  • 输出效果:
tensor([[[-7.8098, -7.5260, -6.9244,  ..., -7.6340, -6.9026, -7.5232],
         [-6.9093, -7.3295, -7.2972,  ..., -6.6221, -7.2268, -7.0772],
         [-7.0263, -7.2229, -7.8533,  ..., -6.7307, -6.9294, -7.3042],
         [-6.5045, -6.0504, -6.6241,  ..., -5.9063, -6.5361, -7.1484]],

        [[-7.1651, -6.0224, -7.4931,  ..., -7.9565, -8.0460, -6.6490],
         [-6.3779, -7.6133, -8.3572,  ..., -6.6565, -7.1867, -6.5112],
         [-6.4914, -6.9289, -6.2634,  ..., -6.2471, -7.5348, -6.8541],
         [-6.8651, -7.0460, -7.6239,  ..., -7.1411, -6.5496, -7.3749]]],
       grad_fn=<LogSoftmaxBackward>)
torch.Size([2, 4, 1000])

4 小结¶

  • 学习了输出部分包含:

    • 线性层
    • softmax层
  • 线性层的作用:

    • 通过对上一步的线性变化得到指定维度的输出, 也就是转换维度的作用.
  • softmax层的作用:

    • 使最后一维的向量中的数字缩放到0-1的概率值域内, 并满足他们的和为1.
  • 学习并实现了线性层和softmax层的类: Generator

    • 初始化函数的输入参数有两个, d_model代表词嵌入维度, vocab_size代表词表大小.
    • forward函数接受上一层的输出.
    • 最终获得经过线性层和softmax层处理的结果.

7 模型构建

学习目标¶

  • 掌握编码器-解码器结构的实现过程.
  • 掌握Transformer模型的构建过程.

1 模型构建介绍¶

通过上面的小节, 我们已经完成了所有组成部分的实现, 接下来就来实现完整的编码器-解码器结构.

  • Transformer总体架构图:

2 编码器-解码器结构的代码实现¶

# 使用EncoderDecoder类来实现编码器-解码器结构
class EncoderDecoder(nn.Module):
    def __init__(self, encoder, decoder, source_embed, target_embed, generator):
        """初始化函数中有5个参数, 分别是编码器对象, 解码器对象, 
           源数据嵌入函数, 目标数据嵌入函数,  以及输出部分的类别生成器对象
        """
        super(EncoderDecoder, self).__init__()
        # 将参数传入到类中
        self.encoder = encoder
        self.decoder = decoder
        self.src_embed = source_embed
        self.tgt_embed = target_embed
        self.generator = generator

    def forward(self, source, target, source_mask, target_mask):
        """在forward函数中,有四个参数, source代表源数据, target代表目标数据, 
           source_mask和target_mask代表对应的掩码张量"""

        # 在函数中, 将source, source_mask传入编码函数, 得到结果后,
        # 与source_mask,target,和target_mask一同传给解码函数.
        return self.decode(self.encode(source, source_mask), source_mask,
                            target, target_mask)

    def encode(self, source, source_mask):
        """编码函数, 以source和source_mask为参数"""
        # 使用src_embed对source做处理, 然后和source_mask一起传给self.encoder
        return self.encoder(self.src_embed(source), source_mask)

    def decode(self, memory, source_mask, target, target_mask):
        """解码函数, 以memory即编码器的输出, source_mask, target, target_mask为参数"""
        # 使用tgt_embed对target做处理, 然后和source_mask, target_mask, memory一起传给self.decoder
        return self.decoder(self.tgt_embed(target), memory, source_mask, target_mask)

  • 实例化参数
vocab_size = 1000
d_model = 512
encoder = en
decoder = de
source_embed = nn.Embedding(vocab_size, d_model)
target_embed = nn.Embedding(vocab_size, d_model)
generator = gen
  • 输入参数:
# 假设源数据与目标数据相同, 实际中并不相同
source = target = Variable(torch.LongTensor([[100, 2, 421, 508], [491, 998, 1, 221]]))

# 假设src_mask与tgt_mask相同,实际中并不相同
source_mask = target_mask = Variable(torch.zeros(8, 4, 4))
  • 调用:
ed = EncoderDecoder(encoder, decoder, source_embed, target_embed, generator)
ed_result = ed(source, target, source_mask, target_mask)
print(ed_result)
print(ed_result.shape)
  • 输出效果:
tensor([[[ 0.2102, -0.0826, -0.0550,  ...,  1.5555,  1.3025, -0.6296],
         [ 0.8270, -0.5372, -0.9559,  ...,  0.3665,  0.4338, -0.7505],
         [ 0.4956, -0.5133, -0.9323,  ...,  1.0773,  1.1913, -0.6240],
         [ 0.5770, -0.6258, -0.4833,  ...,  0.1171,  1.0069, -1.9030]],

        [[-0.4355, -1.7115, -1.5685,  ..., -0.6941, -0.1878, -0.1137],
         [-0.8867, -1.2207, -1.4151,  ..., -0.9618,  0.1722, -0.9562],
         [-0.0946, -0.9012, -1.6388,  ..., -0.2604, -0.3357, -0.6436],
         [-1.1204, -1.4481, -1.5888,  ..., -0.8816, -0.6497,  0.0606]]],
       grad_fn=<AddBackward0>)
torch.Size([2, 4, 512])
  • 接着将基于以上结构构建用于训练的模型.

3 Tansformer模型构建过程的代码分析¶

def make_model(source_vocab, target_vocab, N=6, 
               d_model=512, d_ff=2048, head=8, dropout=0.1):
    """该函数用来构建模型, 有7个参数,分别是源数据特征(词汇)总数,目标数据特征(词汇)总数,
       编码器和解码器堆叠数,词向量映射维度,前馈全连接网络中变换矩阵的维度,
       多头注意力结构中的多头数,以及置零比率dropout."""

    # 首先得到一个深度拷贝命令,接下来很多结构都需要进行深度拷贝,
    # 来保证他们彼此之间相互独立,不受干扰.
    c = copy.deepcopy

    # 实例化了多头注意力类,得到对象attn
    attn = MultiHeadedAttention(head, d_model)

    # 然后实例化前馈全连接类,得到对象ff 
    ff = PositionwiseFeedForward(d_model, d_ff, dropout)

    # 实例化位置编码类,得到对象position
    position = PositionalEncoding(d_model, dropout)

    # 根据结构图, 最外层是EncoderDecoder,在EncoderDecoder中,
    # 分别是编码器层,解码器层,源数据Embedding层和位置编码组成的有序结构,
    # 目标数据Embedding层和位置编码组成的有序结构,以及类别生成器层. 
    # 在编码器层中有attention子层以及前馈全连接子层,
    # 在解码器层中有两个attention子层以及前馈全连接层.
    model = EncoderDecoder(
        Encoder(EncoderLayer(d_model, c(attn), c(ff), dropout), N),
        Decoder(DecoderLayer(d_model, c(attn), c(attn), 
                             c(ff), dropout), N),
        nn.Sequential(Embeddings(d_model, source_vocab), c(position)),
        nn.Sequential(Embeddings(d_model, target_vocab), c(position)),
        Generator(d_model, target_vocab))

    # 模型结构完成后,接下来就是初始化模型中的参数,比如线性层中的变换矩阵
    # 这里一但判断参数的维度大于1,则会将其初始化成一个服从均匀分布的矩阵,
    for p in model.parameters():
        if p.dim() > 1:
            nn.init.xavier_uniform(p)
    return model
  • nn.init.xavier_uniform演示:
# 结果服从均匀分布U(-a, a)
>>> w = torch.empty(3, 5)
>>> w = nn.init.xavier_uniform_(w, gain=nn.init.calculate_gain('relu'))
>>> w
tensor([[-0.7742,  0.5413,  0.5478, -0.4806, -0.2555],
        [-0.8358,  0.4673,  0.3012,  0.3882, -0.6375],
        [ 0.4622, -0.0794,  0.1851,  0.8462, -0.3591]])
  • 输入参数:
source_vocab = 11
target_vocab = 11 
N = 6
# 其他参数都使用默认值 
  • 调用:
if __name__ == '__main__':
    res = make_model(source_vocab, target_vocab, N)
    print(res)
  • 输出效果:
# 根据Transformer结构图构建的最终模型结构
EncoderDecoder(
  (encoder): Encoder(
    (layers): ModuleList(
      (0): EncoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512)
            (1): Linear(in_features=512, out_features=512)
            (2): Linear(in_features=512, out_features=512)
            (3): Linear(in_features=512, out_features=512)
          )
          (dropout): Dropout(p=0.1)
        )
        (feed_forward): PositionwiseFeedForward(
          (w_1): Linear(in_features=512, out_features=2048)
          (w_2): Linear(in_features=2048, out_features=512)
          (dropout): Dropout(p=0.1)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm(
            )
            (dropout): Dropout(p=0.1)
          )
          (1): SublayerConnection(
            (norm): LayerNorm(
            )
            (dropout): Dropout(p=0.1)
          )
        )
      )
      (1): EncoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512)
            (1): Linear(in_features=512, out_features=512)
            (2): Linear(in_features=512, out_features=512)
            (3): Linear(in_features=512, out_features=512)
          )
          (dropout): Dropout(p=0.1)
        )
        (feed_forward): PositionwiseFeedForward(
          (w_1): Linear(in_features=512, out_features=2048)
          (w_2): Linear(in_features=2048, out_features=512)
          (dropout): Dropout(p=0.1)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm(
            )
            (dropout): Dropout(p=0.1)
          )
          (1): SublayerConnection(
            (norm): LayerNorm(
            )
            (dropout): Dropout(p=0.1)
          )
        )
      )
    )
    (norm): LayerNorm(
    )
  )
  (decoder): Decoder(
    (layers): ModuleList(
      (0): DecoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512)
            (1): Linear(in_features=512, out_features=512)
            (2): Linear(in_features=512, out_features=512)
            (3): Linear(in_features=512, out_features=512)
          )
          (dropout): Dropout(p=0.1)
        )
        (src_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512)
            (1): Linear(in_features=512, out_features=512)
            (2): Linear(in_features=512, out_features=512)
            (3): Linear(in_features=512, out_features=512)
          )
          (dropout): Dropout(p=0.1)
        )
        (feed_forward): PositionwiseFeedForward(
          (w_1): Linear(in_features=512, out_features=2048)
          (w_2): Linear(in_features=2048, out_features=512)
          (dropout): Dropout(p=0.1)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm(
            )
            (dropout): Dropout(p=0.1)
          )
          (1): SublayerConnection(
            (norm): LayerNorm(
            )
            (dropout): Dropout(p=0.1)
          )
          (2): SublayerConnection(
            (norm): LayerNorm(
            )
            (dropout): Dropout(p=0.1)
          )
        )
      )
      (1): DecoderLayer(
        (self_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512)
            (1): Linear(in_features=512, out_features=512)
            (2): Linear(in_features=512, out_features=512)
            (3): Linear(in_features=512, out_features=512)
          )
          (dropout): Dropout(p=0.1)
        )
        (src_attn): MultiHeadedAttention(
          (linears): ModuleList(
            (0): Linear(in_features=512, out_features=512)
            (1): Linear(in_features=512, out_features=512)
            (2): Linear(in_features=512, out_features=512)
            (3): Linear(in_features=512, out_features=512)
          )
          (dropout): Dropout(p=0.1)
        )
        (feed_forward): PositionwiseFeedForward(
          (w_1): Linear(in_features=512, out_features=2048)
          (w_2): Linear(in_features=2048, out_features=512)
          (dropout): Dropout(p=0.1)
        )
        (sublayer): ModuleList(
          (0): SublayerConnection(
            (norm): LayerNorm(
            )
            (dropout): Dropout(p=0.1)
          )
          (1): SublayerConnection(
            (norm): LayerNorm(
            )
            (dropout): Dropout(p=0.1)
          )
          (2): SublayerConnection(
            (norm): LayerNorm(
            )
            (dropout): Dropout(p=0.1)
          )
        )
      )
    )
    (norm): LayerNorm(
    )
  )
  (src_embed): Sequential(
    (0): Embeddings(
      (lut): Embedding(11, 512)
    )
    (1): PositionalEncoding(
      (dropout): Dropout(p=0.1)
    )
  )
  (tgt_embed): Sequential(
    (0): Embeddings(
      (lut): Embedding(11, 512)
    )
    (1): PositionalEncoding(
      (dropout): Dropout(p=0.1)
    )
  )
  (generator): Generator(
    (proj): Linear(in_features=512, out_features=11)
  )
)

4 小结¶

  • 学习并实现了编码器-解码器结构的类: EncoderDecoder

    • 类的初始化函数传入5个参数, 分别是编码器对象, 解码器对象, 源数据嵌入函数, 目标数据嵌入函数, 以及输出部分的类别生成器对象.
    • 类中共实现三个函数, forward, encode, decode
    • forward是主要逻辑函数, 有四个参数, source代表源数据, target代表目标数据, source_mask和target_mask代表对应的掩码张量.
    • encode是编码函数, 以source和source_mask为参数.
    • decode是解码函数, 以memory即编码器的输出, source_mask, target, target_mask为参数
  • 学习并实现了模型构建函数: make_model

    • 有7个参数,分别是源数据特征(词汇)总数,目标数据特征(词汇)总数,编码器和解码器堆叠数,词向量映射维度,前馈全连接网络中变换矩阵的维度,多头注意力结构中的多头数,以及置零比率dropout.
    • 该函数最后返回一个构建好的模型对象.

第五章 迁移学习

1 迁移学习概念

学习目标¶

  • 了解迁移学习中的有关概念.
  • 知道迁移学习的两种迁移方式.

1 迁移学习有关概念¶

  • 预训练模型
  • 微调
  • 微调脚本

1.1 预训练模型(Pretrained model):¶

  • 一般情况下预训练模型都是大型模型,具备复杂的网络结构,众多的参数量,以及在足够大的数据集下进行训练而产生的模型. 在NLP领域,预训练模型往往是语言模型,因为语言模型的训练是无监督的,可以获得大规模语料,同时语言模型又是许多典型NLP任务的基础,如机器翻译,文本生成,阅读理解等,常见的预训练模型有BERT, GPT, roBERTa, transformer-XL等.

1.2 微调(Fine-tuning):¶

  • 根据给定的预训练模型,改变它的部分参数或者为其新增部分输出结构后,通过在小部分数据集上训练,来使整个模型更好的适应特定任务.

1.3 微调脚本(Fine-tuning script):¶

  • 实现微调过程的代码文件。这些脚本文件中,应包括对预训练模型的调用,对微调参数的选定以及对微调结构的更改等,同时,因为微调是一个训练过程,它同样需要一些超参数的设定,以及损失函数和优化器的选取等, 因此微调脚本往往也包含了整个迁移学习的过程.
  • 关于微调脚本的说明: - 一般情况下,微调脚本应该由不同的任务类型开发者自己编写,但是由于目前研究的NLP任务类型(分类,提取,生成)以及对应的微调输出结构都是有限的,有些微调方式已经在很多数据集上被验证是有效的,因此微调脚本也可以使用已经完成的规范脚本.

1.4 两种迁移方式:¶

  • 直接使用预训练模型,进行相同任务的处理,不需要调整参数或模型结构,这些模型开箱即用。但是这种情况一般只适用于普适任务, 如: BERT模型。另外,很多预训练模型开发者为了达到开箱即用的效果,将模型结构分各个部分保存为不同的预训练模型,提供对应的加载方法来完成特定目标.
  • 更加主流的迁移学习方式是发挥预训练模型特征抽象的能力,然后再通过微调的方式,通过训练更新小部分参数以此来适应不同的任务。这种迁移方式需要提供小部分的标注数据来进行监督学习.

2 NLP中的标准数据集

学习目标¶

  • 了解NLP中GLUE标准数据集合的相关知识.
  • 掌握GLUE标准数据集合的下载方式, 数据样式及其对应的任务类型.

1 GLUE数据集合介绍¶

1.1 数据集合介绍¶

GLUE由纽约大学, 华盛顿大学, Google联合推出, 涵盖不同NLP任务类型, 截止至2020年1月其中包括11个子任务数据集, 成为衡量NLP研究发展的衡量标准.

  • CoLA 数据集
  • SST-2 数据集
  • MRPC 数据集
  • STS-B 数据集
  • QQP 数据集
  • MNLI 数据集
  • SNLI 数据集
  • QNLI 数据集
  • RTE 数据集
  • WNLI 数据集
  • diagnostics数据集(官方未完善)

1.2 数据集合路径¶

数据集在虚拟机/root/data/glue_data下
另外这GLUE的11个数据集都放到的百度云 , 需要的可以自取: GLUE数据集
提取码: b6se

2 GLUE子数据集的样式及其任务类型¶

2.1 CoLA数据集文件样式¶

  • 数据集释义:CoLA(The Corpus of Linguistic Acceptability,语言可接受性语料库)纽约大学发布的有关语法的数据集
  • 本质: 是对一个给定句子,判定其是否语法正确的单个句子的文本二分类任务.
- CoLA/
    - dev.tsv  
    - original/
    - test.tsv  
    - train.tsv
  • 文件样式说明:
  • 在使用中常用到的文件是train.tsv, dev.tsv, test.tsv, 分别代表训练集, 验证集和测试集. 其中train.tsv与dev.tsv数据样式相同, 都是带有标签的数据, 其中test.tsv是不带有标签的数据.
  • train.tsv数据样式:
...
gj04    1       She coughed herself awake as the leaf landed on her nose.
gj04    1       The worm wriggled onto the carpet.
gj04    1       The chocolate melted onto the carpet.
gj04    0   *   The ball wriggled itself loose.
gj04    1       Bill wriggled himself loose.
bc01    1       The sinking of the ship to collect the insurance was very devious.
bc01    1       The ship's sinking was very devious.
bc01    0   *   The ship's sinking to collect the insurance was very devious.
bc01    1       The testing of such drugs on oneself is too risky.
bc01    0   *   This drug's testing on oneself is too risky.
...
  • train.tsv数据样式说明:
  • train.tsv中的数据内容共分为4列, 第一列数据, 如gj04, bc01等代表每条文本数据的来源即出版物代号; 第二列数据, 0或1, 代表每条文本数据的语法是否正确, 0代表不正确, 1代表正确; 第三列数据, '
    ', 是作者最初的正负样本标记, 与第二列意义相同, '
    '表示不正确;
    第四列即是被标注的语法使用是否正确的文本句子.
  • test.tsv数据样式:
index   sentence
0   Bill whistled past the house.
1   The car honked its way down the road.
2   Bill pushed Harry off the sofa.
3   the kittens yawned awake and played.
4   I demand that the more John eats, the more he pay.
5   If John eats more, keep your mouth shut tighter, OK?
6   His expectations are always lower than mine are.
7   The sooner you call, the more carefully I will word the letter.
8   The more timid he feels, the more people he interviews without asking questions of.
9   Once Janet left, Fred became a lot crazier.
...
  • test.tsv数据样式说明:
  • test.tsv中的数据内容共分为2列, 第一列数据代表每条文本数据的索引; 第二列数据代表用于测试的句子.
  • CoLA数据集的任务类型:
  • 二分类任务
  • 评估指标为: MCC(马修斯相关系数, 在正负样本分布十分不均衡的情况下使用的二分类评估指标)

2.2 SST-2数据集文件样式¶

  • 数据集释义:SST-2(The Stanford Sentiment Treebank,斯坦福情感树库),单句子分类任务,包含电影评论中的句子和它们情感的人类注释.
  • 本质:句子级别的二分类任务
- SST-2/
        - dev.tsv
        - original/
        - test.tsv
        - train.tsv
  • 文件样式说明:
  • 在使用中常用到的文件是train.tsv, dev.tsv, test.tsv, 分别代表训练集, 验证集和测试集. 其中train.tsv与dev.tsv数据样式相同, 都是带有标签的数据, 其中test.tsv是不带有标签的数据.
  • train.tsv数据样式:
sentence    label
hide new secretions from the parental units     0
contains no wit , only labored gags     0
that loves its characters and communicates something rather beautiful about human nature    1
remains utterly satisfied to remain the same throughout     0
on the worst revenge-of-the-nerds clichés the filmmakers could dredge up    0
that 's far too tragic to merit such superficial treatment  0
demonstrates that the director of such hollywood blockbusters as patriot games can still turn out a small , personal film with an emotional wallop .    1
of saucy    1
a depressed fifteen-year-old 's suicidal poetry     0
...
  • train.tsv数据样式说明:
  • train.tsv中的数据内容共分为2列, 第一列数据代表具有感情色彩的评论文本; 第二列数据, 0或1, 代表每条文本数据是积极或者消极的评论, 0代表消极, 1代表积极.
  • test.tsv数据样式:
index   sentence
0   uneasy mishmash of styles and genres .
1   this film 's relationship to actual tension is the same as what christmas-tree flocking in a spray can is to actual snow : a poor -- if durable -- imitation .
2   by the end of no such thing the audience , like beatrice , has a watchful affection for the monster .
3   director rob marshall went out gunning to make a great one .
4   lathan and diggs have considerable personal charm , and their screen rapport makes the old story seem new .
5   a well-made and often lovely depiction of the mysteries of friendship .
6   none of this violates the letter of behan 's book , but missing is its spirit , its ribald , full-throated humor .
7   although it bangs a very cliched drum at times , this crowd-pleaser 's fresh dialogue , energetic music , and good-natured spunk are often infectious .
8   it is not a mass-market entertainment but an uncompromising attempt by one artist to think about another .
9   this is junk food cinema at its greasiest .
...
  • test.tsv数据样式说明: - test.tsv中的数据内容共分为2列, 第一列数据代表每条文本数据的索引; 第二列数据代表用于测试的句子.
  • SST-2数据集的任务类型:
  • 二分类任务
  • 评估指标为: ACC

2.3 MRPC数据集文件样式¶

  • 数据集释义:MRPC(The Microsoft Research Paraphrase Corpus,微软研究院释义语料库),相似性和释义任务,是从在线新闻源中自动抽取句子对语料库,并人工注释句子对中的句子是否在语义上等效。
  • 本质:句子级别的二分类任务
- MRPC/
        - dev.tsv
        - test.tsv
        - train.tsv
    - dev_ids.tsv
    - msr_paraphrase_test.txt
    - msr_paraphrase_train.txt
  • 文件样式说明:
  • 在使用中常用到的文件是train.tsv, dev.tsv, test.tsv, 分别代表训练集, 验证集和测试集. 其中train.tsv与dev.tsv数据样式相同, 都是带有标签的数据, 其中test.tsv是不带有标签的数据.
  • train.tsv数据样式:
Quality #1 ID   #2 ID   #1 String   #2 String
1   702876  702977  Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence . Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .
0   2108705 2108831 Yucaipa owned Dominick 's before selling the chain to Safeway in 1998 for $ 2.5 billion .   Yucaipa bought Dominick 's in 1995 for $ 693 million and sold it to Safeway for $ 1.8 billion in 1998 .
1   1330381 1330521 They had published an advertisement on the Internet on June 10 , offering the cargo for sale , he added .   On June 10 , the ship 's owners had published an advertisement on the Internet , offering the explosives for sale .
0   3344667 3344648 Around 0335 GMT , Tab shares were up 19 cents , or 4.4 % , at A $ 4.56 , having earlier set a record high of A $ 4.57 . Tab shares jumped 20 cents , or 4.6 % , to set a record closing high at A $ 4.57 .
1   1236820 1236712 The stock rose $ 2.11 , or about 11 percent , to close Friday at $ 21.51 on the New York Stock Exchange .   PG & E Corp. shares jumped $ 1.63 or 8 percent to $ 21.03 on the New York Stock Exchange on Friday .
1   738533  737951  Revenue in the first quarter of the year dropped 15 percent from the same period a year earlier .   With the scandal hanging over Stewart 's company , revenue the first quarter of the year dropped 15 percent from the same period a year earlier .
0   264589  264502  The Nasdaq had a weekly gain of 17.27 , or 1.2 percent , closing at 1,520.15 on Friday .    The tech-laced Nasdaq Composite .IXIC rallied 30.46 points , or 2.04 percent , to 1,520.15 .
1   579975  579810  The DVD-CCA then appealed to the state Supreme Court .  The DVD CCA appealed that decision to the U.S. Supreme Court .
...
  • train.tsv数据样式说明:
  • train.tsv中的数据内容共分为5列, 第一列数据, 0或1, 代表每对句子是否具有相同的含义, 0代表含义不相同, 1代表含义相同. 第二列和第三列分别代表每对句子的id, 第四列和第五列分别具有相同/不同含义的句子对.
  • test.tsv数据样式:
index   #1 ID   #2 ID   #1 String   #2 String
0   1089874 1089925 PCCW 's chief operating officer , Mike Butcher , and Alex Arena , the chief financial officer , will report directly to Mr So . Current Chief Operating Officer Mike Butcher and Group Chief Financial Officer Alex Arena will report to So .
1   3019446 3019327 The world 's two largest automakers said their U.S. sales declined more than predicted last month as a late summer sales frenzy caused more of an industry backlash than expected . Domestic sales at both GM and No. 2 Ford Motor Co. declined more than predicted as a late summer sales frenzy prompted a larger-than-expected industry backlash .
2   1945605 1945824 According to the federal Centers for Disease Control and Prevention ( news - web sites ) , there were 19 reported cases of measles in the United States in 2002 .   The Centers for Disease Control and Prevention said there were 19 reported cases of measles in the United States in 2002 .
3   1430402 1430329 A tropical storm rapidly developed in the Gulf of Mexico Sunday and was expected to hit somewhere along the Texas or Louisiana coasts by Monday night . A tropical storm rapidly developed in the Gulf of Mexico on Sunday and could have hurricane-force winds when it hits land somewhere along the Louisiana coast Monday night .
4   3354381 3354396 The company didn 't detail the costs of the replacement and repairs .   But company officials expect the costs of the replacement work to run into the millions of dollars .
5   1390995 1391183 The settling companies would also assign their possible claims against the underwriters to the investor plaintiffs , he added . Under the agreement , the settling companies will also assign their potential claims against the underwriters to the investors , he added .
6   2201401 2201285 Air Commodore Quaife said the Hornets remained on three-minute alert throughout the operation . Air Commodore John Quaife said the security operation was unprecedented .
7   2453843 2453998 A Washington County man may have the countys first human case of West Nile virus , the health department said Friday .  The countys first and only human case of West Nile this year was confirmed by health officials on Sept . 8 .
...
  • test.tsv数据样式说明:
    • test.tsv中的数据内容共分为5列, 第一列数据代表每条文本数据的索引; 其余列的含义与train.tsv中相同.
  • MRPC数据集的任务类型:
  • 句子对二分类任务
  • 评估指标为: ACC和F1

2.4 STS-B数据集文件样式¶

  • 数据集释义: STSB(The Semantic Textual Similarity Benchmark,语义文本相似性基准测试)
  • 本质: 回归任务/句子对的文本五分类任务
- STS-B/
        - dev.tsv
        - test.tsv
        - train.tsv
    - LICENSE.txt
    - readme.txt
    - original/
  • 文件样式说明:
  • 在使用中常用到的文件是train.tsv, dev.tsv, test.tsv, 分别代表训练集, 验证集和测试集. 其中train.tsv与dev.tsv数据样式相同, 都是带有标签的数据, 其中test.tsv是不带有标签的数据.
  • train.tsv数据样式:
index   genre   filename    year    old_index   source1 source2 sentence1   sentence2   score
0   main-captions   MSRvid  2012test    0001    none    none    A plane is taking off.  An air plane is taking off. 5.000
1   main-captions   MSRvid  2012test    0004    none    none    A man is playing a large flute. A man is playing a flute.   3.800
2   main-captions   MSRvid  2012test    0005    none    none    A man is spreading shreded cheese on a pizza.   A man is spreading shredded cheese on an uncooked pizza.    3.800
3   main-captions   MSRvid  2012test    0006    none    none    Three men are playing chess.Two men are playing chess.  2.600
4   main-captions   MSRvid  2012test    0009    none    none    A man is playing the cello.A man seated is playing the cello.   4.250
5   main-captions   MSRvid  2012test    0011    none    none    Some men are fighting.  Two men are fighting.   4.250
6   main-captions   MSRvid  2012test    0012    none    none    A man is smoking.   A man is skating.   0.500
7   main-captions   MSRvid  2012test    0013    none    none    The man is playing the piano.   The man is playing the guitar.  1.600
8   main-captions   MSRvid  2012test    0014    none    none    A man is playing on a guitar and singing.   A woman is playing an acoustic guitar and singing.  2.200
9   main-captions   MSRvid  2012test    0016    none    none    A person is throwing a cat on to the ceiling.   A person throws a cat on the ceiling.   5.000
...
  • train.tsv数据样式说明:
  • train.tsv中的数据内容共分为10列, 第一列数据是数据索引; 第二列代表每对句子的来源, 如main-captions表示来自字幕; 第三列代表来源的具体保存文件名, 第四列代表出现时间(年); 第五列代表原始数据的索引; 第六列和第七列分别代表句子对原始来源; 第八列和第九列代表相似程度不同的句子对; 第十列代表句子对的相似程度由低到高, 值域范围是[0, 5].
  • test.tsv数据样式:
index   genre   filename    year    old_index   source1 source2 sentence1   sentence2
0   main-captions   MSRvid  2012test    0024    none    none    A girl is styling her hair. A girl is brushing her hair.
1   main-captions   MSRvid  2012test    0033    none    none    A group of men play soccer on the beach.    A group of boys are playing soccer on the beach.
2   main-captions   MSRvid  2012test    0045    none    none    One woman is measuring another woman's ankle.   A woman measures another woman's ankle.
3   main-captions   MSRvid  2012test    0063    none    none    A man is cutting up a cucumber. A man is slicing a cucumber.
4   main-captions   MSRvid  2012test    0066    none    none    A man is playing a harp.    A man is playing a keyboard.
5   main-captions   MSRvid  2012test    0074    none    none    A woman is cutting onions.  A woman is cutting tofu.
6   main-captions   MSRvid  2012test    0076    none    none    A man is riding an electric bicycle.    A man is riding a bicycle.
7   main-captions   MSRvid  2012test    0082    none    none    A man is playing the drums. A man is playing the guitar.
8   main-captions   MSRvid  2012test    0092    none    none    A man is playing guitar.    A lady is playing the guitar.
9   main-captions   MSRvid  2012test    0095    none    none    A man is playing a guitar.  A man is playing a trumpet.
10  main-captions   MSRvid  2012test    0096    none    none    A man is playing a guitar.  A man is playing a trumpet.
...
  • test.tsv数据样式说明:
  • test.tsv中的数据内容共分为9列, 含义与train.tsv前9列相同.
  • STS-B数据集的任务类型:
  • 句子对多分类任务/句子对回归任务
  • 评估指标为: Pearson-Spearman Corr

2.5 QQP数据集文件样式¶

  • 数据集释义: QQP(The Quora Question Pairs, Quora问题对数集),相似性和释义任务,是社区问答网站Quora中问题对的集合。
  • 本质: 句子对的二分类任务
- QQP/
        - dev.tsv
        - original/
        - test.tsv
        - train.tsv
  • 文件样式说明:
  • 在使用中常用到的文件是train.tsv, dev.tsv, test.tsv, 分别代表训练集, 验证集和测试集. 其中train.tsv与dev.tsv数据样式相同, 都是带有标签的数据, 其中test.tsv是不带有标签的数据.
  • train.tsv数据样式:
id  qid1    qid2    question1   question2   is_duplicate
133273  213221  213222  How is the life of a math student? Could you describe your own experiences?Which level of prepration is enough for the exam jlpt5?  0
402555  536040  536041  How do I control my horny emotions? How do you control your horniness?  1
360472  364011  490273  What causes stool color to change to yellow?    What can cause stool to come out as little balls?   0
150662  155721  7256    What can one do after MBBS? What do i do after my MBBS ?    1
183004  279958  279959  Where can I find a power outlet for my laptop at Melbourne Airport? Would a second airport in Sydney, Australia be needed if a high-speed rail link was created between Melbourne and Sydney?   0
119056  193387  193388  How not to feel guilty since I am Muslim and I'm conscious we won't have sex together?  I don't beleive I am bulimic, but I force throw up atleast once a day after I eat something and feel guilty. Should I tell somebody, and if so who? 0
356863  422862  96457   How is air traffic controlled?  How do you become an air traffic controller?0
106969  147570  787 What is the best self help book you have read? Why? How did it change your life?    What are the top self help books I should read? 1
...
  • train.tsv数据样式说明:
  • train.tsv中的数据内容共分为6列, 第一列代表文本数据索引; 第二列和第三列数据分别代表问题1和问题2的id; 第四列和第五列代表需要进行'是否重复'判定的句子对; 第六列代表上述问题是/不是重复性问题的标签, 0代表不重复, 1代表重复.
  • test.tsv数据样式:
id  question1   question2
0   Would the idea of Trump and Putin in bed together scare you, given the geopolitical implications?   Do you think that if Donald Trump were elected President, he would be able to restore relations with Putin and Russia as he said he could, based on the rocky relationship Putin had with Obama and Bush?
1   What are the top ten Consumer-to-Consumer E-commerce online?    What are the top ten Consumer-to-Business E-commerce online?
2   Why don't people simply 'Google' instead of asking questions on Quora?  Why do people ask Quora questions instead of just searching google?
3   Is it safe to invest in social trade biz?   Is social trade geniune?
4   If the universe is expanding then does matter also expand?  If universe and space is expanding? Does that mean anything that occupies space is also expanding?
5   What is the plural of hypothesis?   What is the plural of thesis?
6   What is the application form you need for launching a company?  What is the application form you need for launching a company in Austria?
7   What is Big Theta? When should I use Big Theta as opposed to big O? Is O(Log n) close to O(n) or O(1)?
8   What are the health implications of accidentally eating a small quantity of aluminium foil?What are the implications of not eating vegetables?
...
  • test.tsv数据样式说明:
  • test.tsv中的数据内容共分为3列, 第一列数据代表每条文本数据的索引; 第二列和第三列数据代表用于测试的问题句子对.
  • QQP数据集的任务类型:
  • 句子对二分类任务
  • 评估指标为: ACC/F1

2.6 (MNLI/SNLI)数据集文件样式¶

  • 数据集释义:
  • MNLI(The Multi-Genre Natural Language Inference Corpus, 多类型自然语言推理数据库)
  • 本质: 句子对的三分类任务
- (MNLI/SNLI)/
    - dev_matched.tsv
    - dev_mismatched.tsv
    - original/
    - test_matched.tsv
    - test_mismatched.tsv
    - train.tsv
  • 文件样式说明:
  • 在使用中常用到的文件是train.tsv, dev_matched.tsv, dev_mismatched.tsv, test_matched.tsv, test_mismatched.tsv分别代表训练集, 与训练集一同采集的验证集, 与训练集不是一同采集验证集, 与训练集一同采集的测试集, 与训练集不是一同采集测试集. 其中train.tsv与dev_matched.tsv和dev_mismatched.tsv数据样式相同, 都是带有标签的数据, 其中test_matched.tsv与test_mismatched.tsv数据样式相同, 都是不带有标签的数据.
  • train.tsv数据样式:
index   promptID    pairID  genre   sentence1_binary_parse  sentence2_binary_parse  sentence1_parse sentence2_parse sentence1   sentence2   label1  gold_label
0   31193   31193n  government  ( ( Conceptually ( cream skimming ) ) ( ( has ( ( ( two ( basic dimensions ) ) - ) ( ( product and ) geography ) ) ) . ) )  ( ( ( Product and ) geography ) ( ( are ( what ( make ( cream ( skimming work ) ) ) ) ) . ) )   (ROOT (S (NP (JJ Conceptually) (NN cream) (NN skimming)) (VP (VBZ has) (NP (NP (CD two) (JJ basic) (NNS dimensions)) (: -) (NP (NN product) (CC and) (NN geography)))) (. .)))  (ROOT (S (NP (NN Product) (CC and) (NN geography)) (VP (VBP are) (SBAR (WHNP (WP what)) (S (VP (VBP make) (NP (NP (NN cream)) (VP (VBG skimming) (NP (NN work)))))))) (. .)))   Conceptually cream skimming has two basic dimensions - product and geography.   Product and geography are what make cream skimming work.    neutral neutral
1   101457  101457e telephone   ( you ( ( know ( during ( ( ( the season ) and ) ( i guess ) ) ) ) ( at ( at ( ( your level ) ( uh ( you ( ( ( lose them ) ( to ( the ( next level ) ) ) ) ( if ( ( if ( they ( decide ( to ( recall ( the ( the ( parent team ) ) ) ) ) ) ) ) ( ( the Braves ) ( decide ( to ( call ( to ( ( recall ( a guy ) ) ( from ( ( triple A ) ( ( ( then ( ( a ( double ( A guy ) ) ) ( ( goes up ) ( to ( replace him ) ) ) ) ) and ) ( ( a ( single ( A guy ) ) ) ( ( goes up ) ( to ( replace him ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ( You ( ( ( ( lose ( the things ) ) ( to ( the ( following level ) ) ) ) ( if ( ( the people ) recall ) ) ) . ) )   (ROOT (S (NP (PRP you)) (VP (VBP know) (PP (IN during) (NP (NP (DT the) (NN season)) (CC and) (NP (FW i) (FW guess)))) (PP (IN at) (IN at) (NP (NP (PRP$ your) (NN level)) (SBAR (S (INTJ (UH uh)) (NP (PRP you)) (VP (VBP lose) (NP (PRP them)) (PP (TO to) (NP (DT the) (JJ next) (NN level))) (SBAR (IN if) (S (SBAR (IN if) (S (NP (PRP they)) (VP (VBP decide) (S (VP (TO to) (VP (VB recall) (NP (DT the) (DT the) (NN parent) (NN team)))))))) (NP (DT the) (NNPS Braves)) (VP (VBP decide) (S (VP (TO to) (VP (VB call) (S (VP (TO to) (VP (VB recall) (NP (DT a) (NN guy)) (PP (IN from) (NP (NP (RB triple) (DT A)) (SBAR (S (S (ADVP (RB then)) (NP (DT a) (JJ double) (NNP A) (NN guy)) (VP (VBZ goes) (PRT (RP up)) (S (VP (TO to) (VP (VB replace) (NP (PRP him))))))) (CC and) (S (NP (DT a) (JJ single) (NNP A) (NN guy)) (VP (VBZ goes) (PRT (RP up)) (S (VP (TO to) (VP (VB replace) (NP (PRP him)))))))))))))))))))))))))))) (ROOT (S (NP (PRP You)) (VP (VBP lose) (NP (DT the) (NNS things)) (PP (TO to) (NP (DT the) (JJ following) (NN level))) (SBAR (IN if) (S (NP (DT the) (NNS people)) (VP (VBP recall))))) (. .))) you know during the season and i guess at at your level uh you lose them to the next level if if they decide to recall the the parent team the Braves decide to call to recall a guy from triple A then a double A guy goes up to replace him and a single A guy goes up to replace him You lose the things to the following level if the people recall.    entailment  entailment
2   134793  134793e fiction ( ( One ( of ( our number ) ) ) ( ( will ( ( ( carry out ) ( your instructions ) ) minutely ) ) . ) )   ( ( ( A member ) ( of ( my team ) ) ) ( ( will ( ( execute ( your orders ) ) ( with ( immense precision ) ) ) ) . ) )   (ROOT (S (NP (NP (CD One)) (PP (IN of) (NP (PRP$ our) (NN number)))) (VP (MD will) (VP (VB carry) (PRT (RP out)) (NP (PRP$ your) (NNS instructions)) (ADVP (RB minutely)))) (. .))) (ROOT (S (NP (NP (DT A) (NN member)) (PP (IN of) (NP (PRP$ my) (NN team)))) (VP (MD will) (VP (VB execute) (NP (PRP$ your) (NNS orders)) (PP (IN with) (NP (JJ immense) (NN precision))))) (. .)))  One of our number will carry out your instructions minutely.    A member of my team will execute your orders with immense precision.    entailment  entailment
3   37397   37397e  fiction ( ( How ( ( ( do you ) know ) ? ) ) ( ( All this ) ( ( ( is ( their information ) ) again ) . ) ) ) ( ( This information ) ( ( belongs ( to them ) ) . ) )  (ROOT (S (SBARQ (WHADVP (WRB How)) (SQ (VBP do) (NP (PRP you)) (VP (VB know))) (. ?)) (NP (PDT All) (DT this)) (VP (VBZ is) (NP (PRP$ their) (NN information)) (ADVP (RB again))) (. .)))   (ROOT (S (NP (DT This) (NN information)) (VP (VBZ belongs) (PP (TO to) (NP (PRP them)))) (. .)))    How do you know? All this is their information again.   This information belongs to them.   entailment  entailment
...
  • train.tsv数据样式说明:
  • train.tsv中的数据内容共分为12列, 第一列代表文本数据索引; 第二列和第三列数据分别代表句子对的不同类型id; 第四列代表句子对的来源; 第五列和第六列代表具有句法结构分析的句子对表示; 第七列和第八列代表具有句法结构和词性标注的句子对表示, 第九列和第十列代表原始的句子对, 第十一和第十二列代表不同标准的标注方法产生的标签, 在这里,他们始终相同, 一共有三种类型的标签, neutral代表两个句子既不矛盾也不蕴含, entailment代表两个句子具有蕴含关系, contradiction代表两个句子观点矛盾.
  • test_matched.tsv数据样式:
index   promptID    pairID  genre   sentence1_binary_parse  sentence2_binary_parse  sentence1_parse sentence2_parse sentence1   sentence2
0   31493   31493   travel  ( ( ( ( ( ( ( ( Hierbas , ) ( ans seco ) ) , ) ( ans dulce ) ) , ) and ) frigola ) ( ( ( are just ) ( ( a ( few names ) ) ( worth ( ( keeping ( a look-out ) ) for ) ) ) ) . ) )    ( Hierbas ( ( is ( ( a name ) ( worth ( ( looking out ) for ) ) ) ) . ) )   (ROOT (S (NP (NP (NNS Hierbas)) (, ,) (NP (NN ans) (NN seco)) (, ,) (NP (NN ans) (NN dulce)) (, ,) (CC and) (NP (NN frigola))) (VP (VBP are) (ADVP (RB just)) (NP (NP (DT a) (JJ few) (NNS names)) (PP (JJ worth) (S (VP (VBG keeping) (NP (DT a) (NN look-out)) (PP (IN for))))))) (. .))) (ROOT (S (NP (NNS Hierbas)) (VP (VBZ is) (NP (NP (DT a) (NN name)) (PP (JJ worth) (S (VP (VBG looking) (PRT (RP out)) (PP (IN for))))))) (. .)))    Hierbas, ans seco, ans dulce, and frigola are just a few names worth keeping a look-out for.    Hierbas is a name worth looking out for.
1   92164   92164   government  ( ( ( The extent ) ( of ( the ( behavioral effects ) ) ) ) ( ( would ( ( depend ( in ( part ( on ( ( the structure ) ( of ( ( ( the ( individual ( account program ) ) ) and ) ( any limits ) ) ) ) ) ) ) ) ( on ( accessing ( the funds ) ) ) ) ) . ) )    ( ( Many people ) ( ( would ( be ( very ( unhappy ( to ( ( loose control ) ( over ( their ( own money ) ) ) ) ) ) ) ) ) . ) )   (ROOT (S (NP (NP (DT The) (NN extent)) (PP (IN of) (NP (DT the) (JJ behavioral) (NNS effects)))) (VP (MD would) (VP (VB depend) (PP (IN in) (NP (NP (NN part)) (PP (IN on) (NP (NP (DT the) (NN structure)) (PP (IN of) (NP (NP (DT the) (JJ individual) (NN account) (NN program)) (CC and) (NP (DT any) (NNS limits)))))))) (PP (IN on) (S (VP (VBG accessing) (NP (DT the) (NNS funds))))))) (. .))) (ROOT (S (NP (JJ Many) (NNS people)) (VP (MD would) (VP (VB be) (ADJP (RB very) (JJ unhappy) (PP (TO to) (NP (NP (JJ loose) (NN control)) (PP (IN over) (NP (PRP$ their) (JJ own) (NN money)))))))) (. .))) The extent of the behavioral effects would depend in part on the structure of the individual account program and any limits on accessing the funds. Many people would be very unhappy to loose control over their own money.
2   9662    9662    government  ( ( ( Timely access ) ( to information ) ) ( ( is ( in ( ( the ( best interests ) ) ( of ( ( ( both GAO ) and ) ( the agencies ) ) ) ) ) ) . ) )    ( It ( ( ( is ( in ( ( everyone 's ) ( best interest ) ) ) ) ( to ( ( have access ) ( to ( information ( in ( a ( timely manner ) ) ) ) ) ) ) ) . ) )   (ROOT (S (NP (NP (JJ Timely) (NN access)) (PP (TO to) (NP (NN information)))) (VP (VBZ is) (PP (IN in) (NP (NP (DT the) (JJS best) (NNS interests)) (PP (IN of) (NP (NP (DT both) (NNP GAO)) (CC and) (NP (DT the) (NNS agencies))))))) (. .))) (ROOT (S (NP (PRP It)) (VP (VBZ is) (PP (IN in) (NP (NP (NN everyone) (POS 's)) (JJS best) (NN interest))) (S (VP (TO to) (VP (VB have) (NP (NN access)) (PP (TO to) (NP (NP (NN information)) (PP (IN in) (NP (DT a) (JJ timely) (NN manner))))))))) (. .)))   Timely access to information is in the best interests of both GAO and the agencies. It is in everyone's best interest to have access to information in a timely manner.
3   5991    5991    travel  ( ( Based ( in ( ( the ( Auvergnat ( spa town ) ) ) ( of Vichy ) ) ) ) ( , ( ( the ( French government ) ) ( often ( ( ( ( proved ( more zealous ) ) ( than ( its masters ) ) ) ( in ( ( ( suppressing ( civil liberties ) ) and ) ( ( drawing up ) ( anti-Jewish legislation ) ) ) ) ) . ) ) ) ) ) ( ( The ( French government ) ) ( ( passed ( ( anti-Jewish laws ) ( aimed ( at ( helping ( the Nazi ) ) ) ) ) ) . ) )   (ROOT (S (PP (VBN Based) (PP (IN in) (NP (NP (DT the) (NNP Auvergnat) (NN spa) (NN town)) (PP (IN of) (NP (NNP Vichy)))))) (, ,) (NP (DT the) (JJ French) (NN government)) (ADVP (RB often)) (VP (VBD proved) (NP (JJR more) (NNS zealous)) (PP (IN than) (NP (PRP$ its) (NNS masters))) (PP (IN in) (S (VP (VP (VBG suppressing) (NP (JJ civil) (NNS liberties))) (CC and) (VP (VBG drawing) (PRT (RP up)) (NP (JJ anti-Jewish) (NN legislation))))))) (. .))) (ROOT (S (NP (DT The) (JJ French) (NN government)) (VP (VBD passed) (NP (NP (JJ anti-Jewish) (NNS laws)) (VP (VBN aimed) (PP (IN at) (S (VP (VBG helping) (NP (DT the) (JJ Nazi)))))))) (. .))) Based in the Auvergnat spa town of Vichy, the French government often proved more zealous than its masters in suppressing civil liberties and drawing up anti-Jewish legislation.   The French government passed anti-Jewish laws aimed at helping the Nazi.
...
  • test_matched.tsv数据样式说明:
  • test_matched.tsv中的数据内容共分为10列, 与train.tsv的前10列含义相同.
  • (MNLI/SNLI)数据集的任务类型:
  • 句子对多分类任务
  • 评估指标为: ACC

2.7 (QNLI/RTE/WNLI)数据集文件样式¶

  • 数据集释义:
  • QNLI(Qusetion-answering NLI,问答自然语言推断),自然语言推断任务。QNLI是从另一个数据集The Stanford Question Answering Dataset(斯坦福问答数据集, SQuAD 1.0)[3]转换而来的.
  • RTE(The Recognizing Textual Entailment datasets,识别文本蕴含数据集),自然语言推断任务,它是将一系列的年度文本蕴含挑战赛的数据集进行整合合并而来的.
  • WNLI(Winograd NLI,Winograd自然语言推断),自然语言推断任务,数据集来自于竞赛数据的转换。
  • 本质: QNLI是二分类任务. RTE是二分类任务. WNLI是二分类任务.
  • QNLI, RTE, WNLI三个数据集的样式基本相同.
- (QNLI/RTE/WNLI)/
        - dev.tsv
        - test.tsv
        - train.tsv
  • 文件样式说明:
  • 在使用中常用到的文件是train.tsv, dev.tsv, test.tsv, 分别代表训练集, 验证集和测试集. 其中train.tsv与dev.tsv数据样式相同, 都是带有标签的数据, 其中test.tsv是不带有标签的数据.
  • QNLI中的train.tsv数据样式:
index   question    sentence    label
0   When did the third Digimon series begin?    Unlike the two seasons before it and most of the seasons that followed, Digimon Tamers takes a darker and more realistic approach to its story featuring Digimon who do not reincarnate after their deaths and more complex character development in the original Japanese. not_entailment
1   Which missile batteries often have individual launchers several kilometres from one another?    When MANPADS is operated by specialists, batteries may have several dozen teams deploying separately in small sections; self-propelled air defence guns may deploy in pairs.    not_entailment
2   What two things does Popper argue Tarski's theory involves in an evaluation of truth?   He bases this interpretation on the fact that examples such as the one described above refer to two things: assertions and the facts to which they refer.   entailment
3   What is the name of the village 9 miles north of Calafat where the Ottoman forces attacked the Russians?    On 31 December 1853, the Ottoman forces at Calafat moved against the Russian force at Chetatea or Cetate, a small village nine miles north of Calafat, and engaged them on 6 January 1854.  entailment
4   What famous palace is located in London?    London contains four World Heritage Sites: the Tower of London; Kew Gardens; the site comprising the Palace of Westminster, Westminster Abbey, and St Margaret's Church; and the historic settlement of Greenwich (in which the Royal Observatory, Greenwich marks the Prime Meridian, 0° longitude, and GMT).  not_entailment
5   When is the term 'German dialects' used in regard to the German language?   When talking about the German language, the term German dialects is only used for the traditional regional varieties.   entailment
6   What was the name of the island the English traded to the Dutch in return for New Amsterdam?    At the end of the Second Anglo-Dutch War, the English gained New Amsterdam (New York) in North America in exchange for Dutch control of Run, an Indonesian island.  entailment
7   How were the Portuguese expelled from Myanmar?  From the 1720s onward, the kingdom was beset with repeated Meithei raids into Upper Myanmar and a nagging rebellion in Lan Na.  not_entailment
8   What does the word 'customer' properly apply to?    The bill also required rotation of principal maintenance inspectors and stipulated that the word "customer" properly applies to the flying public, not those entities regulated by the FAA. entailment
...
  • RTE中的train.tsv数据样式:
index   sentence1   sentence2   label
0   No Weapons of Mass Destruction Found in Iraq Yet.   Weapons of Mass Destruction Found in Iraq.  not_entailment
1   A place of sorrow, after Pope John Paul II died, became a place of celebration, as Roman Catholic faithful gathered in downtown Chicago to mark the installation of new Pope Benedict XVI.Pope Benedict XVI is the new leader of the Roman Catholic Church. entailment
2   Herceptin was already approved to treat the sickest breast cancer patients, and the company said, Monday, it will discuss with federal regulators the possibility of prescribing the drug for more breast cancer patients.  Herceptin can be used to treat breast cancer.   entailment
3   Judie Vivian, chief executive at ProMedica, a medical service company that helps sustain the 2-year-old Vietnam Heart Institute in Ho Chi Minh City (formerly Saigon), said that so far about 1,500 children have received treatment.   The previous name of Ho Chi Minh City was Saigon.entailment
4   A man is due in court later charged with the murder 26 years ago of a teenager whose case was the first to be featured on BBC One's Crimewatch. Colette Aram, 16, was walking to her boyfriend's house in Keyworth, Nottinghamshire, on 30 October 1983 when she disappeared. Her body was later found in a field close to her home. Paul Stewart Hutchinson, 50, has been charged with murder and is due before Nottingham magistrates later.  Paul Stewart Hutchinson is accused of having stabbed a girl.    not_entailment
5   Britain said, Friday, that it has barred cleric, Omar Bakri, from returning to the country from Lebanon, where he was released by police after being detained for 24 hours. Bakri was briefly detained, but was released.   entailment
6   Nearly 4 million children who have at least one parent who entered the U.S. illegally were born in the United States and are U.S. citizens as a result, according to the study conducted by the Pew Hispanic Center. That's about three quarters of the estimated 5.5 million children of illegal immigrants inside the United States, according to the study. About 1.8 million children of undocumented immigrants live in poverty, the study found.  Three quarters of U.S. illegal immigrants have children.    not_entailment
7   Like the United States, U.N. officials are also dismayed that Aristide killed a conference called by Prime Minister Robert Malval in Port-au-Prince in hopes of bringing all the feuding parties together.  Aristide had Prime Minister Robert Malval  murdered in Port-au-Prince.  not_entailment
8   WASHINGTON --  A newly declassified narrative of the Bush administration's advice to the CIA on harsh interrogations shows that the small group of Justice Department lawyers who wrote memos authorizing controversial interrogation techniques were operating not on their own but with direction from top administration officials, including then-Vice President Dick Cheney and national security adviser Condoleezza Rice. At the same time, the narrative suggests that then-Defense Secretary Donald H. Rumsfeld and then-Secretary of State Colin Powell were largely left out of the decision-making process. Dick Cheney was the Vice President of Bush. entailment

  • WNLI中的train.tsv数据样式:
index   sentence1   sentence2   label
0   I stuck a pin through a carrot. When I pulled the pin out, it had a hole.   The carrot had a hole.  1
1   John couldn't see the stage with Billy in front of him because he is so short.  John is so short.   1
2   The police arrested all of the gang members. They were trying to stop the drug trade in the neighborhood.   The police were trying to stop the drug trade in the neighborhood.  1
3   Steve follows Fred's example in everything. He influences him hugely.   Steve influences him hugely.    0
4   When Tatyana reached the cabin, her mother was sleeping. She was careful not to disturb her, undressing and climbing back into her berth.   mother was careful not to disturb her, undressing and climbing back into her berth. 0
5   George got free tickets to the play, but he gave them to Eric, because he was particularly eager to see it. George was particularly eager to see it.    0
6   John was jogging through the park when he saw a man juggling watermelons. He was very impressive.   John was very impressive.   0
7   I couldn't put the pot on the shelf because it was too tall.    The pot was too tall.   1
8   We had hoped to place copies of our newsletter on all the chairs in the auditorium, but there were simply not enough of them.   There were simply not enough copies of the newsletter.  1

  • (QNLI/RTE/WNLI)中的train.tsv数据样式说明:
  • train.tsv中的数据内容共分为4列, 第一列代表文本数据索引; 第二列和第三列数据代表需要进行'是否蕴含'判定的句子对; 第四列数据代表两个句子是否具有蕴含关系, 0/not_entailment代表不是蕴含关系, 1/entailment代表蕴含关系.
  • QNLI中的test.tsv数据样式:
index   question    sentence
0   What organization is devoted to Jihad against Israel?   For some decades prior to the First Palestine Intifada in 1987, the Muslim Brotherhood in Palestine took a "quiescent" stance towards Israel, focusing on preaching, education and social services, and benefiting from Israel's "indulgence" to build up a network of mosques and charitable organizations.
1   In what century was the Yarrow-Schlick-Tweedy balancing system used?    In the late 19th century, the Yarrow-Schlick-Tweedy balancing 'system' was used on some marine triple expansion engines.
2   The largest brand of what store in the UK is located in Kingston Park?  Close to Newcastle, the largest indoor shopping centre in Europe, the MetroCentre, is located in Gateshead.
3   What does the IPCC rely on for research?    In principle, this means that any significant new evidence or events that change our understanding of climate science between this deadline and publication of an IPCC report cannot be included.
4   What is the principle about relating spin and space variables?  Thus in the case of two fermions there is a strictly negative correlation between spatial and spin variables, whereas for two bosons (e.g. quanta of electromagnetic waves, photons) the correlation is strictly positive.
5   Which network broadcasted Super Bowl 50 in the U.S.?    CBS broadcast Super Bowl 50 in the U.S., and charged an average of $5 million for a 30-second commercial during the game.
6   What did the museum acquire from the Royal College of Science?  To link this to the rest of the museum, a new entrance building was constructed on the site of the former boiler house, the intended site of the Spiral, between 1978 and 1982.
7   What is the name of the old north branch of the Rhine?  From Wijk bij Duurstede, the old north branch of the Rhine is called Kromme Rijn ("Bent Rhine") past Utrecht, first Leidse Rijn ("Rhine of Leiden") and then, Oude Rijn ("Old Rhine").
8   What was one of Luther's most personal writings?    It remains in use today, along with Luther's hymns and his translation of the Bible.
...
  • (RTE/WNLI)中的test.tsv数据样式:
index   sentence1   sentence2
0   Maude and Dora had seen the trains rushing across the prairie, with long, rolling puffs of black smoke streaming back from the engine. Their roars and their wild, clear whistles could be heard from far away. Horses ran away when they came in sight.    Horses ran away when Maude and Dora came in sight.
1   Maude and Dora had seen the trains rushing across the prairie, with long, rolling puffs of black smoke streaming back from the engine. Their roars and their wild, clear whistles could be heard from far away. Horses ran away when they came in sight.    Horses ran away when the trains came in sight.
2   Maude and Dora had seen the trains rushing across the prairie, with long, rolling puffs of black smoke streaming back from the engine. Their roars and their wild, clear whistles could be heard from far away. Horses ran away when they came in sight.    Horses ran away when the puffs came in sight.
3   Maude and Dora had seen the trains rushing across the prairie, with long, rolling puffs of black smoke streaming back from the engine. Their roars and their wild, clear whistles could be heard from far away. Horses ran away when they came in sight.    Horses ran away when the roars came in sight.
4   Maude and Dora had seen the trains rushing across the prairie, with long, rolling puffs of black smoke streaming back from the engine. Their roars and their wild, clear whistles could be heard from far away. Horses ran away when they came in sight.    Horses ran away when the whistles came in sight.
5   Maude and Dora had seen the trains rushing across the prairie, with long, rolling puffs of black smoke streaming back from the engine. Their roars and their wild, clear whistles could be heard from far away. Horses ran away when they came in sight.    Horses ran away when the horses came in sight.
6   Maude and Dora had seen the trains rushing across the prairie, with long, rolling puffs of black smoke streaming back from the engine. Their roars and their wild, clear whistles could be heard from far away. Horses ran away when they saw a train coming.   Maude and Dora saw a train coming.
7   Maude and Dora had seen the trains rushing across the prairie, with long, rolling puffs of black smoke streaming back from the engine. Their roars and their wild, clear whistles could be heard from far away. Horses ran away when they saw a train coming.   The trains saw a train coming.
8   Maude and Dora had seen the trains rushing across the prairie, with long, rolling puffs of black smoke streaming back from the engine. Their roars and their wild, clear whistles could be heard from far away. Horses ran away when they saw a train coming.   The puffs saw a train coming.
...
  • (QNLI/RTE/WNLI)中的test.tsv数据样式说明:
  • test.tsv中的数据内容共分为3列, 第一列数据代表每条文本数据的索引; 第二列和第三列数据代表需要进行'是否蕴含'判定的句子对.
  • (QNLI/RTE/WNLI)数据集的任务类型:
  • 句子对二分类任务
  • 评估指标为: ACC

3 小结¶

  • 学习了GLUE数据集合的介绍:

    • GLUE由纽约大学, 华盛顿大学, Google联合推出, 涵盖不同NLP任务类型, 截止至2020年1月其中包括11个子任务数据集, 成为衡量NLP研究发展的衡量标准.
  • GLUE数据集合包含以下数据集:

    • CoLA 数据集
    • SST-2 数据集
    • MRPC 数据集
    • STS-B 数据集
    • QQP 数据集
    • MNLI 数据集
    • SNLI 数据集
    • QNLI 数据集
    • RTE 数据集
    • WNLI 数据集

3 NLP中的常用预训练模型

学习目标¶

  • 了解当下NLP中流行的预训练模型.
  • 掌握如何加载和使用预训练模型.

1 当下NLP中流行的预训练模型¶

  • BERT
  • GPT
  • GPT-2
  • Transformer-XL
  • XLNet
  • XLM
  • RoBERTa
  • DistilBERT
  • ALBERT
  • T5
  • XLM-RoBERTa

1.1 BERT及其变体¶

  • bert-base-uncased: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 在小写的英文文本上进行训练而得到.
  • bert-large-uncased: 编码器具有24个隐层, 输出1024维张量, 16个自注意力头, 共340M参数量, 在小写的英文文本上进行训练而得到.
  • bert-base-cased: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 在不区分大小写的英文文本上进行训练而得到.
  • bert-large-cased: 编码器具有24个隐层, 输出1024维张量, 16个自注意力头, 共340M参数量, 在不区分大小写的英文文本上进行训练而得到.
  • bert-base-multilingual-uncased: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 在小写的102种语言文本上进行训练而得到.
  • bert-large-multilingual-uncased: 编码器具有24个隐层, 输出1024维张量, 16个自注意力头, 共340M参数量, 在小写的102种语言文本上进行训练而得到.
  • bert-base-chinese: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 在简体和繁体中文文本上进行训练而得到.

1.2 GPT¶

  • openai-gpt: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 由OpenAI在英文语料上进行训练而得到.

1.3 GPT-2及其变体¶

  • gpt2: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共117M参数量, 在OpenAI GPT-2英文语料上进行训练而得到.
  • gpt2-xl: 编码器具有48个隐层, 输出1600维张量, 25个自注意力头, 共1558M参数量, 在大型的OpenAI GPT-2英文语料上进行训练而得到.

1.4 Transformer-XL¶

  • transfo-xl-wt103: 编码器具有18个隐层, 输出1024维张量, 16个自注意力头, 共257M参数量, 在wikitext-103英文语料进行训练而得到.

1.5 XLNet及其变体¶

  • xlnet-base-cased: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共110M参数量, 在英文语料上进行训练而得到.
  • xlnet-large-cased: 编码器具有24个隐层, 输出1024维张量, 16个自注意力头, 共240参数量, 在英文语料上进行训练而得到.

1.6 XLM¶

  • xlm-mlm-en-2048: 编码器具有12个隐层, 输出2048维张量, 16个自注意力头, 在英文文本上进行训练而得到.

1.7 RoBERTa及其变体¶

  • roberta-base: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共125M参数量, 在英文文本上进行训练而得到.
  • roberta-large: 编码器具有24个隐层, 输出1024维张量, 16个自注意力头, 共355M参数量, 在英文文本上进行训练而得到.

1.8 DistilBERT及其变体¶

  • distilbert-base-uncased: 基于bert-base-uncased的蒸馏(压缩)模型, 编码器具有6个隐层, 输出768维张量, 12个自注意力头, 共66M参数量.
  • distilbert-base-multilingual-cased: 基于bert-base-multilingual-uncased的蒸馏(压缩)模型, 编码器具有6个隐层, 输出768维张量, 12个自注意力头, 共66M参数量.

1.9 ALBERT¶

  • albert-base-v1: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共125M参数量, 在英文文本上进行训练而得到.
  • albert-base-v2: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共125M参数量, 在英文文本上进行训练而得到, 相比v1使用了更多的数据量, 花费更长的训练时间.

1.10 T5及其变体¶

  • t5-small: 编码器具有6个隐层, 输出512维张量, 8个自注意力头, 共60M参数量, 在C4语料上进行训练而得到.
  • t5-base: 编码器具有12个隐层, 输出768维张量, 12个自注意力头, 共220M参数量, 在C4语料上进行训练而得到.
  • t5-large: 编码器具有24个隐层, 输出1024维张量, 16个自注意力头, 共770M参数量, 在C4语料上进行训练而得到.

1.1 XLM-RoBERTa及其变体¶

  • xlm-roberta-base: 编码器具有12个隐层, 输出768维张量, 8个自注意力头, 共125M参数量, 在2.5TB的100种语言文本上进行训练而得到.
  • xlm-roberta-large: 编码器具有24个隐层, 输出1027维张量, 16个自注意力头, 共355M参数量, 在2.5TB的100种语言文本上进行训练而得到.

2 预训练模型说明¶

  • 所有上述预训练模型及其变体都是以transformer为基础,只是在模型结构如神经元连接方式,编码器隐层数,多头注意力的头数等发生改变,这些改变方式的大部分依据都是由在标准数据集上的表现而定,因此,对于我们使用者而言,不需要从理论上深度探究这些预训练模型的结构设计的优劣,只需要在自己处理的目标数据上,尽量遍历所有可用的模型对比得到最优效果即可.

3 小结¶

  • 当下NLP中流行的预训练模型:
    • BERT
    • GPT
    • GPT-2
    • Transformer-XL
    • XLNet
    • XLM
    • RoBERTa
    • DistilBERT
    • ALBERT
    • T5
    • XLM-RoBERTa

第六章 Transformer机制详解

1 BERT模型介绍

学习目标¶

  • 了解什么是BERT.
  • 掌握BERT的架构.
  • 掌握BERT的预训练任务.

1 BERT简介¶

BERT是2018年10月由Google AI研究院提出的一种预训练模型.

  • BERT的全称是Bidirectional Encoder Representation from Transformers.
  • BERT在机器阅读理解顶级水平测试SQuAD1.1中表现出惊人的成绩: 全部两个衡量指标上全面超越人类, 并且在11种不同NLP测试中创出SOTA表现. 包括将GLUE基准推高至80.4% (绝对改进7.6%), MultiNLI准确度达到86.7% (绝对改进5.6%). 成为NLP发展史上的里程碑式的模型成就.

2 BERT的架构¶

总体架构: 如下图所示, 最左边的就是BERT的架构图, 可以很清楚的看到BERT采用了Transformer Encoder block进行连接, 因为是一个典型的双向编码模型.

从上面的架构图中可以看到, 宏观上BERT分三个主要模块.

  • 最底层黄色标记的Embedding模块.
  • 中间层蓝色标记的Transformer模块.
  • 最上层绿色标记的预微调模块.

2.1 Embedding模块¶

BERT中的该模块是由三种Embedding共同组成而成, 如下图

  • Token Embeddings 是词嵌入张量, 第一个单词是CLS标志, 可以用于之后的分类任务.
  • Segment Embeddings 是句子分段嵌入张量, 是为了服务后续的两个句子为输入的预训练任务.
  • Position Embeddings 是位置编码张量, 此处注意和传统的Transformer不同, 不是三角函数计算的固定位置编码, 而是通过学习得出来的.
  • 整个Embedding模块的输出张量就是这3个张量的直接加和结果.

2.2 双向Transformer模块¶

BERT中只使用了经典Transformer架构中的Encoder部分, 完全舍弃了Decoder部分. 而两大预训练任务也集中体现在训练Transformer模块中.

2.3 预微调模块¶

  • 经过中间层Transformer的处理后, BERT的最后一层根据任务的不同需求而做不同的调整即可.
  • 比如对于sequence-level的分类任务, BERT直接取第一个[CLS] token 的final hidden state, 再加一层全连接层后进行softmax来预测最终的标签.
  • 对于不同的任务, 微调都集中在预微调模块, 几种重要的NLP微调任务架构图展示如下
  • 从上图中可以发现, 在面对特定任务时, 只需要对预微调层进行微调, 就可以利用Transformer强大的注意力机制来模拟很多下游任务, 并得到SOTA的结果. (句子对关系判断, 单文本主题分类, 问答任务(QA), 单句贴标签(NER))
  • 若干可选的超参数建议如下:
Batch size: 16, 32
Learning rate (Adam): 5e-5, 3e-5, 2e-5
Epochs: 3, 4

3 BERT的预训练任务¶

BERT包含两个预训练任务:

  • 任务一: Masked LM (带mask的语言模型训练)
  • 任务二: Next Sentence Prediction (下一句话预测任务)

3.1 任务一: Masked LM¶

带mask的语言模型训练

  • 关于传统的语言模型训练, 都是采用left-to-right, 或者left-to-right + right-to-left结合的方式, 但这种单向方式或者拼接的方式提取特征的能力有限. 为此BERT提出一个深度双向表达模型(deep bidirectional representation). 即采用MASK任务来训练模型.
  • 1: 在原始训练文本中, 随机的抽取15%的token作为参与MASK任务的对象.
  • 2: 在这些被选中的token中, 数据生成器并不是把它们全部变成[MASK], 而是有下列3种情况.
  • 2.1: 在80%的概率下, 用[MASK]标记替换该token, 比如my dog is hairy -> my dog is [MASK]
  • 2.2: 在10%的概率下, 用一个随机的单词替换token, 比如my dog is hairy -> my dog is apple
  • 2.3: 在10%的概率下, 保持该token不变, 比如my dog is hairy -> my dog is hairy
  • 3: 模型在训练的过程中, 并不知道它将要预测哪些单词? 哪些单词是原始的样子? 哪些单词被遮掩成了[MASK]? 哪些单词被替换成了其他单词? 正是在这样一种高度不确定的情况下, 反倒逼着模型快速学习该token的分布式上下文的语义, 尽最大努力学习原始语言说话的样子. 同时因为原始文本中只有15%的token参与了MASK操作, 并不会破坏原语言的表达能力和语言规则.

3.2 任务二: Next Sentence Prediction¶

下一句话预测任务

  • 在NLP中有一类重要的问题比如QA(Quention-Answer), NLI(Natural Language Inference), 需要模型能够很好的理解两个句子之间的关系, 从而需要在模型的训练中引入对应的任务. 在BERT中引入的就是Next Sentence Prediction任务. 采用的方式是输入句子对(A, B), 模型来预测句子B是不是句子A的真实的下一句话.
  • 1: 所有参与任务训练的语句都被选中作为句子A.
  • 1.1: 其中50%的B是原始文本中真实跟随A的下一句话. (标记为IsNext, 代表正样本)
  • 1.2: 其中50%的B是原始文本中随机抽取的一句话. (标记为NotNext, 代表负样本)
  • 2: 在任务二中, BERT模型可以在测试集上取得97%-98%的准确率.

4 小结¶

  • 学习了什么是BERT.
    • BERT是一个基于Transformer Encoder的预训练语言模型.
    • BERT在11种NLP测试任务中创出SOAT表现.
  • 学习了BERT的结构. - 最底层的Embedding模块, 包括Token Embeddings, Segment Embeddings, Position Embeddings.
    • 中间层的Transformer模块, 只使用了经典Transformer架构中的Encoder部分.
    • 最上层的预微调模块, 具体根据不同的任务类型来做相应的处理.
  • 学习了BERT的两大预训练任务.
    • MLM任务(Masked Language Model), 在原始文本中随机抽取15%的token参与任务.
      • 在80%概率下, 用[MASK]替换该token.
      • 在10%概率下, 用一个随机的单词替换该token.
      • 在10%概率下, 保持该token不变
    • NSP任务(Next Sentence Prediction), 采用的方式是输入句子对(A, B), 模型预测句子B是不是句子A的真实的下一句话.
      • 其中50%的B是原始文本中真实跟随A的下一句话.(标记为IsNext, 代表正样本)
      • 其中50%的B是原始文本中随机抽取的一句话. (标记为NotNext, 代表负样本)

2 Transformer Encoder模块

学习目标¶

  • 掌握Encoder模块的结构和作用
  • 掌握Decoder模块的结构和作用
  • 掌握其他模块的结构和作用

1 Encoder模块¶

1.1 Encoder模块的结构和作用:¶

  • 经典的Transformer结构中的Encoder模块包含6个Encoder Block.
  • 每个Encoder Block包含一个多头自注意力层, 和一个前馈全连接层.

1.2 关于Encoder Block¶

  • 在Transformer架构中, 6个一模一样的Encoder Block层层堆叠在一起, 共同组成完整的Encoder, 因此剖析一个Block就可以对整个Encoder的内部结构有清晰的认识.

1.3 多头自注意力层(self-attention)¶

首先来看self-attention的计算规则图:

  • 上述attention可以被描述为将query和key-value键值对的一组集合映射到输出, 输出被计算为values的加权和, 其中分配给每个value的权重由query与对应key的相似性函数计算得来. 这种attention的形式被称为Scaled Dot-Product Attention, 对应的数学公式形式如下:
    Attention(Q,K,V)=Softmax(\frac{Q\cdot K^T}{\sqrt{d_{k}}})\cdot V
    Attention(Q,K,V)=Softmax(\frac{Q\cdot K^T}{\sqrt{d_{k}}})\cdot V
  • 所谓的多头self-attention层, 则是先将Q, K, V经过参数矩阵进行映射, 再做self-attention, 最后将结果拼接起来送入一个全连接层即可.
  • 上述的多头self-attention, 对应的数学公式形式如下:
  • MultiHead(Q, K, V) = Concat(head_1, head_2, ..., head_h) \cdot W^o
    MultiHead(Q, K, V) = Concat(head_1, head_2, ..., head_h) \cdot W^o
  • head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)
    head_i = Attention(QW_i^Q, KW_i^K, VW_i^V)
  • 多头self-attention层的作用: 实验结果表明, Multi-head可以在更细致的层面上提取不同head的特征, 总体计算量和单一head相同的情况下, 提取特征的效果更佳.
  • 前馈全连接层模块 - 前馈全连接层模块, 由两个线性变换组成, 中间有一个Relu激活函数, 对应的数学公式形式如下:

\[FFN(x)=\max(0,xW_1+b_1)W_2+b_2 \]

  • 注意: 原版论文中的前馈全连接层, 输入和输出的维度均为d_model = 512, 层内的连接维度d_ff = 2048, 均采用4倍的大小关系.
  • 前馈全连接层的作用: 单纯的多头注意力机制并不足以提取到理想的特征, 因此增加全连接层来提升网络的能力.

1.4 Decoder模块¶

  • Decoder模块的结构和作用: - 经典的Transformer结构中的Decoder模块包含6个Decoder Block.
  • 每个Decoder Block包含三个子层.
    • 一个多头self-attention层
    • 一个Encoder-Decoder attention层
    • 一个前馈全连接层
  • Decoder Block中的多头self-attention层
    • Decoder中的多头self-attention层与Encoder模块一致, 但需要注意的是Decoder模块的多头self-attention需要做look-ahead-mask, 因为在预测的时候"不能看见未来的信息", 所以要将当前的token和之后的token全部mask.
  • Decoder Block中的Encoder-Decoder attention层 - 这一层区别于自注意力机制的Q = K = V, 此处矩阵Q来源于Decoder端经过上一个Decoder Block的输出, 而矩阵K, V则来源于Encoder端的输出, 造成了Q != K = V的情况.
    • 这样设计是为了让Decoder端的token能够给予Encoder端对应的token更多的关注.
  • Decoder Block中的前馈全连接层 - 此处的前馈全连接层和Encoder模块中的完全一样.
  • Decoder Block中有2个注意力层的作用: 多头self-attention层是为了拟合Decoder端自身的信息, 而Encoder-Decoder attention层是为了整合Encoder和Decoder的信息.

1.5 Add & Norm模块¶

  • Add & Norm模块接在每一个Encoder Block和Decoder Block中的每一个子层的后面. 具体来说Add表示残差连接, Norm表示LayerNorm. - 对于每一个Encoder Block, 里面的两个子层后面都有Add & Norm.
    • 对于每一个Decoder Block, 里面的三个子层后面都有Add & Norm.
    • 具体的数学表达形式为: LayerNorm(x + Sublayer(x)), 其中Sublayer(x)为子层的输出.
  • Add残差连接的作用: 和其他神经网络模型中的残差连接作用一致, 都是为了将信息传递的更深, 增强模型的拟合能力. 试验表明残差连接的确增强了模型的表现.
  • Norm的作用: 随着网络层数的额增加, 通过多层的计算后参数可能会出现过大, 过小, 方差变大等现象, 这会导致学习过程出现异常, 模型的收敛非常慢. 因此对每一层计算后的数值进行规范化可以提升模型的表现.

1.6 位置编码器Positional Encoding¶

  • Transformer中直接采用正弦函数和余弦函数来编码位置信息, 如下图所示:

    \[PE_{(pos, 2i)}=\sin(\frac{pos} {10000^{\frac{2i}{d_{model}}}})\\ PE_{(pos, 2i+1)}=\cos(\frac{pos} {10000^{\frac{2i}{d_{model}}}})\\ \]

  • 需要注意: 三角函数应用在此处的一个重要的优点, 因为对于任意的PE(pos+k), 都可以表示为PE(pos)的线性函数, 大大方便计算. 而且周期性函数不受序列长度的限制, 也可以增强模型的泛化能力.

    \[\sin(\alpha+\beta)=\sin(\alpha)\cos(\beta)+\cos(\alpha)\sin(\beta)\\ \cos(\alpha+\beta)=\cos(\alpha)\cos(\beta)-\sin(\alpha)\sin(\beta) \]

2 小结¶

  • Encoder模块 - 经典的Transformer架构中的Encoder模块包含6个Encoder Block.

    • 每个Encoder Block包含两个子模块, 分别是多头自注意力层, 和前馈全连接层.
      • 多头自注意力层采用的是一种Scaled Dot-Product Attention的计算方式, 实验结果表明, Mul
        ti-head可以在更细致的层面上提取不同head的特征, 比单一head提取特征的效果更佳.
      • 前馈全连接层是由两个全连接层组成, 线性变换中间增添一个Relu激活函数, 具体的维度采用4倍关系, 即多头自注意力的d_model=512, 则层内的变换维度d_ff=2048.
  • Decoder模块 - 经典的Transformer架构中的Decoder模块包含6个Decoder Block.

    • 每个Decoder Block包含3个子模块, 分别是多头自注意力层, Encoder-Decoder Attention层, 和前馈全连接层.
        • 多头自注意力层采用和Encoder模块一样的Scaled Dot-Product Attention的计算方式, 最大的
          区别在于需要添加look-ahead-mask, 即遮掩"未来的信息".
      • Encoder-Decoder Attention层和上一层多头自注意力层最主要的区别在于Q != K = V, 矩阵Q来源于上一层Decoder Block的输出, 同时K, V来源于Encoder端的输出.
      • 前馈全连接层和Encoder中完全一样.
  • Add & Norm模块

    • Add & Norm模块接在每一个Encoder Block和Decoder Block中的每一个子层的后面.
    • 对于每一个Encoder Block, 里面的两个子层后面都有Add & Norm.
    • 对于每一个Decoder Block, 里面的三个子层后面都有Add & Norm.
    • Add表示残差连接, 作用是为了将信息无损耗的传递的更深, 来增强模型的拟合能力.
    • Norm表示LayerNorm, 层级别的数值标准化操作, 作用是防止参数过大过小导致的学习过程异常, 模型收敛特别慢的问题.
  • 位置编码器Positional Encoding

      • Transformer中采用三角函数来计算位置编码.
    • 因为三角函数是周期性函数, 不受序列长度的限制, 而且这种计算方式可以对序列中不同位置的编码的重要程度同等看待.

3 Transformer Decoder模块

学习目标¶

  • 掌握Transformer结构中的Decoder端的输入张量特点和含义.
  • 掌握Decoder在训练阶段的输入是什么.
  • 掌握Decoder在预测阶段的输入是什么.

1 Decoder端的输入解析¶

1.1 Decoder端的架构¶

Transformer原始论文中的Decoder模块是由N=6个相同的Decoder Block堆叠而成, 其中每一个Block是由3个子模块构成, 分别是多头self-attention模块, Encoder-Decoder attention模块, 前馈全连接层模块.

  • 6个Block的输入不完全相同: - 最下面的一层Block接收的输入是经历了MASK之后的Decoder端的输入 + Encoder端的输出.
    • 其他5层Block接收的输入模式一致, 都是前一层Block的输出 + Encoder端的输出.

1.2 Decoder在训练阶段的输入解析¶

  • 从第二层Block到第六层Block的输入模式一致, 无需特殊处理, 都是固定操作的循环处理.
  • 聚焦在第一层的Block上: 训练阶段每一个time step的输入是上一个time step的输入加上真实标签序列向后移一位. 具体来说, 假设现在的真实标签序列等于"How are you?", 当time step=1时, 输入张量为一个特殊的token, 比如"SOS"; 当time step=2时, 输入张量为"SOS How"; 当time step=3时, 输入张量为"SOS How are", 以此类推...
  • 注意: 在真实的代码实现中, 训练阶段不会这样动态输入, 而是一次性的把目标序列全部输入给第一层的Block, 然后通过多头self-attention中的MASK机制对序列进行同样的遮掩即可.

1.3 Decoder在预测阶段的输入解析¶

  • 同理于训练阶段, 预测时从第二层Block到第六层Block的输入模式一致, 无需特殊处理, 都是固定操作的循环处理.
  • 聚焦在第一层的Block上: 因为每一步的输入都会有Encoder的输出张量, 因此这里不做特殊讨论, 只专注于纯粹从Decoder端接收的输入. 预测阶段每一个time step的输入是从time step=0, input_tensor="SOS"开始, 一直到上一个time step的预测输出的累计拼接张量. 具体来说: - 当time step=1时, 输入的input_tensor="SOS", 预测出来的输出值是output_tensor="What";
    • 当time step=2时, 输入的input_tensor="SOS What", 预测出来的输出值是output_tensor="is";
    • 当time step=3时, 输入的input_tensor="SOS What is", 预测出来的输出值是output_tensor="the";
    • 当time step=4时, 输入的input_tensor="SOS What is the", 预测出来的输出值是output_tensor="matter";
    • 当time step=5时, 输入的input_tensor="SOS What is the matter", 预测出来的输出值是output_tensor="?";
    • 当time step=6时, 输入的input_tensor="SOS What is the matter ?", 预测出来的输出值是output_tensor="EOS", 代表句子的结束符, 说明解码结束, 预测结束.

2 小结¶

  • 在Transformer结构中的Decoder模块的输入, 区分于不同的Block, 最底层的Block输入有其特殊的地方. 第二层到第六层的输入一致, 都是上一层的输出和Encoder的输出.
  • 最底层的Block在训练阶段, 每一个time step的输入是上一个time step的输入加上真实标签序列向后移一位. 具体来看, 就是每一个time step的输入序列会越来越长, 不断的将之前的输入融合进来.
  • 最底层的Block在训练阶段, 真实的代码实现中, 采用的是MASK机制来模拟输入序列不断添加的过程.
  • 最底层的Block在预测阶段, 每一个time step的输入是从time step=0开始, 一直到上一个time step的预测值的累积拼接张量. 具体来看, 也是随着每一个time step的输入序列会越来越长. 相比于训练阶段最大的不同是这里不断拼接进来的token是每一个time step的预测值, 而不是训练阶段每一个time step取得的groud truth值.

4 Self attention机制详解

学习目标¶

  • 掌握self-attention的机制和原理.
  • 掌握为什么要使用三元组(Q, K, V)来计算self-attention.
  • 理解softmax函数的输入是如何影响输出分布的.
  • 理解softmax函数反向传播进行梯度求导的数学过程.
  • 理解softmax函数出现梯度消失的原因.
  • 理解self-attention计算规则中归一化的原因.

1 Self-attention的特点¶

self-attention是一种通过自身和自身进行关联的attention机制, 从而得到更好的representation来表达自身.

self-attention是attention机制的一种特殊情况,在self-attention中, Q=K=V, 序列中的每个单词(token)都和该序列中的其他所有单词(token)进行attention规则的计算.

attention机制计算的特点在于, 可以直接跨越一句话中不同距离的token, 可以远距离的学习到序列的知识依赖和语序结构.

  • 从上图中可以看到, self-attention可以远距离的捕捉到语义层面的特征(its的指代对象是Law).

  • 应用传统的RNN, LSTM, 在获取长距离语义特征和结构特征的时候, 需要按照序列顺序依次计算, 距离越远的联系信息的损耗越大, 有效提取和捕获的可能性越小.

  • 但是应用self-attention时, 计算过程中会直接将句子中任意两个token的联系通过一个计算步骤直接联系起来,

    关于self-attention为什么要使用(Q, K, V)三元组而不是其他形式:

    • 首先一条就是从分析的角度看, 查询Query是一条独立的序列信息, 通过关键词Key的提示作用, 得到最终语义的真实值Value表达, 数学意义更充分, 完备.
    • 这里不使用(K, V)或者(V)没有什么必须的理由, 也没有相关的论文来严格阐述比较试验的结果差异, 所以可以作为开放性问题未来去探索, 只要明确在经典self-attention实现中用的是三元组就好.
      self-attention公式中的归一化有什么作用? 为什么要添加scaled?

2 Self-attention中的归一化概述¶

  • 训练上的意义: 随着词嵌入维度d_k的增大, q * k 点积后的结果也会增大, 在训练时会将softmax函数推入梯度非常小的区域, 可能出现梯度消失的现象, 造成模型收敛困难.
  • 数学上的意义: 假设q和k的统计变量是满足标准正态分布的独立随机变量, 意味着q和k满足均值为0, 方差为1. 那么q和k的点积结果就是均值为0, 方差为d_k, 为了抵消这种方差被放大d_k倍的影响, 在计算中主动将点积缩放1/sqrt(d_k), 这样点积后的结果依然满足均值为0, 方差为1.

3 softmax的梯度变化¶

这里我们分3个步骤来解释softmax的梯度问题:

  • 第一步: softmax函数的输入分布是如何影响输出的.
  • 第二步: softmax函数在反向传播的过程中是如何梯度求导的.
  • 第三步: softmax函数出现梯度消失现象的原因.

3.1 softmax函数的输入分布是如何影响输出的¶

  • 对于一个输入向量x, softmax函数将其做了一个归一化的映射, 首先通过自然底数e将输入元素之间的差距先"拉大", 然后再归一化为一个新的分布. 在这个过程中假设某个输入x中最大的元素下标是k, 如果输入的数量级变大(就是x中的每个分量绝对值都很大), 那么在数学上会造成y_k的值非常接近1.
  • 具体用一个例子来演示, 假设输入的向量x = [a, a, 2a], 那么随便给几个不同数量级的值来看看对y3产生的影响
a = 1时,   y3 = 0.5761168847658291
a = 10时,  y3 = 0.9999092083843412
a = 100时, y3 = 1.0
  • 采用一段实例代码将a在不同取值下, 对应的y3全部画出来, 以曲线的形式展示:
from math import exp
from matplotlib import pyplot as plt
import numpy as np 
f = lambda x: exp(x * 2) / (exp(x) + exp(x) + exp(x * 2))
x = np.linspace(0, 100, 100)
y_3 = [f(x_i) for x_i in x]
plt.plot(x, y_3)
plt.show()
  • 得到如下的曲线:
  • 从上图可以很清楚的看到输入元素的数量级对softmax最终的分布影响非常之大.
  • 结论: 在输入元素的数量级较大时, softmax函数几乎将全部的概率分布都分配给了最大值分量所对应的标签.

3.2 softmax函数在反向传播的过程中是如何梯度求导的¶

首先定义神经网络的输入和输出:
image-20250716185016399
反向传播就是输出端的损失函数对输入端求偏导的过程, 这里要分两种情况, 第一种如下所示:
image-20250716185039540
第二种如下所示:
image-20250716185058839
经过对两种情况分别的求导计算, 可以得出最终的结论如下:
image-20250716185113513

3.3 softmax函数出现梯度消失现象的原因¶

  • 根据第二步中softmax函数的求导结果, 可以将最终的结果以矩阵形式展开如下:
    image-20250716185130983
  • 根据第一步中的讨论结果, 当输入x的分量值较大时, softmax函数会将大部分概率分配给最大的元素, 假设最大元素是x1, 那么softmax的输出分布将产生一个接近one-hot的结果张量y_ = [1, 0, 0,..., 0], 此时结果矩阵变为:
    image-20250716185142006
  • 结论: 综上可以得出, 所有的梯度都消失为0(接近于0), 参数几乎无法更新, 模型收敛困难.

4 维度与点积大小的关系¶

  • 针对为什么维度会影响点积的大小, 原始论文中有这样的一点解释如下:
To illustrate why the dot products get large, assume that the components of q and k 
are independent random variables with mean 0 and variance 1. Then their doct product,
q*k = (q1k1+q2k2+......+q(d_k)k(d_k)), has mean 0 and variance d_k.
  • 我们分两步对其进行一个推导, 首先就是假设向量q和k的各个分量是相互独立的随机变量, X = q_i, Y = k_i, X和Y各自有d_k个分量, 也就是向量的维度等于d_k, 有E(X) = E(Y) = 0, 以及D(X) = D(Y) = 1.
  • 可以得到E(XY) = E(X)E(Y) = 0 * 0 = 0
  • 同理, 对于D(XY)推导如下:
    image-20250716185235630
  • 根据期望和方差的性质, 对于互相独立的变量满足下式:
    image-20250716185245327
  • 根据上面的公式, 可以很轻松的得出q*k的均值为E(qk) = 0, D(qk) = d_k.
  • 所以方差越大, 对应的qk的点积就越大, 这样softmax的输出分布就会更偏向最大值所在的分量.
  • 一个技巧就是将点积除以sqrt(d_k), 将方差在数学上重新"拉回1", 如下所示:
    image-20250716185257153
  • 最终的结论: 通过数学上的技巧将方差控制在1, 也就有效的控制了点积结果的发散, 也就控制了对应的梯度消失的问题!

5 小结¶

  • self-attention机制的重点是使用三元组(Q, K, V)参与规则运算, 这里面Q=K=V.
  • self-attention最大的优势是可以方便有效的提取远距离依赖的特征和结构信息, 不必向RNN那样依次计算产生传递损耗.
  • 关于self-attention采用三元组的原因, 经典实现的方式数学意义明确, 理由充分, 至于其他方式的可行性暂时没有论文做充分的对比试验研究.
  • 学习了softmax函数的输入是如何影响输出分布的. - softmax函数本质是对输入的数据分布做一次归一化处理, 但是输入元素的数量级对softmax最终的分布影响非常之大.
    • 在输入元素的数量级较大时, softmax函数几乎将全部的概率分布都分配给了最大值分量所对应的标签.
  • 学习了softmax函数在反向传播的过程中是如何梯度求导的. - 具体的推导过程见讲义正文部分, 注意要分两种情况讨论, 分别处理.
  • 学习了softmax函数出现梯度消失现象的原因. - 结合第一步, 第二步的结论, 可以很清楚的看到最终的梯度矩阵接近于零矩阵, 这样在进行参数更新的时候就会产生梯度消失现象.
  • 学习了维度和点积大小的关系推导. - 通过期望和方差的推导理解了为什么点积会造成方差变大.
    • 理解了通过数学技巧除以sqrt(d_k)就可以让方差恢复成1.

5 Multi head Attention详解

学习目标¶

  • 掌握Transformer中应用多头注意力的原因.
  • 掌握Transformer中多头注意力的计算方式.

1 采用Multi-head Attention的原因¶

  • 原始论文中提到进行Multi-head Attention的原因是将模型分为多个头, 可以形成多个子空间, 让模型去关注不同方面的信息, 最后再将各个方面的信息综合起来得到更好的效果.
  • 多个头进行attention计算最后再综合起来, 类似于CNN中采用多个卷积核的作用, 不同的卷积核提取不同的特征, 关注不同的部分, 最后再进行融合.
  • 直观上讲, 多头注意力有助于神经网络捕捉到更丰富的特征信息.

2 Multi-head Attention的计算方式¶

  • Multi-head Attention和单一head的Attention唯一的区别就在于, 其对特征张量的最后一个维度进行了分割, 一般是对词嵌入的embedding_dim=512进行切割成head=8, 这样每一个head的嵌入维度就是512/8=64, 后续的Attention计算公式完全一致, 只不过是在64这个维度上进行一系列的矩阵运算而已.
  • 在head=8个头上分别进行注意力规则的运算后, 简单采用拼接concat的方式对结果张量进行融合就得到了Multi-head Attention的计算结果.

3 小结¶

  • 学习了Transformer架构采用Multi-head Attention的原因. - 将模型划分为多个头, 分别进行Attention计算, 可以形成多个子空间, 让模型去关注不同方面的信息特征, 更好的提升模型的效果.
    • 多头注意力有助于神经网络捕捉到更丰富的特征信息.
  • 学习了Multi-head Attention的计算方式. - 对特征张量的最后一个维度进行了分割, 一般是对词嵌入的维度embedding_dim进行切割, 切割后的计算规则和单一head完全一致.
    • 在不同的head上应用了注意力计算规则后, 得到的结果张量直接采用拼接concat的方式进行融合, 就得到了Multi-head Attention的结果张量.

6 Transformer优势

学习目标¶

  • 掌握Transformer相比于RNN/LSTM的优势和背后的原因.
  • 掌握Transformer架构的并行化是如何进行的.
  • 理解为什么采用这样的方式可以实现Transformer的并行化.
  • 掌握Transformer可以替代seq2seq的核心原因.

1 Transformer的并行计算¶

对于Transformer比传统序列模型RNN/LSTM具备优势的第一大原因就是强大的并行计算能力.

  • 对于RNN来说, 任意时刻t的输入是时刻t的输入x(t)和上一时刻的隐藏层输出h(t-1), 经过运算后得到当前时刻隐藏层的输出h(t), 这个h(t)也即将作为下一时刻t+1的输入的一部分. 这个计算过程是RNN的本质特征, RNN的历史信息是需要通过这个时间步一步一步向后传递的. 而这就意味着RNN序列后面的信息只能等到前面的计算结束后, 将历史信息通过hidden state传递给后面才能开始计算, 形成链式的序列依赖关系, 无法实现并行.
  • 对于Transformer结构来说, 在self-attention层, 无论序列的长度是多少, 都可以一次性计算所有单词之间的注意力关系, 这个attention的计算是同步的, 可以实现并行.

2 Transformer架构的并行化过程¶

2.1 Transformer架构中Encoder的并行化¶

首先Transformer的并行化主要体现在Encoder模块上.

  • 上图最底层绿色的部分, 整个序列所有的token可以并行的进行Embedding操作, 这一层的处理是没有依赖关系的.
  • 上图第二层土黄色的部分, 也就是Transformer中最重要的self-attention部分, 这里对于任意一个单词比如x1, 要计算x1对于其他所有token的注意力分布, 得到z1. 这个过程是具有依赖性的, 必须等到序列中所有的单词完成Embedding才可以进行. 因此这一步是不能并行处理的. 但是从另一个角度看, 我们真实计算注意力分布的时候, 采用的都是矩阵运算, 也就是可以一次性的计算出所有token的注意力张量, 从这个角度看也算是实现了并行, 只是矩阵运算的"并行"和词嵌入的"并行"概念上不同而已.
  • 上图第三层蓝色的部分, 也就是前馈全连接层, 对于不同的向量z之间也是没有依赖关系的, 所以这一层是可以实现并行化处理的. 也就是所有的向量z输入Feed Forward网络的计算可以同步进行, 互不干扰.

2.2 Transformer架构中Decoder的并行化¶

其次Transformer的并行化也部分的体现在Decoder模块上.

  • Decoder模块在训练阶段采用了并行化处理. 其中Self-Attention和Encoder-Decoder Attention两个子层的并行化也是在进行矩阵乘法, 和Encoder的理解是一致的. 在进行Embedding和Feed Forward的处理时, 因为各个token之间没有依赖关系, 所以也是可以完全并行化处理的, 这里和Encoder的理解也是一致的.
  • Decoder模块在预测阶段基本上不认为采用了并行化处理. 因为第一个time step的输入只是一个"SOS", 后续每一个time step的输入也只是依次添加之前所有的预测token.
  • 注意: 最重要的区别是训练阶段目标文本如果有20个token, 在训练过程中是一次性的输入给Decoder端, 可以做到一些子层的并行化处理. 但是在预测阶段, 如果预测的结果语句总共有20个token, 则需要重复处理20次循环的过程, 每次的输入添加进去一个token, 每次的输入序列比上一次多一个token, 所以不认为是并行处理.

3 Transformer的特征抽取能力¶

对于Transformer比传统序列模型RNN/LSTM具备优势的第二大原因就是强大的特征抽取能力.

  • Transformer因为采用了Multi-head Attention结构和计算机制, 拥有比RNN/LSTM更强大的特征抽取能力, 这里并不仅仅由理论分析得来, 而是大量的试验数据和对比结果, 清楚的展示了Transformer的特征抽取能力远远胜于RNN/LSTM.
  • 注意: 不是越先进的模型就越无敌, 在很多具体的应用中RNN/LSTM依然大有用武之地, 要具体问题具体分析.

4 为什么说Transformer可以代替seq2seq?¶

4.1 seq2seq的两大缺陷¶

  • seq2seq架构的第一大缺陷是将Encoder端的所有信息压缩成一个固定长度的语义向量中, 用这个固定的向量来代表编码器端的全部信息. 这样既会造成信息的损耗, 也无法让Decoder端在解码的时候去用注意力聚焦哪些是更重要的信息.
  • seq2seq架构的第二大缺陷是无法并行, 本质上和RNN/LSTM无法并行的原因一样.

4.2 Transformer的改进¶

  • Transformer架构同时解决了seq2seq的两大缺陷, 既可以并行计算, 又应用Multi-head Attention机制来解决Encoder固定编码的问题, 让Decoder在解码的每一步可以通过注意力去关注编码器输出中最重要的那些部分.

5 小结¶

  • 学习了Transformer相比于RNN/LSTM的优势和原因.

      • 第一大优势是并行计算的优势.
    • 第二大优势是特征提取能力强.
  • 学习了Transformer架构中Encoder模块的并行化机制.

      • Encoder模块在训练阶段和测试阶段都可以实现完全相同的并行化.
    • Encoder模块在Embedding层, Feed Forward层, Add & Norm层都是可以并行化的.
    • Encoder模块在self-attention层, 因为各个token之间存在依赖关系, 无法独立计算, 不是真正意义上的并行化.
    • Encoder模块在self-attention层, 因为采用了矩阵运算的实现方式, 可以一次性的完成所有注意力张量的计算, 也是另一种"并行化"的体现.
  • 学习了Transformer架构中Decoder模块的并行化机制.

      • Decoder模块在训练阶段可以实现并行化.
    • Decoder模块在训练阶段的Embedding层, Feed Forward层, Add & Norm层都是可以并行化的.
    • Decoder模块在self-attention层, 以及Encoder-Decoder Attention层, 因为各个token之间存在依赖关系, 无法独立计算, 不是真正意义上的并行化.
    • Decoder模块在self-attention层, 以及Encoder-Decoder Attention层, 因为采用了矩阵运算的实现方式, 可以一次性的完成所有注意力张量的计算, 也是另一种"并行化"的体现.
    • Decoder模块在预测计算不能并行化处理.
  • 学习了seq2seq架构的两大缺陷.

      • 第一个缺陷是Encoder端的所有信息被压缩成一个固定的输出张量, 当序列长度较长时会造成比较严重的信息损耗.
    • 第二个缺陷是无法并行计算.
  • 学习了Transformer架构对seq2seq两大缺陷的改进.

      • Transformer应用Multi-head Attention机制让编码器信息可以更好的展示给解码器.
    • Transformer可以实现Encoder端的并行计算.

7 BERT模型特点

学习目标¶

  • 理解BERT模型的优点和原因.
  • 理解BERT模型的缺点和原因.
  • 理解在MLM任务中采用80%, 10%, 10%策略的原因.
  • 掌握利用BERT处理长文本的任务如何构造训练样本.

1 BERT模型优缺点¶

1.1 BERT的优点¶

  • 通过预训练, 加上Fine-tunning, 在11项NLP任务上取得最优结果.
  • BERT的根基源于Transformer, 相比传统RNN更加高效, 可以并行化处理同时能捕捉长距离的语义和结构依赖.
  • BERT采用了Transformer架构中的Encoder模块, 不仅仅获得了真正意义上的bidirectional context, 而且为后续微调任务留出了足够的调整空间.

1.2 BERT的缺点¶

  • BERT模型过于庞大, 参数太多, 不利于资源紧张的应用场景, 也不利于上线的实时处理.
  • BERT目前给出的中文模型中, 是以字为基本token单位的, 很多需要词向量的应用无法直接使用. 同时该模型无法识别很多生僻词, 只能以UNK代替.
  • BERT中第一个预训练任务MLM中, [MASK]标记只在训练阶段出现, 而在预测阶段不会出现, 这就造成了一定的信息偏差, 因此训练时不能过多的使用[MASK], 否则会影响模型的表现.
  • 按照BERT的MLM任务中的约定, 每个batch数据中只有15%的token参与了训练, 被模型学习和预测, 所以BERT收敛的速度比left-to-right模型要慢很多(left-to-right模型中每一个token都会参与训练).

2 BERT的MLM任务¶

2.1 BERT的MLM任务中为什么采用了80%, 10%, 10%的策略?¶

  • 首先, 如果所有参与训练的token被100%的[MASK], 那么在fine-tunning的时候所有单词都是已知的, 不存在[MASK], 那么模型就只能根据其他token的信息和语序结构来预测当前词, 而无法利用到这个词本身的信息, 因为它们从未出现在训练过程中, 等于模型从未接触到它们的信息, 等于整个语义空间损失了部分信息. 采用80%的概率下应用[MASK], 既可以让模型去学着预测这些单词, 又以20%的概率保留了语义信息展示给模型.
  • 保留下来的信息如果全部使用原始token, 那么模型在预训练的时候可能会偷懒, 直接照抄当前token信息. 采用10%概率下random token来随机替换当前token, 会让模型不能去死记硬背当前的token, 而去尽力学习单词周边的语义表达和远距离的信息依赖, 尝试建模完整的语言信息.
  • 最后再以10%的概率保留原始的token, 意义就是保留语言本来的面貌, 让信息不至于完全被遮掩, 使得模型可以"看清"真实的语言面貌.

3 BERT处理长文本的方法¶

首选要明确一点, BERT预训练模型所接收的最大sequence长度是512.
那么对于长文本(文本长度超过512的句子), 就需要特殊的方式来构造训练样本. 核心就是如何进行截断.

  • head-only方式: 这是只保留长文本头部信息的截断方式, 具体为保存前510个token (要留两个位置给[CLS]和[SEP]).
  • tail-only方式: 这是只保留长文本尾部信息的截断方式, 具体为保存最后510个token (要留两个位置给[CLS]和[SEP]).
  • head+only方式: 选择前128个token和最后382个token (文本总长度在800以内), 或者前256个token和最后254个token (文本总长度大于800).

4 小结¶

  • 学习了BERT模型的3个优点:

      • 在11个NLP任务上取得SOAT成绩.
    • 利用了Transformer的并行化能力以及长语句捕捉语义依赖和结构依赖.
    • BERT实现了双向Transformer并为后续的微调任务留出足够的空间.
  • 学习了BERT模型的4个缺点:

      • BERT模型太大, 太慢.
    • BERT模型中的中文模型是以字为基本token单位的, 无法利用词向量, 无法识别生僻词.
    • BERT模型中的MLM任务, [MASK]标记在训练阶段出现, 预测阶段不出现, 这种偏差会对模型有一定影响.
    • BERT模型的MLM任务, 每个batch只有15%的token参与了训练, 造成大量文本数据的"无用", 收敛速度慢, 需要的算力和算时都大大提高.
  • 学习了长文本处理如果要利用BERT的话, 需要进行截断处理.

      • 第一种方式就是只保留前面510个token.
    • 第二种方式就是只保留后面510个token.
    • 第三种方式就是前后分别保留一部分token, 总数是510.
  • BERT中MLM任务中的[MASK]是以一种显示的方式告诉模型"这个词我不告诉你, 你自己从上下文里猜", 非常类似于同学们在做完形填空. 如果[MASK]意外的部分全部都用原始token, 模型会学习到"如果当前词是[MASK], 就根据其他词的信息推断这个词; 如果当前词是一个正常的单词, 就直接照抄". 这样一来, 到了fine-tunning阶段, 所有单词都是正常单词了, 模型就会照抄所有单词, 不再提取单词之间的依赖关系了.

  • BERT中MLM任务以10%的概率填入random token, 就是让模型时刻处于"紧张情绪"中, 让模型搞不清楚当前看到的token是真实的单词还是被随机替换掉的单词, 这样模型在任意的token位置就只能把当前token的信息和上下文信息结合起来做综合的判断和建模. 这样一来, 到了fine-tunning阶段, 模型也会同时提取这两方面的信息, 因为模型"心理很紧张", 它不知道当前看到的这个token, 所谓的"正常单词"到底有没有"提前被动过手脚".


8 ELMo模型介绍

学习目标¶

  • 了解什么是ELMo.
  • 掌握ELMo的架构.
  • 掌握ELMo的预训练任务.
  • 了解ELMo的效果和成绩.
  • 了解ELMo的优缺点.

1 ELMo简介¶

ELMo是2018年3月由华盛顿大学提出的一种预训练模型.

  • ELMo的全称是Embeddings from Language Models.
  • ELMo模型的提出源于论文<< Deep Contextualized Word Representations >>.
  • ELMo模型提出的动机源于研究人员认为一个好的预训练语言模型应该能够包含丰富的句法和语义信息, 并且能够对多义词进行建模. 而传统的词向量(2013年的word2vec, 2014年的GloVe)都是上下文无关的, 也就是固定的词向量. 最典型的例子就是"apple"在不同的语境下, 应该可以表示水果或公司, 但是固定的词向量显然无法做到这一点. 因为研究团队利用新的语言模型训练一个上下文相关的预训练模型, 成为ELMo, 并在6个NLP任务上获得提升.

2 ELMo的架构¶

2.1 总体架构¶


从上面的架构图中可以看到, 宏观上ELMo分三个主要模块.

  • 最底层黄色标记的Embedding模块.
  • 中间层蓝色标记的两部分双层LSTM模块.
  • 最上层绿色标记的词向量表征模块.

2.2 Embedding模块¶

ELMo最底层的词嵌入采用CNN对字符级进行编码, 本质就是获得一个静态的词嵌入向量作为网络的底层输入.

2.3 两部分的双层LSTM模块¶

  • 这是整个ELMo中最重要的部分, 架构中分成左侧的前向LSTM网络, 和右侧的反向LSTM网络.
  • ELMo的做法是我们只预训练一个Language Model, 而word embedding是通过输入的句子实时给出的, 这样单词的嵌入向量就包含了上下文的信息, 也就彻底改变了Word2Vec和GloVe的静态词向量的做法.
  • ELMo的这一模块分为左右两部分, 本质上就是一个双向LM, 对于左半部分, 给定了N个tokens(t1, t2, ..., tN), Language Model通过前面k-1个位置的token序列来计算第k个token出现的概率, 构成前向双层LSTM模型.
    image-20250716185504587
  • 同理, 对于架构中的右半部分, 给定了N个tokens(t(k+1), t(k+2), ..., t(N)), Language Model通过后面N-k个位置的token序列来计算第k个token出现的概率, 构成后向双层LSTM模型.
    image-20250716185514346
  • ELMo在训练过程中的目标函数就是最大化下面的公式:
    image-20250716185523096

2.4 词向量表征模块¶

  • 因为ELMo是个语言模型, 对于每个token, 通过一个L层的双向LSTM网络可以计算出2L+1个表示向量如下:
    image-20250716185532987
  • 从上面的公式可以清楚的看到, 有3个不同的组成部分, 第一个就是对token直接进行CNN编码的结果, 也是ELMo最底层模块的输出; 第二个就是前向LSTM的输出结果, 每一层都会有一个输出, 总共L层就会有L个输出; 第三个就是后向LSTM的输出结果, 每一层都会有一个输出, 总共L层就会有L个输出; 综合三部分的输出加在一起, 就是2L+1个输出向量.
  • 通过整个网络, 每一个token得到了2L+1个表示向量, 但是我们希望每一个token能对应一个向量. 最简单的做法就是取最上层的输出结果作为token的表示向量, 更通用的做法是加入若干参数来融合所有层的信息, 如下所示:
    image-20250716185539801
  • 上式的意思是对于2L+1个向量, 每一个前面都加上一个权重稀疏, 然后直接融合成一个向量, 最后再乘一个系数作为最终该token的词向量.
  • 原始论文中提到最前面的那个系数, 在不同任务中取不同的值效果会有较大的差异, 需要注意在SQuAD中设置为0.01取得的效果要好于设置为1.
  • 原始论文中在进行底层token编码时, 用CNN形成了一个512维的列向量, 也就是初始嵌入维度等于512. 中间层使用了双层的LSTM分别进行前向编码和后向编码, 每层的单个LSTM输入维度是512, 输出维度也是512, 保持一致. 因为是双向编码并且分左右两部分, 所以每层的输出维度是512*2=1024, 最后进行权重融合后的向量维度就是1024.

3 ELMo的预训练任务¶

3.1 ELMo的本质思想¶

  • 首先用一个语言模型学好一个单词的word embedding, 此时是无法区分多义词的, 但没关系. 当实际使用word embedding的时候, 该单词已经具备了特定的上下文信息, 这个时候可以根据上下文单词的语义去调整单词的word embedding表示, 这样经过调整后得到的word embedding向量就可以准确的表达单词在当前上下文中的真实含义了, 也就自然的解决了多义词问题.
  • 结论就是ELMo模型是个根据当前上下文对word embedding动态调整的语言模型.

3.2 ELMo的预训练采用了典型的两阶段过程¶

  • 第一阶段: 利用语言模型进行预训练.
  • 第二阶段: 在做下游任务时, 从预训练网络中提取对应单词的网络各层的word embedding作为新特征补充到下游任务中.
  • 第一阶段: 语言模型预训练.
  • 再次回到ELMo的总体架构图, 网络结构采用了双层双向LSTM.
    • 目前语言模型训练的任务目标是根据单词Wi的上下文去正确预测单词Wi, Wi之前的单词序列context-before称为上文, Wi之后的单词序列context-after称为下文.
    • 架构图上左侧的前向双层LSTM代表正方向编码器, 输入的是从左向右顺序的除了预测单词Wi之外的上文context-before; 右侧的反向双层LSTM代表反方向编码器, 输入的是从右向左的逆序的下文context-after;
    • 每个编码器的深度都是L=2, 即双层LSTM叠加.
    • 使用上述的网络结构利用大量语料做语言模型任务就能预训练好这个网络. 当输入一个新句子S_new时, 句子中每个单词都能得到对应的3个embedding向量: 1-最底层的单词的word embedding. 2-中间第一层双向LSTM中对应单词位置的embedding, 这层编码对应单词的句法信息更多一些. 3-中间第二层双向LSTM中对应单词位置的embedding, 这层编码对应单词的语义信息更多一些.
    • ELMo的预训练过程不仅仅学会了单词的word embedding, 还学习了一个双层双向的LSTM网络, 这两者后续都会用到, 是整个ELMo预训练的两大产出结果.
  • 第二阶段: 下游任务的调整.
  • 比如我们的下游任务是QA问题.
    • 对于问句X, 可以先将句子X作为预训练好的ELMo网络的输入, 这样X中每个单词在ELMo中都能获得3个对应的embedding向量. 之后赋给这3个向量各自一个权重a, 这个权重a既可以是学习得来的也可以是最简单的平均分布赋值, 然后把3个向量加权求和, 整个成一个词向量. 最后将整合后的词向量作为X在自己任务的那个网络结构中对应单词的输入, 以此作为新特征补充进下游任务中. 对于回答Y可以同样处理.
    • 因为ELMo给下游提供的是每个单词的特征形式, 所以这一类预训练方法被称为"Feature-based Pre-Training".

4 ELMo模型的效果¶

ELMo对于多义词问题的解决结果:

  • 前面提到静态的word embedding无法解决多义词的问题, 那么ELMo引入上下文动态语义调整后的embedding word可以解决多义词问题吗? 答案正如上图所示, 而且比我们期待的解决效果要更好.
  • 上图中的例子, 对于GloVe训练出来的word embedding来说, 多义词比如play, 根据它的embedding找出最接近其语义的单词, 发现结果集合几乎全部都在体育领域, 这很明显是因为训练数据中包含play的语句中体育领域的数量明显占多数导致的.
  • 再来看使用ELMo后的效果, 根据上下文动态调整后的embedding word不仅仅能找出对应于"play":"演出"的相同语义的句子, 而且还可以保证找出的句子中的play对应的词性也是相同的, 这真的是超出期待之外的惊喜!
  • 原始论文中提到ELMo的试验效果, 在6个NLP主流任务中性能都有不同幅度的提升, 最高的提升达到25%, 任务的覆盖范围很广, 包含句子语义关系判断, 分类任务, 阅读理解等等.

5 ELMo的待改进点¶

ELMo在传统静态word embedding方法(Word2Vec, GloVe)的基础上提升了很多, 但是依然存在缺陷, 有很大的改进余地.

  • 第一点: 一个很明显的缺点在于特征提取器的选择上, ELMo使用了双向双层LSTM, 而不是现在横扫千军的Transformer, 在特征提取能力上肯定是要弱一些的. 设想如果ELMo的提升提取器选用Transformer, 那么后来的BERT的反响将远不如当时那么火爆了.
  • 第二点: ELMo选用双向拼接的方式进行特征融合, 这种方法肯定不如BERT一体化的双向提取特征好.

6 小结¶

  • 学习了什么是ELMo.

    • ELMo是2018年3月由华盛顿大学提出的一种预训练语言模型.
    • ELMo在6种NLP测试任务中有很大的提升表现.
  • 学习了ELMo的结构.

    • ELMo架构总体上采用了双向双层LSTM的结构.
    • 最底层的Embedding模块.
    • 中间层的双向双层LSTM模块.
    • 最上层的特征融合模块.
  • 学习了ELMo的预训练任务.

    • ELMo的本质思想就是根据当前上下文对word embedding进行动态调整的语言模型.
    • ELMo的预训练是一个明显的两阶段过程.
      • 第一阶段: 利用语言模型进行预训练, 得到基础静态词向量和双向双层LSTM网络.
      • 第二阶段: 在拥有上下文的环境中, 将上下文输入双向双层LSTM中, 得到动态调整后的word embedding, 等于将单词融合进了上下文的语义, 可以更准确的表达单词的真实含义.
  • 学习了ELMo的效果.

      • 经过与GloVe静态词向量的对比, 明显可以看出ELMo的词向量可以更好的表达真实语义, 更好的解决多义词的问题.
  • 学习了ELMo的待改进点.

      • ELMo的特征提取器没有选用更强大的Transformer, 在提取特征上肯定弱于现在的最优结果.

9 GPT模型介绍

学习目标¶

  • 了解什么是GPT.
  • 掌握GPT的架构.
  • 掌握GPT的预训练任务.

1 GPT介绍¶

  • GPT是OpenAI公司提出的一种语言预训练模型.
  • OpenAI在论文<< Improving Language Understanding by Generative Pre-Training >>中提出GPT模型.
  • OpenAI后续又在论文<< Language Models are Unsupervised Multitask Learners >>中提出GPT2模型.
  • GPT和GPT2模型结构差别不大, 但是GPT2采用了更大的数据集进行训练.
  • OpenAI GPT模型是在Google BERT模型之前提出的, 与BERT最大的区别在于GPT采用了传统的语言模型方法进行预训练, 即使用单词的上文来预测单词, 而BERT是采用了双向上下文的信息共同来预测单词.
  • 正是因为训练方法上的区别, 使得GPT更擅长处理自然语言生成任务(NLG), 而BERT更擅长处理自然语言理解任务(NLU).

2 GPT的架构¶

  • 看三个语言模型的对比架构图, 中间的就是GPT:
  • 从上图可以很清楚的看到GPT采用的是单向Transformer模型, 例如给定一个句子[u1, u2, ..., un], GPT在预测单词ui的时候只会利用[u1, u2, ..., u(i-1)]的信息, 而BERT会同时利用上下文的信息[u1, u2, ..., u(i-1), u(i+1), ..., un].
  • 作为两大模型的直接对比, BERT采用了Transformer的Encoder模块, 而GPT采用了Transformer的Decoder模块. 并且GPT的Decoder Block和经典Transformer Decoder Block还有所不同, 如下图所示:
  • 如上图所示, 经典的Transformer Decoder Block包含3个子层, 分别是Masked Multi-Head Attention层, encoder-decoder attention层, 以及Feed Forward层. 但是在GPT中取消了第二个encoder-decoder attention子层, 只保留Masked Multi-Head Attention层, 和Feed Forward层.
  • 作为单向Transformer Decoder模型, GPT利用句子序列信息预测下一个单词的时候, 要使用Masked Multi-Head Attention对单词的下文进行遮掩, 来防止未来信息的提前泄露. 例如给定一个句子包含4个单词[A, B, C, D], GPT需要用[A]预测B, 用[A, B]预测C, 用[A, B, C]预测D. 很显然的就是当要预测B时, 需要将[B, C, D]遮掩起来.
  • 具体的遮掩操作是在slef-attention进行softmax之前进行的, 一般的实现是将MASK的位置用一个无穷小的数值-inf来替换, 替换后执行softmax计算得到新的结果矩阵. 这样-inf的位置就变成了0. 如上图所示, 最后的矩阵可以很方便的做到当利用A预测B的时候, 只能看到A的信息; 当利用[A, B]预测C的时候, 只能看到A, B的信息.
  • 注意: 对比于经典的Transformer架构, 解码器模块采用了6个Decoder Block; GPT的架构中采用了12个Decoder Block.

3 GPT训练过程¶

GPT的训练也是典型的两阶段过程:

  • 第一阶段: 无监督的预训练语言模型.
  • 第二阶段: 有监督的下游任务fine-tunning.

3.1 无监督的预训练语言模型¶

image-20250716185838703

3.2 有监督的下游任务fine-tunning¶

image-20250716185909445

4 小结¶

  • 学习了什么是GPT.

    • GPT是OpenAI公司提出的一种预训练语言模型.
    • 本质上来说, GPT是一个单向语言模型.
  • 学习了GPT的架构.

    • GPT采用了Transformer架构中的解码器模块.
    • GPT在使用解码器模块时做了一定的改造, 将传统的3层Decoder Block变成了2层Block, 删除了encoder-decoder attention子层, 只保留Masked Multi-Head Attention子层和Feed Forward子层.
    • GPT的解码器总共是由12个改造后的Decoder Block组成的.
  • 学习了GPT的预训练任务.

    • 第一阶段: 无监督的预训练语言模型. 只利用单词前面的信息来预测当前单词.
    • 第二阶段: 有监督的下游任务fine-tunning.

10 GPT2模型介绍

学习目标¶

  • 掌握GPT2的架构
  • 掌握GPT2的训练任务和模型细节

1 GPT2的架构¶

从模型架构上看, GPT2并没有特别新颖的架构, 它和只带有解码器模块的Transformer很像.
所谓语言模型, 作用就是根据已有句子的一部分, 来预测下一个单词会是什么. 现实应用中大家最熟悉的一个语言模型应用, 就是智能手机上的输入法, 它可以根据当前输入的内容智能推荐下一个要打的字.

GPT2也是一个语言预测生成模型, 只不过比手机上应用的模型要大很多, 也更加复杂. 常见的手机端应用的输入法模型基本占用50MB空间, 而OpenAI的研究人员使用了40GB的超大数据集来训练GPT2, 训练后的GPT2模型最小的版本也要占用超过500MB空间来存储所有的参数, 至于最大版本的GPT2则需要超过6.5GB的存储空间.
自从Transformer问世以来, 很多预训练语言模型的工作都在尝试将编码器或解码器堆叠的尽可能高, 那类似的模型可以堆叠到多深呢? 事实上, 这个问题的答案也就是区别不同GPT2版本的主要因素之一. 比如最小版本的GPT2堆叠了12层, 中号的24层, 大号的36层, 超大号的堆叠了整整48层!

2 GPT2模型的细节¶

以机器人第一法则为例, 来具体看GPT2的工作细节.

  • 机器人第一法则: 机器人不得伤害人类, 或者目睹人类将遭受危险而袖手旁观.

2.1 模型过程¶

首先明确一点: GPT2的工作流程很像传统语言模型, 一次只输出一个单词(token).

GPT2之所以在生成式任务中表现优秀, 是因为在每个新单词(token)产生后, 该单词就被添加在之前生成的单词序列后面, 添加后的新序列又会成为模型下一步的新输入. 这种机制就叫做自回归(auto-regression), 如下所示:

其次明确一点: GPT2模型是一个只包含了Transformer Decoder模块的模型.

和BERT模型相比, GPT2的解码器在self-attention层上有一个关键的差异: 它将后面的单词(token)遮掩掉, 而BERT是按照一定规则将单词替换成[MASK].

举个例子, 如果我们重点关注4号位置的单词及其前序路径, 我们可以让模型只允许注意当前计算的单词和它之前的单词, 如下图所示:

注意: 能够清楚的区分BERT使用的自注意力模块(self-attention)和GPT2使用的带掩码的自注意力模块(masked self-attention)很重要! 普通的self-attention允许模型的任意一个位置看到它右侧的信息(下图左侧), 而带掩码的self-attention则不允许这么做(下图右侧).

在Transformer原始论文发表后, 一篇名为<< Generating Wikipedia by Summarizing Long Sequences >>的论文提出用另一种Transformer模块的排列方式来进行语言建模-它直接扔掉了编码器, 只保留解码器. 这个早期的基于Transformer的模型由6个Decoder Block堆叠而成:

上图中所有的解码器模块都是一样的, 因为只展开了第一个解码器的内部结构. 和GPT一样, 只保留了带掩码的self-attention子层, 和Feed Forward子层.
这些解码器和经典Transformer原始论文中的解码器模块相比, 除了删除了第二个Encoder-Decoder Attention子层外, 其他构造都一样.

2.2 GPT2工作细节探究¶

  • GPT2可以处理最长1024个单词的序列.
  • 每个单词都会和它的前序路径一起"流经"所有的解码器模块.
  • 对于生成式模型来说, 基本工作方式都是提供一个预先定义好的起始token, 比如记做"s".
  • 此时模型的输入只有一个单词, 所以只有这个单词的路径是活跃的. 单词经过层层处理, 最终得到一个词向量. 该向量可以对于词汇表的每个单词计算出一个概率(GPT2的词汇表中有50000个单词). 在本例中, 我们选择概率最高的单词["The"]作为下一个单词.
  • 注意: 这种选择最高概率输出的策略有时会出现问题-如果我们持续点击输入法推荐单词的第一个, 它可能会陷入推荐同一个词的循环中, 只有你点击第二个或第三个推荐词, 才能跳出这种循环. 同理, GPT2有一个top-k参数, 模型会从概率最大的前k个单词中抽样选取下一个单词.
  • 接下来, 我们将输出的单词["The"]添加在输入序列的尾部, 从而构建出新的输入序列["s", "The"], 让模型进行下一步的预测:
  • 此时第二个单词的路径是当前唯一活跃的路径了. GPT2的每一层都保留了它们对第一个单词的解释, 并且将运用这些信息处理第二个单词, GPT2不会根据第二个单词重新来解释第一个单词.
  • 关于输入编码: 当我们更加深入的了解模型的内部细节时, 最开始就要面对模型的输入, 和其他自然语言模型一样, GPT2同样从嵌入矩阵中查找单词对应的嵌入向量, 该矩阵(embedding matrix)也是整个模型训练结果的一部分.
  • 如上图所示, 每一行都是一个词嵌入向量: 一个能够表征某个单词, 并捕获其语义的数字向量. 嵌入的维度大小和GPT2模型的大小相关, 最小的模型采用了768这个维度, 最大的采用了1600这个维度.
  • 所以在整个模型运作起来的最开始, 我们需要在嵌入矩阵中查找起始单词"s"对应的嵌入向量. 但在将其输入给模型之前, 还需要引入位置编码(positional encoding), 1024分输入序列位置中的每一个都对应了一个位置编码, 同理于词嵌入矩阵, 这些位置编码组成的矩阵也是整个模型训练结果的一部分.
  • 经历前面的1, 2两步, 输入单词在进入模型第一个transformer模块前的所有处理步骤就结束了. 综上所述, GPT2模型包含两个权值矩阵: 词嵌入矩阵和位置编码矩阵. 而输入到transformer模块中的张量就是这两个矩阵对应的加和结果.

    transformer模块的堆叠:
  • 最底层的transformer模块处理单词的步骤:
    • 首先通过自注意力层处理, 接着将其传递给前馈全连接层, 这其中包含残差连接和Layer Norm等子层操作.
    • 最底层的transformer模块处理结束后, 会将结果张量传递给第二层的transformer模块, 继续进行计算.
    • 每一个transformer模块的处理方式都是一样的, 不断的重复相同的模式, 但是每个模块都会维护自己的self-attention层和Feed Forward层的权重值.
  • GPT2的自注意力机制回顾
  • 自然语言的含义是极度依赖上下文的, 比如下面所展示的"机器人第二法则":
  • 机器人必须遵守人类给它的命令, 除非该命令违背了第一法则.
  • 在上述语句中, 有三处单词具有指代含义, 除非我们知道这些词所精确指代的上下文, 否则根本不可能理解这句话的真实语义.
  • 当模型处理这句话的时候, 模型必须知道以下三点:
  • [它]指代机器人.
  • [命令]指代前半句话中人类给机器人下达的命令, 即[人类给它的命令].
  • [第一法则]指代机器人第一法则的完整内容.
  • 这就是自注意力机制所做的工作, 它在处理每个单词之前, 融入了模型对于用来解释某个单词的上下文的相关单词的理解. 具体的做法是: 给序列中的每一个单词都赋予一个相关度得分, 本质上就是注意力权重.
  • 看下图, 举个例子, 最上层的transformer模块在处理单词"it"的时候会关注"a robot", 所以"a", "robot", "it", 这三个单词与其得分相乘加权求和后的特征向量会被送入之后的Feed Forward层.
  • 自注意力机制沿着序列的每一个单词的路径进行处理, 主要由3个向量组成:
  • Query(查询向量), 当前单词的查询向量被用来和其它单词的键向量相乘, 从而得到其它词相对于当前词的注意力得分.
  • Key(键向量), 键向量就像是序列中每个单词的标签, 它使我们搜索相关单词时用来匹配的对象.
  • Value(值向量), 值向量是单词真正的表征, 当我们算出注意力得分后, 使用值向量进行加权求和得到能代表当前位置上下文的向量.
  • 如上图所示, 一个简单的比喻是在档案柜中找文件. 查询向量Query就像一张便利贴, 上面写着你正在研究的课题. 键向量Key像是档案柜中文件夹上贴的标签. 当你找到和便利贴上所写相匹配的文件夹时, 拿出对应的文件夹, 文件夹里的东西便是值向量Value.
  • 将单词的查询向量Query分别乘以每个文件夹的键向量Key,得到各个文件夹对应的注意力得分Score.
  • 我们将每个文件夹的值向量Value乘以其对应的注意力得分Score, 然后求和, 得到最终自注意力层的输出, 如下图所示:
  • 这样将值向量加权混合得到的结果也是一个向量, 它将其50%的注意力放在了单词"robot"上, 30%的注意力放在了"a"上, 还有19%的注意力放在了"it"上.
  • 模型的输出:
  • 当最后一个transformer模块产生输出之后, 模型会将输出张量乘上词嵌入矩阵:
  • 我们知道, 词嵌入矩阵的每一行都对应模型的词汇表中一个单词的嵌入向量. 所以这个乘法操作得到的结果就是词汇表中每个单词对应的注意力得分, 如下图所示:
  • 一般来说, 我们都采用贪心算法, 选取得分最高的单词作为输出结果(top_k = 1).
  • 但是一个更好的策略是对于词汇表中得分较高的一部分单词, 将它们的得分作为概率从整个单词列表中进行抽样(得分越高的单词越容易被选中).
  • 通常会用一个折中的方法, 即选取top_k = 40, 这样模型会考虑注意力得分排名前40的单词.
  • 如上图所示, 模型就完成了一个时间步的迭代, 输出了一个单词. 接下来模型会不断的迭代, 直至生成完整的序列(序列长度达到1024的上限, 或者序列的某一个时间步生成了结束符).

3 小结¶

  • 学习了GPT2的架构:

    • GPT2只采用了Transformer架构中的Decoder模块.
    • GPT2是在GPT基础上发展处的更强大的语言预训练模型.
  • 学习了GPT2的工作细节:

    • GPT2可以处理最长1024个单词的序列.
    • 每个单词都会和它的前序路径一起"流经"所有的解码器模块.
    • GPT2本质上也是自回归模型.
    • 输入张量要经历词嵌入矩阵和位置编码矩阵的加和后, 才能输入进transformer模块中.
  • 学习了GPT2自注意力机制的细节:

    • 首先, GPT2的自注意力是Masked self-attention, 只能看见左侧的序列, 不能看见右侧的信息.
    • Query, Key, Value这三个张量之间的形象化的例子, 生动的说明了各自的作用和运算方式.
    • 最后的输出可以采用多个方法, 贪心方案, 概率分布方案, 或者top-k方案等.

11 BERT GPT ELMo模型的对比

学习目标¶

  • 理解BERT, GPT, ELMo相互间的不同点.
  • 理解BERT, GPT, ELMo相互比较下的各自优点和缺点.

1 BERT, GPT, ELMo之间的不同点¶

  • 关于特征提取器:
    • ELMo采用两部分双层双向LSTM进行特征提取, 然后再进行特征拼接来融合语义信息.
    • GPT和BERT采用Transformer进行特征提取.
    • 很多NLP任务表明Transformer的特征提取能力强于LSTM, 对于ELMo而言, 采用1层静态token embedding + 2层LSTM, 提取特征的能力有限.
  • 单/双向语言模型:
    • 三者之中, 只有GPT采用单向语言模型, 而ELMo和BERT都采用双向语言模型.
    • ELMo虽然被认为采用了双向语言模型, 但实际上是左右两个单向语言模型分别提取特征, 然后进行特征拼接, 这种融合特征的能力比BERT一体化的融合特征方式弱.
    • 三者之中, 只有ELMo没有采用Transformer. GPT和BERT都源于Transformer架构, GPT的单向语言模型采用了经过修改后的Decoder模块, Decoder采用了look-ahead mask, 只能看到context before上文信息, 未来的信息都被mask掉了. 而BERT的双向语言模型采用了Encoder模块, Encoder只采用了padding mask, 可以同时看到context before上文信息, 以及context after下文信息.

2 BERT, GPT, ELMo各自的优点和缺点¶

ELMo: * 优点: * 从早期的Word2Vec预训练模型的最大缺点出发, 进行改进, 这一缺点就是无法解决多义词的问题. * ELMo根据上下文动态调整word embedding, 可以解决多义词的问题. * 缺点: * ELMo使用LSTM提取特征的能力弱于Transformer. * ELMo使用向量拼接的方式融合上下文特征的能力弱于Transformer.

GPT: * 优点: * GPT使用了Transformer提取特征, 使得模型能力大幅提升. * 缺点: * GPT只使用了单向Decoder, 无法融合未来的信息.

BERT: * 优点: * BERT使用了双向Transformer提取特征, 使得模型能力大幅提升. * 添加了两个预训练任务, MLM + NSP的多任务方式进行模型预训练. * 缺点: * 模型过于庞大, 参数量太多, 需要的数据和算力要求过高, 训练好的模型应用场景要求高. * 更适合用于语言嵌入表达, 语言理解方面的任务, 不适合用于生成式的任务.

3 小结¶

学习了BERT, GPT, ELMo之间的区别: * 三者所选取的特征提取器不同. * BERT采用的是Transformer架构中的Encoder模块. * GPT采用的是Transformer架构中的Decoder模块. * ELMo采用的双层双向LSTM模块.

  • 三者所采用的语言模型单/双向不同.
    • BERT采用的是最彻底的双向语言模型, 可以同时关注context before和context after.
    • GPT采用的是单向语言模型, 即Transformer中的Decoder, 由于采用了mask机制, 所以未来信息context after都不可见.
    • ELMo表面上被认为是双向语言模型, 但实际上是左右两个单向LSTM模型分别提取特征, 在进行简单的拼接融合.

第七章 经典序列模型

1 HMM和CRF介绍

学习目标¶

  • 了解HMM与CRF模型的输入和输出.
  • 了解HMM与CRF模型的作用.
  • 了解HMM与CRF模型的使用过程.
  • 了解HMM与CRF模型之间的差异.
  • 了解HMM和CRF的发展现状.

1 HMM模型介绍¶

1.1 HMM模型的输入和输出¶

HMM(Hidden Markov Model), 中文称作隐含马尔科夫模型, 因俄国数学家马尔可夫而得名. 它一般以文本序列数据为输入, 以该序列对应的隐含序列为输出.
什么是隐含序列:

  • 序列数据中每个单元包含的隐性信息, 这些隐性信息之间也存在一定关联.
    举个例子:
给定一段文本: "人生该如何起头"

我们看到的这句话可以叫做: 观测序列

我们可以将这句话以词为单位进行划分得到:

["人生", "该", "如何", "起头"]

那么每个词对应的词性就是它的隐含序列, 如: 

["n", "r", "r", "v"]

1.2 HMM模型的作用¶

  • 在NLP领域, HMM用来解决文本序列标注问题. 如分词, 词性标注, 命名实体识别都可以看作是序列标注问题.

1.3 HMM模型使用过程简述¶

  • 首先, HMM模型表示为: lambda = HMM(A, B, pi), 其中A, B, pi都是模型的参数, 分别称作: 转移概率矩阵, 发射概率矩阵和初始概率矩阵.
  • 接着, 我们开始训练HMM模型, 语料就是事先准备好的一定数量的观测序列及其对应的隐含序列, 通过极大似然估计求得一组参数, 使由观测序列到对应隐含序列的概率最大.
  • 在训练过程中, 为了简化计算, 马尔可夫提出一种假设: 隐含序列中每个单元的可能性只与上一个单元有关. 这个假设就是著名的隐含假设.
  • 训练后, 我们就得到了具备预测能力的新模型: lambda = HMM(A, B, pi), 其中的模型参数已经改变.
  • 之后给定输入序列(x1, x2, ..., xn), 经过模型计算lambda(x1, x2, ..., xn)得到对应隐含序列的条件概率分布.
  • 最后, 使用维特比算法从隐含序列的条件概率分布中找出概率最大的一条序列路径就是我们需要的隐含序列: (y1, y2, ..., yn).

2 CRF模型介绍¶

2.1 CRF模型的输入和输出¶

  • CRF(Conditional Random Fields), 中文称作条件随机场, 同HMM一样, 它一般也以文本序列数据为输入, 以该序列对应的隐含序列为输出.

2.2 CRF模型的作用¶

  • 同HMM一样, 在NLP领域, CRF用来解决文本序列标注问题. 如分词, 词性标注, 命名实体识别.

2.3 CRF模型使用过程简述¶

  • 首先, CRF模型表示为: lambda = CRF(w1, w2, ..., wn), 其中w1到wn是模型参数.
  • 接着, 我们开始训练CRF模型, 语料同样是事先准备好的一定数量的观测序列及其对应的隐含序列.
  • 与此同时我们还需要做人工特征工程, 然后通过不断训练求得一组参数, 使由观测序列到对应隐含序列的概率最大.
  • 训练后, 我们就得到了具备预测能力的新模型: lambda = CRF(w1, w2, ..., wn), 其中的模型参数已经改变.
  • 之后给定输入序列(x1, x2, ..., xn), 经过模型计算lambda(x1, x2, ..., xn)得到对应隐含序列的条件概率分布.
  • 最后, 还是使用维特比算法从隐含序列的条件概率分布中找出概率最大的一条序列路径就是我们需要的隐含序列: (y1, y2, ..., yn).

3 HMM与CRF模型之间差异和现状¶

3.1 HMM与CRF模型之间差异¶

  • HMM模型存在隐马假设, 而CRF不存在, 因此HMM的计算速度要比CRF模型快很多, 适用于对预测性能要求较高的场合.
  • 同样因为隐马假设, 当预测问题中隐含序列单元并不是只与上一个单元有关时, HMM的准确率会大大降低, 而CRF不受这样限制, 准确率明显高于HMM.

3.2 HMM和CRF的发展现状¶

  • HMM和CRF模型曾在多种序列任务中表现出色, 伴随NLP工程师度过漫长的一段时期.
  • 但由于近年来深度学习发展迅速, 经典序列模型, 如HMM和CRF, 已经开始慢慢淡出人们的视野.
  • 因此, 我们这里也是对其做了简洁的总结知识, 让大家对其有一定的基本认识.

4 小结¶

  • 学习了HMM与CRF模型的输入和输出.
  • 学习了HMM与CRF模型的作用.
  • 学习了HMM与CRF模型的使用过程.
  • 学习了HMM与CRF模型之间的差异.
  • 学习了HMM和CRF的发展现状.

posted @ 2025-07-16 19:26  凫弥  阅读(276)  评论(0)    收藏  举报