├── .gitbook.yaml ├── 1.1 深度学习和神经网络 ├── 1.1.1 深度学习的介绍.md ├── 1.1.2 神经网络的介绍.md └── README.md ├── 1.2 pytorch ├── 1.2.1 pytorch的介绍和安装.md ├── 1.2.2 pytorch的入门使用.md ├── 1.2.3 梯度下降和反向传播原理.md ├── 1.2.4 手动完成线性回归 .md ├── 1.2.5调用 pytorch API完成线性回归.md ├── 1.2.6 pytorch中的数据加载.md ├── 1.2.7 使用pytorch实现手写数字识别.md └── README.md ├── 1.3 循环神经网络 ├── 1.3.1 循环神经网络基础.md ├── 1.3.2 文本情感分类.md ├── 1.3.3 循环神经网络.md ├── 1.3.4 循环神经网络实现情感分类.md ├── 1.3.5 神经网络中的序列化容器.md └── README.md ├── 2.1 项目准备 ├── 2.1.1 走进聊天机器人.md ├── 2.1.2 需求分析和流程介绍.md ├── 2.1.3 环境准备.md ├── 2.1.4 语料准备.md ├── 2.1.5 文本分词.md ├── 2.1.6 动手练习.md └── README.md ├── 2.2 fasttext文本分类 ├── 2.2.1 分类的目的和方法.md ├── 2.2.1 分类的目的和方法md ├── 2.2.2 fasttext实现文本分类.md ├── 2.2.3 fasttext的原理剖析.md └── README.md ├── 2.3 Seq2Seq模型和闲聊机器人 ├── 2.3.1 闲聊机器人的介绍.md ├── 2.3.2 seq2seq模型的原理.md ├── 2.3.3 seq2seq实现闲聊机器人.md ├── 2.3.4 Attention的原理和实现.md ├── 2.3.5 BeamSearch的原理和实现.md ├── 2.3.6 闲聊机器人的优化.md └── README.md ├── 2.4 QA机器人 ├── 2.4.1 QA机器人介绍.md ├── 2.4.2 QA机器人的召回.md ├── 2.4.3 召回优化.md ├── 2.4.4 QA机器人排序模型.md ├── 2.4.5 代码的封装和提供接口.md └── README.md ├── README.md ├── book.json ├── images ├── 1.1 │ ├── 全连接层.png │ ├── 区别.png │ ├── 单层神经网络.png │ ├── 多层神经网络.png │ ├── 感知机.png │ ├── 数据量.png │ ├── 激活函数1.png │ ├── 激活函数2.png │ ├── 激活函数3.png │ ├── 激活函数4.png │ ├── 激活函数5.jpg │ ├── 神经元.png │ ├── 神经网络例子1.png │ ├── 神经网络例子2.png │ ├── 神经网络例子3.png │ └── 神经网络例子4.png ├── 1.2 │ ├── MNIST-dataset-5.png │ ├── MNIST-dataset.png │ ├── dataset数据示例.png │ ├── softmax.png │ ├── tensor的数据类型.png │ ├── torch版本.png │ ├── 优化器方法.gif │ ├── 优化器方法.png │ ├── 偏导的计算.png │ ├── 偏导的计算2.png │ ├── 偏导的计算3.png │ ├── 偏导的计算4.png │ ├── 梯度1.png │ ├── 梯度2.png │ ├── 神经网络计算图.png │ ├── 神经网络计算图2.png │ ├── 线性回归1.png │ ├── 线性回归2.png │ ├── 计算图.png │ └── 计算梯度.png ├── 1.3 │ ├── GRU.png │ ├── LSTM-update.png │ ├── LSTM1.jpg │ ├── LSTM2.jpg │ ├── LSTM3.png │ ├── LSTM4.png │ ├── RNN功能.png │ ├── RNN图.png │ ├── RNN展开.png │ ├── bidir_lstm.png │ ├── sigmoid导数.png │ ├── word_embedding.png │ ├── 基础的RNN展开图.png │ ├── 情感分类-data加载1.png │ ├── 情感分类-data加载错误.png │ ├── 易王门.png │ ├── 替换激活函数.png │ ├── 样本 内容.png │ ├── 样本名称.png │ ├── 梯度消失.png │ ├── 输入门.png │ └── 输出门.png ├── 2.1 │ ├── QAbot.png │ ├── app截图.png │ ├── chabot.png │ ├── excel中的问题.png │ ├── 小蜜的交互过程.png │ ├── 小蜜的架构.png │ ├── 小蜜的检索流程.png │ ├── 整体架构.png │ ├── 百度相似问题搜索.png │ ├── 词典.png │ └── 问答对.png ├── 2.2 │ ├── Inked哈夫曼树.jpg │ ├── bow模型.png │ ├── fasttext原理.jpg │ ├── fasttext负采样.png │ ├── 哈夫曼树.jpg │ ├── 哈夫曼树构造过程.png │ ├── 哈夫曼编码 - 副本.jpg │ ├── 哈夫曼编码.jpg │ ├── 数据准备1.png │ ├── 数据准备2.png │ ├── 负采样.png │ └── 负采样梯度上升.png ├── 2.3 │ ├── Attention2.png │ ├── Attention3.png │ ├── Attention4.png │ ├── Attention5.png │ ├── Bahdanau.png │ ├── Encoder.png │ ├── Luong.png │ ├── attention1.png │ ├── attention6.png │ ├── beamsearch.png │ ├── grad_clip.png │ ├── greedy search.png │ ├── scores.png │ ├── seq2seq.png │ ├── seq2seq2.png │ ├── soft-hard attention.jpg │ ├── teacher forcing.jpg │ ├── 小黄鸡语料.png │ ├── 微博语料1.png │ ├── 微博语料2.png │ ├── 文本处理后.png │ └── 文本处理后2.png ├── 2.4 │ ├── AAAI.png │ ├── bm25.png │ ├── bm25_2.png │ ├── deepqa.png │ ├── elu.png │ ├── pooling.png │ ├── pysparnn.png │ └── 对比损失.png └── 补充 │ ├── HMM案例1.png │ ├── 不同对比.png │ ├── 取最大值.png │ ├── 向前算法.png │ ├── 条件随机场.png │ ├── 海藻.png │ ├── 海藻和天气.png │ ├── 状态转移矩阵.png │ ├── 维特比.png │ ├── 自动机.png │ ├── 马尔可夫链-天气 │ └── 马尔可夫链天气.png ├── summary.md └── 补充 ├── HMM.md ├── MEMM和CRF.md └── 最大匹配法.md /.gitbook.yaml: -------------------------------------------------------------------------------- 1 | root: ./ 2 | 3 | structure: 4 | readme: README.md 5 | summary: summary.md 6 | 7 | redirects: 8 | previous/page: summary.md -------------------------------------------------------------------------------- /1.1 深度学习和神经网络/1.1.1 深度学习的介绍.md: -------------------------------------------------------------------------------- 1 | # 1深度学习的介绍 2 | 3 | ## 目标 4 | 5 | 1. 知道什么是深度学习 6 | 2. 知道深度学习和机器学习的区别 7 | 3. 能够说出深度学习的主要应用场景 8 | 4. 知道深度学习的常见框架 9 | 10 | ## 1. 深度学习的概念 11 | 12 | **深度学习**(英语:deep learning)是机器学习的分支,是一种以人工神经网络为架构,对数据进行特征学习的算法。 13 | 14 | 15 | 16 | ## 2. 机器学习和深度学习的区别 17 | 18 | ### 2.1 区别1 :特征提取 19 | 20 | ![](../images/1.1/区别.png) 21 | 22 | 从**特征提取**的角度出发: 23 | 24 | 1. 机器学习需要有人工的特征提取的过程 25 | 2. 深度学习没有复杂的人工特征提取的过程,特征提取的过程可以通过深度神经网络自动完成 26 | 27 | 28 | 29 | ### 2.2 区别2:数据量 30 | 31 | ![](../images/1.1/数据量.png) 32 | 33 | 从**数据量**的角度出发: 34 | 35 | 1. 深度学习需要大量的训练数据集,会有更高的效果 36 | 2. 深度学习训练深度神经网络需要大量的算力,因为其中有更多的参数 37 | 38 | 39 | 40 | ## 3. 深度学习的应用场景 41 | 42 | 1. 图像识别 43 | 44 | 1. 物体识别 45 | 2. 场景识别 46 | 3. 人脸检测跟踪 47 | 4. 人脸身份认证 48 | 49 | 2. 自然语言处理技术 50 | 51 | 1. 机器翻译 52 | 2. 文本识别 53 | 3. 聊天对话 54 | 55 | 3. 语音技术 56 | 57 | 1. 语音识别 58 | 59 | ## 4. 常见的深度学习框架 60 | 61 | 目前企业中常见的深度学习框架有很多,TensorFlow, Caffe2, Keras, Theano, PyTorch, Chainer, DyNet, MXNet, and CNTK等等 62 | 63 | 其中tensorflow和Kears是google出品的,使用者很多,但是其语法晦涩而且和python的语法不尽相同,对于入门玩家而言上手难度较高。 64 | 65 | 所以在之后的课程中我们会使用facebook出的PyTorch,PyTorch的使用和python的语法相同,整个操作类似Numpy的操作,并且 PyTorch使用的是动态计算,会让代码的调试变的更加简单 66 | 67 | -------------------------------------------------------------------------------- /1.1 深度学习和神经网络/1.1.2 神经网络的介绍.md: -------------------------------------------------------------------------------- 1 | # 神经网络的介绍 2 | 3 | ## 目标 4 | 5 | 1. 知道神经网络的概念 6 | 2. 知道什么是神经元 7 | 3. 知道什么是单层神经网络 8 | 4. 知道什么是感知机 9 | 5. 知道什么是多层神经网络 10 | 6. 知道激活函数是什么,有什么作用 11 | 7. 理解神经网络的思想 12 | 13 | ## 1. 人工神经网络的概念 14 | 15 | **人工神经网络**(英语:Artificial Neural Network,ANN),简称**神经网络**(Neural Network,NN)或**类神经网络**,是一种模仿生物神经网络(动物的中枢神经系统,特别是大脑)的结构和功能的数学模型,用于对函数进行估计或近似。 16 | 17 | 和其他机器学习方法一样,神经网络已经被用于解决各种各样的问题,例如机器视觉和语音识别。这些问题都是很难被传统基于规则的编程所解决的。 18 | 19 | 20 | 21 | ## 2. 神经元的概念 22 | 23 | 在生物神经网络中,每个神经元与其他神经元相连,当它“兴奋”时,就会向相连的神经元发送化学物质,从而改变这些神经元内的电位;如果某神经元的电位超过了一个“阈值”,那么它就会被激活,即“兴奋”起来,向其他神经元发送化学物质。 24 | 25 | 1943 年,McCulloch 和 Pitts 将上述情形抽象为上图所示的简单模型,这就是一直沿用至今的 **M-P 神经元模型**。把许多这样的神经元按一定的层次结构连接起来,就得到了神经网络。 26 | 27 | 一个简单的神经元如下图所示, 28 | 29 | ![](../images/1.1/神经元.png) 30 | 31 | 其中: 32 | 33 | 1. $a_1,a_2\dots a_n​$ 为各个输入的分量 34 | 2. $w_1,w_2 \cdots w_n$ 为各个输入分量对应的权重参数 35 | 3. $b$ 为偏置 36 | 4. $f$ 为**激活函数**,常见的激活函数有tanh,sigmoid,relu 37 | 5. $t$ 为神经元的输出 38 | 39 | 使用数学公式表示就是: 40 | $$ 41 | t = f(W^TA+b) 42 | $$ 43 | 可见,**一个神经元的功能是求得输入向量与权向量的内积后,经一个非线性传递函数得到一个标量结果**。 44 | 45 | 46 | 47 | ## 3. 单层神经网络 48 | 49 | 是最基本的神经元网络形式,由有限个神经元构成,所有神经元的输入向量都是同一个向量。由于每一个神经元都会产生一个标量结果,所以单层神经元的输出是一个向量,向量的维数等于神经元的数目。 50 | 51 | 示意图如下: 52 | 53 | ![](../images/1.1/单层神经网络.png) 54 | 55 | ## 4. 感知机 56 | 57 | 感知机由**两层神经网**络组成,**输入层**接收外界输入信号后传递给**输出层(输出+1正例,-1反例)**,输出层是 M-P 神经元 58 | 59 | ![](../images/1.1/感知机.png) 60 | 61 | 其中从$w_0,w_1\cdots w_n​$都表示权重 62 | 63 | **感知机的作用:** 64 | 65 | 把一个n维向量空间用一个超平面分割成两部分,给定一个输入向量,超平面可以判断出这个向量位于超平面的哪一边,得到输入时正类或者是反类,**对应到2维空间就是一条直线把一个平面分为两个部分**。 66 | 67 | ## 5. 多层神经网络 68 | 69 | 多层神经网络就是由单层神经网络进行叠加之后得到的,所以就形成了**层**的概念,常见的多层神经网络有如下结构: 70 | 71 | - 输入层(Input layer),众多神经元(Neuron)接受大量输入消息。输入的消息称为输入向量。 72 | - 输出层(Output layer),消息在神经元链接中传输、分析、权衡,形成输出结果。输出的消息称为输出向量。 73 | - 隐藏层(Hidden layer),简称“隐层”,是输入层和输出层之间众多神经元和链接组成的各个层面。隐层可以有一层或多层。隐层的节点(神经元)数目不定,但数目越多神经网络的非线性越显著,从而神经网络的强健性(robustness)更显著。 74 | 75 | 示意图如下: 76 | 77 | ![](../images/1.1/多层神经网络.png) 78 | 79 | **概念:全连接层** 80 | 81 | 全连接层:当前一层和前一层每个神经元相互链接,我们称当前这一层为全连接层。 82 | 83 | 思考:假设第N-1层有m个神经元,第N层有n个神经元,当第N层是全连接层的时候,则N-1和N层之间有1,这些参数可以如何表示? 84 | 85 | ![](../images/1.1/全连接层.png) 86 | 87 | 从上图可以看出,所谓的全连接层就是在前一层的输出的基础上进行一次$Y=Wx+b​$的变化(不考虑激活函数的情况下就是一次线性变化,所谓线性变化就是平移(+b)和缩放的组合(*w)) 88 | 89 | ## 6. 激活函数 90 | 91 | 在前面的神经元的介绍过程中我们提到了激活函数,那么他到底是干什么的呢? 92 | 93 | 假设我们有这样一组数据,三角形和四边形,需要把他们分为两类 94 | 95 | ![](../images/1.1/激活函数1.png) 96 | 97 | 通过不带激活函数的感知机模型我们可以划出一条线, 把平面分割开 98 | 99 | ![](../images/1.1/激活函数2.png) 100 | 101 | 假设我们确定了参数w和b之后,那么带入需要预测的数据,如果y>0,我们认为这个点在直线的右边,也就是正类(三角形),否则是在左边(四边形) 102 | 103 | 但是可以看出,三角形和四边形是没有办法通过直线分开的,那么这个时候该怎么办? 104 | 105 | 可以考虑使用多层神经网络来进行尝试,比如**在前面的感知机模型中再增加一层** 106 | 107 | ![](../images/1.1/激活函数3.png) 108 | 109 | 110 | 111 | 对上图中的等式进行合并,我们可以得到: 112 | $$ 113 | y = (w_{1-11}w_{2-1}+\cdots)x_1+(w_{1-21}w_{2-1}+\cdots)x_2 + (w_{2-1}+\cdots)b_{1-1} 114 | $$ 115 | 上式括号中的都为w参数,和公式$y = w_1x_1 + w_2x_2 +b$完全相同,依然只能够绘制出直线 116 | 117 | 所以可以发现,即使是多层神经网络,相比于前面的感知机,没有任何的改进。 118 | 119 | 但是如果此时,我们在前面感知机的基础上加上**非线性的激活函数**之后,输出的结果就不在是一条直线 120 | 121 | ![](../images/1.1/激活函数4.png) 122 | 123 | 如上图,右边是sigmoid函数,对感知机的结果,通过sigmoid函数进行处理 124 | 125 | 如果给定合适的参数w和b,就可以得到合适的曲线,能够完成对最开始问题的非线性分割 126 | 127 | 所以激活函数很重要的一个**作用**就是**增加模型的非线性分割能力** 128 | 129 | 常见的激活函数有: 130 | 131 | ![](../images/1.1/激活函数5.jpg) 132 | 133 | 看图可知: 134 | 135 | - sigmoid 只会输出正数,以及靠近0的输出变化率最大 136 | - tanh和sigmoid不同的是,tanh输出可以是负数 137 | - Relu是输入只能大于0,如果你输入含有负数,Relu就不适合,如果你的输入是图片格式,Relu就挺常用的,因为图片的像素值作为输入时取值为[0,255]。 138 | 139 | 激活函数的作用除了前面说的**增加模型的非线性分割能力**外,还有 140 | 141 | - **提高模型鲁棒性** 142 | - **缓解梯度消失问题** 143 | - **加速模型收敛等** 144 | 145 | 这些好处,大家后续会慢慢体会到,这里先知道就行 146 | 147 | 148 | 149 | ## 6. 神经网络示例 150 | 151 | 一个男孩想要找一个女朋友,于是实现了一个**女友判定机**,随着年龄的增长,他的判定机也一直在变化 152 | 153 | 14岁的时候: 154 | 155 | ![](../images/1.1/神经网络例子1.png) 156 | 157 | 无数次碰壁之后,男孩意识到追到女孩的可能性和颜值一样重要,于是修改了判定机: 158 | 159 | ![](../images/1.1/神经网络例子2.png) 160 | 161 | 在15岁的时候终于找到呢女朋友,但是一顿时间后他发现有各种难以忍受的习惯,最终决定分手。一段空窗期中,他发现找女朋友很复杂,需要更多的条件才能够帮助他找到女朋友,于是在25岁的时候,他再次修改了判定机: 162 | 163 | ![](../images/1.1/神经网络例子3.png) 164 | 165 | 在更新了女友判定机之后,问题又来了,很多指标不能够很好的量化,如何颜值,什么样的叫做颜值高,什么样的叫做性格好等等,为了解决这个问题,他又更新了判定机,最终得到**超级女友判定机** 166 | 167 | ![](../images/1.1/神经网络例子4.png) 168 | 169 | 170 | 171 | 上述的超级女友判定机其实就是神经网络,它能够接受基础的输入,通过隐藏层的线性的和非线性的变化最终的到输出 172 | 173 | 通过上面例子,希望大家能够理解深度学习的**思想**: 174 | 175 | 输出的最原始、最基本的数据,通过模型来进行特征工程,进行更加高级特征的学习,然后通过传入的数据来确定合适的参数,让模型去更好的拟合数据。 176 | 177 | 这个过程可以理解为盲人摸象,多个人一起摸,把摸到的结果乘上合适的权重,进行合适的变化,让他和目标值趋近一致。整个过程只需要输入基础的数据,程序自动寻找合适的参数。 -------------------------------------------------------------------------------- /1.1 深度学习和神经网络/README.md: -------------------------------------------------------------------------------- 1 | # 1.1 深度学习和神经网络 -------------------------------------------------------------------------------- /1.2 pytorch/1.2.1 pytorch的介绍和安装.md: -------------------------------------------------------------------------------- 1 | # Pytorch的安装 2 | 3 | ## 目标 4 | 5 | 1. 知道如何安装pytorch 6 | 7 | 8 | 9 | ## 1. Pytorch的介绍 10 | 11 | Pytorch是一款facebook发布的深度学习框架,由其易用性,友好性,深受广大用户青睐。 12 | 13 | ## 2. Pytorch的版本 14 | 15 | ![](..\images\1.2\torch版本.png) 16 | 17 | ## 3. Pytorch的安装 18 | 19 | 安装地址介绍:https://pytorch.org/get-started/locally/ 20 | 21 | 带GPU安装步骤: 22 | 23 | `conda install pytorch torchvision cudatoolkit=9.0 -c pytorch` 24 | 25 | 不带GPU安装步骤 26 | 27 | `conda install pytorch-cpu torchvision-cpu -c pytorch` 28 | 29 | 安装之后打开ipython 30 | 31 | 输入: 32 | 33 | ```python 34 | In [1]:import torch 35 | In [2]: torch.__version__ 36 | Out[2]: '1.0.1' 37 | ``` 38 | 39 | 注意:安装模块的时候安装的是`pytorch` ,但是在代码中都是使用`torch` 40 | 41 | -------------------------------------------------------------------------------- /1.2 pytorch/1.2.2 pytorch的入门使用.md: -------------------------------------------------------------------------------- 1 | # Pytorch的入门使用 2 | 3 | ## 目标 4 | 5 | 1. 知道张量和Pytorch中的张量 6 | 2. 知道pytorch中如何创建张量 7 | 3. 知道pytorch中tensor的常见方法 8 | 4. 知道pytorch中tensor的数据类型 9 | 5. 知道pytorch中如何实现tensor在cpu和cuda中转化 10 | 11 | 12 | 13 | ## 1. 张量Tensor 14 | 15 | 张量是一个统称,其中包含很多类型: 16 | 17 | 1. 0阶张量:标量、常数,0-D Tensor 18 | 2. 1阶张量:向量,1-D Tensor 19 | 3. 2阶张量:矩阵,2-D Tensor 20 | 4. 3阶张量 21 | 5. ... 22 | 6. N阶张量 23 | 24 | 25 | ## 2. Pytorch中创建张量 26 | 27 | 1. 使用python中的列表或者序列创建tensor 28 | 29 | ```python 30 | torch.tensor([[1., -1.], [1., -1.]]) 31 | tensor([[ 1.0000, -1.0000], 32 | [ 1.0000, -1.0000]]) 33 | ``` 34 | 35 | 2. 使用numpy中的数组创建tensor 36 | 37 | ```python 38 | torch.tensor(np.array([[1, 2, 3], [4, 5, 6]])) 39 | tensor([[ 1, 2, 3], 40 | [ 4, 5, 6]]) 41 | ``` 42 | 43 | 3. 使用torch的api创建tensor 44 | 45 | 1. `torch.empty(3,4)`创建3行4列的空的tensor,会用无用数据进行填充 46 | 47 | 2. `torch.ones([3,4])` 创建3行4列的**全为1**的tensor 48 | 49 | 3. `torch.zeros([3,4])`创建3行4列的**全为0**的tensor 50 | 51 | 4. `torch.rand([3,4])` 创建3行4列的**随机值**的tensor,随机值的区间是`[0, 1)` 52 | 53 | ```python 54 | >>> torch.rand(2, 3) 55 | tensor([[ 0.8237, 0.5781, 0.6879], 56 | [ 0.3816, 0.7249, 0.0998]]) 57 | ``` 58 | 59 | 5. `torch.randint(low=0,high=10,size=[3,4])` 创建3行4列的**随机整数**的tensor,随机值的区间是`[low, high)` 60 | 61 | ```python 62 | >>> torch.randint(3, 10, (2, 2)) 63 | tensor([[4, 5], 64 | [6, 7]]) 65 | ``` 66 | 67 | 6. `torch.randn([3,4])` 创建3行4列的**随机数**的tensor,随机值的分布式均值为0,方差为1 68 | 69 | ## 3. Pytorch中tensor的常用方法 70 | 71 | 1. 获取tensor中的数据(当tensor中只有一个元素可用):`tensor.item()` 72 | 73 | ```python 74 | In [10]: a = torch.tensor(np.arange(1)) 75 | 76 | In [11]: a 77 | Out[11]: tensor([0]) 78 | 79 | In [12]: a.item() 80 | Out[12]: 0 81 | ``` 82 | 83 | 2. 转化为numpy数组 84 | 85 | ```python 86 | In [55]: z.numpy() 87 | Out[55]: 88 | array([[-2.5871205], 89 | [ 7.3690367], 90 | [-2.4918075]], dtype=float32) 91 | ``` 92 | 93 | 94 | 95 | 3. 获取形状:`tensor.size()` 96 | 97 | ```python 98 | In [72]: x 99 | Out[72]: 100 | tensor([[ 1, 2], 101 | [ 3, 4], 102 | [ 5, 10]], dtype=torch.int32) 103 | 104 | In [73]: x.size() 105 | Out[73]: torch.Size([3, 2]) 106 | ``` 107 | 108 | 109 | 110 | 4. 形状改变:`tensor.view((3,4))`。类似numpy中的reshape,是一种浅拷贝,仅仅是形状发生改变 111 | 112 | ```python 113 | In [76]: x.view(2,3) 114 | Out[76]: 115 | tensor([[ 1, 2, 3], 116 | [ 4, 5, 10]], dtype=torch.int32) 117 | ``` 118 | 119 | 120 | 121 | 5. 获取阶数:`tensor.dim()` 122 | 123 | ```python 124 | In [77]: x.dim() 125 | Out[77]: 2 126 | ``` 127 | 128 | 129 | 130 | 6. 获取最大值:`tensor.max()` 131 | 132 | ```python 133 | In [78]: x.max() 134 | Out[78]: tensor(10, dtype=torch.int32) 135 | ``` 136 | 137 | 138 | 139 | 7. 转置:`tensor.t()` 140 | 141 | ```python 142 | In [79]: x.t() 143 | Out[79]: 144 | tensor([[ 1, 3, 5], 145 | [ 2, 4, 10]], dtype=torch.int32) 146 | ``` 147 | 148 | 8. `tensor[1,3]` 获取tensor中第一行第三列的值 149 | 150 | 9. `tensor[1,3]=100` 对tensor中第一行第三列的位置进行赋值100 151 | 152 | 10. tensor的切片 153 | 154 | ```python 155 | In [101]: x 156 | Out[101]: 157 | tensor([[1.6437, 1.9439, 1.5393], 158 | [1.3491, 1.9575, 1.0552], 159 | [1.5106, 1.0123, 1.0961], 160 | [1.4382, 1.5939, 1.5012], 161 | [1.5267, 1.4858, 1.4007]]) 162 | 163 | In [102]: x[:,1] 164 | Out[102]: tensor([1.9439, 1.9575, 1.0123, 1.5939, 1.4858]) 165 | ``` 166 | 167 | ​ 168 | 169 | ## 4. tensor的数据类型 170 | 171 | tensor中的数据类型非常多,常见类型如下: 172 | 173 | ![](../images/1.2/tensor的数据类型.png) 174 | 175 | 上图中的Tensor types表示这种type的tensor是其实例 176 | 177 | 178 | 179 | 1. 获取tensor的数据类型:`tensor.dtype` 180 | 181 | ```python 182 | In [80]: x.dtype 183 | Out[80]: torch.int32 184 | ``` 185 | 186 | 2. 创建数据的时候指定类型 187 | 188 | ```python 189 | In [88]: torch.ones([2,3],dtype=torch.float32) 190 | Out[88]: 191 | tensor([[9.1167e+18, 0.0000e+00, 7.8796e+15], 192 | [8.3097e-43, 0.0000e+00, -0.0000e+00]]) 193 | ``` 194 | 195 | 196 | 197 | 3. 类型的修改 198 | 199 | ```python 200 | In [17]: a 201 | Out[17]: tensor([1, 2], dtype=torch.int32) 202 | 203 | In [18]: a.type(torch.float) 204 | Out[18]: tensor([1., 2.]) 205 | 206 | In [19]: a.double() 207 | Out[19]: tensor([1., 2.], dtype=torch.float64) 208 | ``` 209 | 210 | 211 | 212 | 213 | 214 | ## 5. tensor的其他操作 215 | 216 | 1. tensor和tensor相加 217 | 218 | ```python 219 | In [94]: x = x.new_ones(5, 3, dtype=torch.float) 220 | 221 | In [95]: y = torch.rand(5, 3) 222 | 223 | In [96]: x+y 224 | Out[96]: 225 | tensor([[1.6437, 1.9439, 1.5393], 226 | [1.3491, 1.9575, 1.0552], 227 | [1.5106, 1.0123, 1.0961], 228 | [1.4382, 1.5939, 1.5012], 229 | [1.5267, 1.4858, 1.4007]]) 230 | In [98]: torch.add(x,y) 231 | Out[98]: 232 | tensor([[1.6437, 1.9439, 1.5393], 233 | [1.3491, 1.9575, 1.0552], 234 | [1.5106, 1.0123, 1.0961], 235 | [1.4382, 1.5939, 1.5012], 236 | [1.5267, 1.4858, 1.4007]]) 237 | In [99]: x.add(y) 238 | Out[99]: 239 | tensor([[1.6437, 1.9439, 1.5393], 240 | [1.3491, 1.9575, 1.0552], 241 | [1.5106, 1.0123, 1.0961], 242 | [1.4382, 1.5939, 1.5012], 243 | [1.5267, 1.4858, 1.4007]]) 244 | In [100]: x.add_(y) #带下划线的方法会对x进行就地修改 245 | Out[100]: 246 | tensor([[1.6437, 1.9439, 1.5393], 247 | [1.3491, 1.9575, 1.0552], 248 | [1.5106, 1.0123, 1.0961], 249 | [1.4382, 1.5939, 1.5012], 250 | [1.5267, 1.4858, 1.4007]]) 251 | 252 | In [101]: x #x发生改变 253 | Out[101]: 254 | tensor([[1.6437, 1.9439, 1.5393], 255 | [1.3491, 1.9575, 1.0552], 256 | [1.5106, 1.0123, 1.0961], 257 | [1.4382, 1.5939, 1.5012], 258 | [1.5267, 1.4858, 1.4007]]) 259 | ``` 260 | 261 | 注意:带下划线的方法(比如:`add_`)会对tensor进行就地修改 262 | 263 | 2. tensor和数字操作 264 | 265 | ```python 266 | In [97]: x +10 267 | Out[97]: 268 | tensor([[11., 11., 11.], 269 | [11., 11., 11.], 270 | [11., 11., 11.], 271 | [11., 11., 11.], 272 | [11., 11., 11.]]) 273 | ``` 274 | 275 | 3. CUDA中的tensor 276 | 277 | CUDA(Compute Unified Device Architecture),是NVIDIA推出的运算平台。 CUDA™是一种由NVIDIA推出的通用并行计算架构,该架构使GPU能够解决复杂的计算问题。 278 | 279 | `torch.cuda`这个模块增加了对CUDA tensor的支持,能够在cpu和gpu上使用相同的方法操作tensor 280 | 281 | 通过`.to`方法能够把一个tensor转移到另外一个设备(比如从CPU转到GPU) 282 | 283 | ```python 284 | #device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 285 | if torch.cuda.is_available(): 286 | device = torch.device("cuda") # cuda device对象 287 | y = torch.ones_like(x, device=device) # 创建一个在cuda上的tensor 288 | x = x.to(device) # 使用方法把x转为cuda 的tensor 289 | z = x + y 290 | print(z) 291 | print(z.to("cpu", torch.double)) # .to方法也能够同时设置类型 292 | 293 | >>tensor([1.9806], device='cuda:0') 294 | >>tensor([1.9806], dtype=torch.float64) 295 | ``` 296 | 297 | 298 | 299 | 通过前面的学习,可以发现torch的各种操作几乎和numpy一样 -------------------------------------------------------------------------------- /1.2 pytorch/1.2.3 梯度下降和反向传播原理.md: -------------------------------------------------------------------------------- 1 | # 梯度下降和反向传播 2 | 3 | ## 目标 4 | 5 | 1. 知道什么是梯度下降 6 | 2. 知道什么是反向传播 7 | 8 | 9 | 10 | ## 1. 梯度是什么? 11 | 12 | 梯度:是一个向量,导数+变化最快的方向(学习的前进方向) 13 | 14 | 回顾机器学习 15 | 16 | 收集数据$x$ ,构建机器学习模型$f$,得到$f(x,w) = Y_{predict}​$ 17 | 18 | 判断模型好坏的方法: 19 | $$ 20 | \begin{align*} 21 | loss & = (Y_{predict}-Y_{true})^2 &(回归损失) \\ 22 | loss & = Y_{true} \cdot log(Y_{predict}) &(分类损失) 23 | \end{align*} 24 | $$ 25 | 26 | 27 | 28 | 目标:通过调整(学习)参数$w$,尽可能的降低$loss$,那么我们该如何调整$w$呢? 29 | 30 | ![](../images/1.2/梯度1.png) 31 | 32 | 随机选择一个起始点$w_0$,通过调整$w_0$,让loss函数取到最小值 33 | 34 | ![](../images/1.2/梯度2.png) 35 | 36 | **$w​$的更新方法**: 37 | 38 | 1. 计算$w$的梯度(导数) 39 | 40 | $$ 41 | \begin{align*} 42 | \nabla w = \frac{f(w+0.000001)-f(w-0.000001)}{2*0.000001} 43 | 44 | \end{align*} 45 | $$ 46 | 47 | 2. 更新$w$ 48 | $$ 49 | w = w - \alpha \nabla w 50 | $$ 51 | 52 | 53 | 其中: 54 | 55 | 1. $\nabla w <0 ​$ ,意味着w将增大 56 | 2. $\nabla w >0 $ ,意味着w将减小 57 | 58 | 总结:梯度就是多元函数参数的变化趋势(参数学习的方向),只有一个自变量时称为**导数** 59 | 60 | ## 2. 偏导的计算 61 | 62 | ### 2.1 常见的导数计算 63 | 64 | - 多项式求导数:$f(x) = x^5​$ ,$f^{'}(x) = 5x^{(5-1)}​$ 65 | 66 | - 基本运算求导:$f(x) = xy​$ ,$f^{'}(x) = y​$ 67 | 68 | - 指数求导:$f(x) = 5e^x​$ ,$f^{'}(x) = 5e^x​$ 69 | 70 | - 对数求导:$f(x) = 5lnx​$ ,$f^{'}(x) = \frac{5}{x}​$,ln 表示log以e为底的对数 71 | 72 | - 导数的微分形式: 73 | $$ 74 | \begin{align*} 75 | & f^{'}(x) = & \frac{d f(x)}{dx} \\ 76 | & 牛顿 &莱布尼兹 77 | \end{align*} 78 | $$ 79 | 80 | 81 | 那么:如何求$f(x) = (1+e^{-x})^{-1}$ 的导数呢?那就可以使用 82 | 83 | 84 | 85 | $f(x) = (1+e^{-x})^{-1}​$ ==> $f(a) = a^{-1},a(b) = (1+b),b(c) = e^c,c(x) = -x​$ 86 | 87 | 则有: 88 | $$ 89 | \begin{align*} 90 | \frac{d f(x)}{dx} & = \frac{df}{da} \times \frac{da}{db} \times \frac{db}{dc}\times \frac{dc}{dx} \\ 91 | &=-a^{-2} \times 1\times e^c \times (-1) \\ 92 | &= -(1+e^{-x})^{-2} \times e^{-x} \times (-1) \\ 93 | &= e^{-x}(1+e^{-x})^{-2} 94 | \end{align*} 95 | $$ 96 | 97 | 98 | 99 | 100 | ### 2.2 多元函数求偏导 101 | 102 | 一元函数,即有一个自变量。类似$f(x)$ 103 | 104 | 多元函数,即有多个自变量。类似$f(x,y,z),三个自变量x,y,z$ 105 | 106 | 多元函数求偏导过程中:**对某一个自变量求导,其他自变量当做常量即可** 107 | 108 | 例1: 109 | $$ 110 | \begin{align*} 111 | &f(x,y,z) &= &ax+by+cz \\ 112 | &\frac{df(x,y,z)}{dx} &= &a \\ 113 | &\frac{df(x,y,z)}{dy} &= &b \\ 114 | &\frac{df(x,y,z)}{dz} &= &c 115 | \end{align*} 116 | $$ 117 | 118 | 119 | 例2: 120 | $$ 121 | \begin{align*} 122 | &f(x,y) &= &xy \\ 123 | &\frac{df(x,y)}{dx} &= & y\\ 124 | &\frac{df(x,y)}{dy} &= &x 125 | \end{align*} 126 | $$ 127 | 例3: 128 | $$ 129 | \begin{align*} 130 | &f(x,w) &= &(y-xw)^2 \\ 131 | &\frac{df(x,w)}{dx} &= & -2w(y-xw)\\ 132 | &\frac{df(x,w)}{dw} &= & -2x(y-xw) 133 | \end{align*} 134 | $$ 135 | **练习:** 136 | 137 | 已知$J(a,b,c) = 3(a+bc),令u=a+v,v = bc​$,求a,b,c各自的偏导数。 138 | $$ 139 | \begin{align*} 140 | 令:& J(a,b,c) = 3u\\ 141 | \frac{dJ}{da} &=\frac{dJ}{du} \times \frac{du}{da} = 3\times1 \\ 142 | \frac{dJ}{db} &=\frac{dJ}{du} \times \frac{du}{dv} \times \frac{dv}{db} = 3\times1\times c \\ 143 | \frac{dJ}{dc} &=\frac{dJ}{du} \times \frac{du}{dv} \times \frac{dv}{dc} = 3\times1\times b \\ 144 | \end{align*} 145 | $$ 146 | 147 | 148 | 149 | 150 | 151 | 152 | ## 3. 反向传播算法 153 | 154 | ### 3.1 计算图和反向传播 155 | 156 | 计算图:通过图的方式来描述函数的图形 157 | 158 | 在上面的练习中,$J(a,b,c) = 3(a+bc),令u=a+v,v = bc$,把它绘制成计算图可以表示为: 159 | 160 | ![](../images/1.2/计算图.png) 161 | 162 | 绘制成为计算图之后,可以清楚的看到向前计算的过程 163 | 164 | 之后,对每个节点求偏导可有: 165 | 166 | ![](../images/1.2/计算梯度.png) 167 | 168 | 那么反向传播的过程就是一个上图的从右往左的过程,自变量$a,b,c$各自的偏导就是连线上的梯度的乘积: 169 | $$ 170 | \begin{align*} 171 | \frac{dJ}{da} &= 3 \times 1 \\ 172 | \frac{dJ}{db} &= 3 \times 1 \times c \\ 173 | \frac{dJ}{dc} &= 3 \times 1 \times b 174 | \end{align*} 175 | $$ 176 | 177 | ### 3.2 神经网络中的反向传播 178 | 179 | 180 | 181 | #### 3.2.1 神经网络的示意图 182 | 183 | $w1,w2,....wn​$表示网络第n层权重 184 | 185 | $w_n[i,j]$表示第n层第i个神经元,连接到第n+1层第j个神经元的权重。 186 | 187 | ![](../images/1.2/神经网络计算图.png) 188 | 189 | #### 3.2.2 神经网络的计算图 190 | 191 | ![](../images/1.2/神经网络计算图2.png) 192 | 193 | 其中: 194 | 195 | 1. $\nabla out​$是根据损失函数对预测值进行求导得到的结果 196 | 2. f函数可以理解为激活函数 197 | 198 | 199 | 200 | 201 | 202 | **问题:**那么此时$w_1[1,2]$的偏导该如何求解呢? 203 | 204 | 通过观察,发现从$out$ 到$w_1[1,2]$的来连接线有两条 205 | 206 | ![](../images/1.2/偏导的计算.png) 207 | 208 | 结果如下: 209 | $$ 210 | \frac{dout}{dW_1[1,2]} = x1*f^{'}(a2)*(W_2[2,1]*f^{'}(b1)*W_3[1,1]*\nabla out +W_2[2,2]*f^{'}(b2)*W_3[2,1]*\nabla out) 211 | $$ 212 | 公式分为两部分: 213 | 214 | 1. 括号外:左边红线部分 215 | 2. 括号内 216 | 1. 加号左边:右边红线部分 217 | 2. 加号右边:蓝线部分 218 | 219 | 但是这样做,当模型很大的时候,计算量非常大 220 | 221 | 所以反向传播的思想就是对其中的某一个参数单独求梯度,之后更新,如下图所示: 222 | 223 | ![](../images/1.2/偏导的计算2.png) 224 | 225 | 226 | 227 | 计算过程如下 228 | $$ 229 | \begin{align*} 230 | &\nabla W_3[1,1] = f(b_1)*\nabla out & (计算W_3[1,1]梯度)\\ 231 | &\nabla W_3[2,1] = f(b_2)*\nabla out & (计算W_3[2,1]梯度)\\ 232 | \\ 233 | &\nabla b_1= f^{'}(b_1)*W_3[1,1]*\nabla out & (计算W_3[2,1]梯度)\\ 234 | &\nabla b_2= f^{'}(b_2)*W_3[2,1]*\nabla out & (计算W_3[2,1]梯度)\\ 235 | 236 | \end{align*} 237 | $$ 238 | 更新参数之后,继续反向传播 239 | 240 | ![](../images/1.2/偏导的计算3.png) 241 | 242 | 计算过程如下: 243 | $$ 244 | \begin{align*} 245 | &\nabla W_2[1,2] = f(a_1)* \nabla b_2 \\ 246 | &\nabla a_2 = f^{'}(a_2)*(w_2[2,1]\nabla b_1 +W_2[2,2] \nabla b_2) 247 | \end{align*} 248 | $$ 249 | 继续反向传播 250 | 251 | ![](../images/1.2/偏导的计算4.png) 252 | 253 | 计算过程如下: 254 | $$ 255 | \begin{align*} 256 | &▽W_1[1,2]= x_1*▽a_2\\ 257 | &▽x_1= (W_1[1,1]*▽a_1+w_1[1,2]*▽a_2)*x_1’ 258 | \end{align*} 259 | $$ 260 | 261 | **通用的描述如下** 262 | $$ 263 | \nabla w^{l}_{i,j} = f(a^l_i)* \nabla a^{i+1}_{j}\\ 264 | \nabla a^{l}_i = f'(a^l_i)*(\sum_{j=1}^{m}w_{i,j}*\nabla a_j^{l+1}) 265 | $$ 266 | -------------------------------------------------------------------------------- /1.2 pytorch/1.2.4 手动完成线性回归 .md: -------------------------------------------------------------------------------- 1 | # Pytorch完成线性回归 2 | 3 | ## 目标 4 | 5 | 1. 知道`requires_grad`的作用 6 | 2. 知道如何使用`backward` 7 | 3. 知道如何手动完成线性回归 8 | 9 | 10 | 11 | 12 | 13 | ## 1. 向前计算 14 | 15 | 对于pytorch中的一个tensor,如果设置它的属性 `.requires_grad`为`True`,那么它将会追踪对于该张量的所有操作。或者可以理解为,这个tensor是一个参数,后续会被计算梯度,更新该参数。 16 | 17 | 18 | 19 | ### 1.1 计算过程 20 | 21 | 假设有以下条件(1/4表示求均值,xi中有4个数),使用torch完成其向前计算的过程 22 | $$ 23 | \begin{align*} 24 | &o = \frac{1}{4}\sum_iz_i \\ 25 | &z_i = 3(x_i+2)^2\\ 26 | 其中:&\\ 27 | &z_i|_{x_i=1}=27\\ 28 | \end{align*} 29 | $$ 30 | 如果x为参数,需要对其进行梯度的计算和更新 31 | 32 | 那么,在最开始随机设置x的值的过程中,需要设置他的requires_grad属性为True,其**默认值为False** 33 | 34 | ```python 35 | import torch 36 | x = torch.ones(2, 2, requires_grad=True) #初始化参数x并设置requires_grad=True用来追踪其计算历史 37 | print(x) 38 | #tensor([[1., 1.], 39 | # [1., 1.]], requires_grad=True) 40 | 41 | y = x+2 42 | print(y) 43 | #tensor([[3., 3.], 44 | # [3., 3.]], grad_fn=) 45 | 46 | z = y*y*3 #平方x3 47 | print(x) 48 | #tensor([[27., 27.], 49 | # [27., 27.]], grad_fn=) 50 | 51 | out = z.mean() #求均值 52 | print(out) 53 | #tensor(27., grad_fn=) 54 | 55 | ``` 56 | 57 | 从上述代码可以看出: 58 | 59 | 1. x的requires_grad属性为True 60 | 2. 之后的每次计算都会修改其`grad_fn`属性,用来记录做过的操作 61 | 1. 通过这个函数和grad_fn能够组成一个和前一小节类似的计算图 62 | 63 | ### 1.2 requires_grad和grad_fn 64 | 65 | ```python 66 | a = torch.randn(2, 2) 67 | a = ((a * 3) / (a - 1)) 68 | print(a.requires_grad) #False 69 | a.requires_grad_(True) #就地修改 70 | print(a.requires_grad) #True 71 | b = (a * a).sum() 72 | print(b.grad_fn) # 73 | with torch.no_gard(): 74 | c = (a * a).sum() #tensor(151.6830),此时c没有gard_fn 75 | 76 | print(c.requires_grad) #False 77 | ``` 78 | 79 | 注意: 80 | 81 | 为了防止跟踪历史记录(和使用内存),可以将代码块包装在`with torch.no_grad():`中。**在评估模型时特别有用**,因为模型可能具有`requires_grad = True`的可训练的参数,但是我们不需要在此过程中对他们进行梯度计算。 82 | 83 | ## 2. 梯度计算 84 | 85 | 对于1.1 中的out而言,我们可以使用`backward`方法来进行反向传播,计算梯度 86 | 87 | `out.backward()`,此时便能够求出导数$\frac{d out}{dx}$,调用`x.gard`能够获取导数值 88 | 89 | 得到 90 | 91 | ```python 92 | tensor([[4.5000, 4.5000], 93 | [4.5000, 4.5000]]) 94 | ``` 95 | 96 | 因为: 97 | $$ 98 | \frac{d(O)}{d(x_i)} = \frac{3}{2}(x_i+2) 99 | $$ 100 | 在$x_i$等于1时其值为4.5 101 | 102 | 103 | 104 | 注意:在输出为一个标量的情况下,我们可以调用输出`tensor`的`backword()` 方法,但是在数据是一个向量的时候,调用`backward()`的时候还需要传入其他参数。 105 | 106 | 很多时候我们的损失函数都是一个标量,所以这里就不再介绍损失为向量的情况。 107 | 108 | 109 | 110 | `loss.backward()`就是根据损失函数,对参数(requires_grad=True)的去计算他的梯度,并且把它累加保存到`x.gard`,此时还并未更新其梯度 111 | 112 | 113 | 114 | 注意点: 115 | 116 | 1. `tensor.data`: 117 | 118 | - 在tensor的require_grad=False,tensor.data和tensor等价 119 | 120 | - require_grad=True时,tensor.data仅仅是获取tensor中的数据 121 | 122 | 2. `tensor.numpy()`: 123 | 124 | - `require_grad=True`不能够直接转换,需要使用`tensor.detach().numpy()` 125 | 126 | 127 | 128 | ## 3. 线性回归实现 129 | 130 | 下面,我们使用一个自定义的数据,来使用torch实现一个简单的线性回归 131 | 132 | 假设我们的基础模型就是`y = wx+b`,其中w和b均为参数,我们使用`y = 3x+0.8`来构造数据x、y,所以最后通过模型应该能够得出w和b应该分别接近3和0.8 133 | 134 | 1. 准备数据 135 | 2. 计算预测值 136 | 3. 计算损失,把参数的梯度置为0,进行反向传播 137 | 4. 更新参数 138 | 139 | ```python 140 | import torch 141 | import numpy as np 142 | from matplotlib import pyplot as plt 143 | 144 | 145 | #1. 准备数据 y = 3x+0.8,准备参数 146 | x = torch.rand([50]) 147 | y = 3*x + 0.8 148 | 149 | w = torch.rand(1,requires_grad=True) 150 | b = torch.rand(1,requires_grad=True) 151 | 152 | def loss_fn(y,y_predict): 153 | loss = (y_predict-y).pow(2).mean() 154 | for i in [w,b]: 155 | #每次反向传播前把梯度置为0 156 | if i.grad is not None: 157 | i.grad.data.zero_() 158 | # [i.grad.data.zero_() for i in [w,b] if i.grad is not None] 159 | loss.backward() 160 | return loss.data 161 | 162 | def optimize(learning_rate): 163 | # print(w.grad.data,w.data,b.data) 164 | w.data -= learning_rate* w.grad.data 165 | b.data -= learning_rate* b.grad.data 166 | 167 | for i in range(3000): 168 | #2. 计算预测值 169 | y_predict = x*w + b 170 | 171 | #3.计算损失,把参数的梯度置为0,进行反向传播 172 | loss = loss_fn(y,y_predict) 173 | 174 | if i%500 == 0: 175 | print(i,loss) 176 | #4. 更新参数w和b 177 | optimize(0.01) 178 | 179 | # 绘制图形,观察训练结束的预测值和真实值 180 | predict = x*w + b #使用训练后的w和b计算预测值 181 | 182 | plt.scatter(x.data.numpy(), y.data.numpy(),c = "r") 183 | plt.plot(x.data.numpy(), predict.data.numpy()) 184 | plt.show() 185 | 186 | print("w",w) 187 | print("b",b) 188 | ``` 189 | 190 | 191 | 192 | 图形效果如下: 193 | 194 | ![](../images/1.2/线性回归1.png) 195 | 196 | 打印w和b,可有 197 | 198 | ```python 199 | w tensor([2.9280], requires_grad=True) 200 | b tensor([0.8372], requires_grad=True) 201 | ``` 202 | 203 | 可知,w和b已经非常接近原来的预设的3和0.8 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /1.2 pytorch/1.2.5调用 pytorch API完成线性回归.md: -------------------------------------------------------------------------------- 1 | # Pytorch完成基础的模型 2 | 3 | ## 目标 4 | 5 | 1. 知道Pytorch中Module的使用方法 6 | 2. 知道Pytorch中优化器类的使用方法 7 | 3. 知道Pytorch中常见的损失函数的使用方法 8 | 4. 知道如何在GPU上运行代码 9 | 5. 能够说出常见的优化器及其原理 10 | 11 | 12 | 13 | 14 | 15 | ## 1. Pytorch完成模型常用API 16 | 17 | 在前一部分,我们自己实现了通过torch的相关方法完成反向传播和参数更新,在pytorch中预设了一些更加灵活简单的对象,让我们来构造模型、定义损失,优化损失等 18 | 19 | 那么接下来,我们一起来了解一下其中常用的API 20 | 21 | 22 | 23 | ### 1.1 `nn.Module` 24 | 25 | `nn.Modul` 是`torch.nn`提供的一个类,是pytorch中我们`自定义网络`的一个基类,在这个类中定义了很多有用的方法,让我们在继承这个类定义网络的时候非常简单 26 | 27 | 当我们自定义网络的时候,有两个方法需要特别注意: 28 | 29 | 1. `__init__`需要调用`super`方法,继承父类的属性和方法 30 | 2. `farward`方法必须实现,用来定义我们的网络的向前计算的过程 31 | 32 | 用前面的`y = wx+b`的模型举例如下: 33 | 34 | ```python 35 | from torch import nn 36 | class Lr(nn.Module): 37 | def __init__(self): 38 | super(Lr, self).__init__() #继承父类init的参数 39 | self.linear = nn.Linear(1, 1) 40 | 41 | def forward(self, x): 42 | out = self.linear(x) 43 | return out 44 | ``` 45 | 46 | 注意: 47 | 48 | 1. `nn.Linear`为torch预定义好的线性模型,也被称为**全链接层**,传入的参数为输入的数量,输出的数量(in_features, out_features),是不算(batch_size的列数) 49 | 2. `nn.Module`定义了`__call__`方法,实现的就是调用`forward`方法,即`Lr`的实例,能够直接被传入参数调用,实际上调用的是`forward`方法并传入参数 50 | 51 | ```python 52 | # 实例化模型 53 | model = Lr() 54 | # 传入数据,计算结果 55 | predict = model(x) 56 | ``` 57 | 58 | ### 1.2 优化器类 59 | 60 | 优化器(`optimizer`),可以理解为torch为我们封装的用来进行更新参数的方法,比如常见的随机梯度下降(`stochastic gradient descent,SGD`) 61 | 62 | 优化器类都是由`torch.optim`提供的,例如 63 | 64 | 1. `torch.optim.SGD(参数,学习率)` 65 | 2. `torch.optim.Adam(参数,学习率)` 66 | 67 | 注意: 68 | 69 | 1. 参数可以使用`model.parameters()`来获取,获取模型中所有`requires_grad=True`的参数 70 | 2. 优化类的使用方法 71 | 1. 实例化 72 | 2. 所有参数的梯度,将其值置为0 73 | 3. 反向传播计算梯度 74 | 4. 更新参数值 75 | 76 | 示例如下: 77 | 78 | ```python 79 | optimizer = optim.SGD(model.parameters(), lr=1e-3) #1. 实例化 80 | optimizer.zero_grad() #2. 梯度置为0 81 | loss.backward() #3. 计算梯度 82 | optimizer.step() #4. 更新参数的值 83 | ``` 84 | 85 | 86 | 87 | ### 1.3 损失函数 88 | 89 | 前面的例子是一个回归问题,torch中也预测了很多损失函数 90 | 91 | 1. 均方误差:`nn.MSELoss()`,常用于回归问题 92 | 2. 交叉熵损失:`nn.CrossEntropyLoss()`,常用于分类问题 93 | 94 | 使用方法: 95 | 96 | ```python 97 | model = Lr() #1. 实例化模型 98 | criterion = nn.MSELoss() #2. 实例化损失函数 99 | optimizer = optim.SGD(model.parameters(), lr=1e-3) #3. 实例化优化器类 100 | for i in range(100): 101 | y_predict = model(x_true) #4. 向前计算预测值 102 | loss = criterion(y_true,y_predict) #5. 调用损失函数传入真实值和预测值,得到损失结果 103 | optimizer.zero_grad() #5. 当前循环参数梯度置为0 104 | loss.backward() #6. 计算梯度 105 | optimizer.step() #7. 更新参数的值 106 | ``` 107 | 108 | ### 1.4 把线性回归完整代码 109 | 110 | ```python 111 | import torch 112 | from torch import nn 113 | from torch import optim 114 | import numpy as np 115 | from matplotlib import pyplot as plt 116 | 117 | # 1. 定义数据 118 | x = torch.rand([50,1]) 119 | y = x*3 + 0.8 120 | 121 | #2 .定义模型 122 | class Lr(nn.Module): 123 | def __init__(self): 124 | super(Lr,self).__init__() 125 | self.linear = nn.Linear(1,1) 126 | 127 | def forward(self, x): 128 | out = self.linear(x) 129 | return out 130 | 131 | # 2. 实例化模型,loss,和优化器 132 | model = Lr() 133 | criterion = nn.MSELoss() 134 | optimizer = optim.SGD(model.parameters(), lr=1e-3) 135 | #3. 训练模型 136 | for i in range(30000): 137 | out = model(x) #3.1 获取预测值 138 | loss = criterion(y,out) #3.2 计算损失 139 | optimizer.zero_grad() #3.3 梯度归零 140 | loss.backward() #3.4 计算梯度 141 | optimizer.step() # 3.5 更新梯度 142 | if (i+1) % 20 == 0: 143 | print('Epoch[{}/{}], loss: {:.6f}'.format(i,30000,loss.data)) 144 | 145 | #4. 模型评估 146 | model.eval() #设置模型为评估模式,即预测模式 147 | predict = model(x) 148 | predict = predict.data.numpy() 149 | plt.scatter(x.data.numpy(),y.data.numpy(),c="r") 150 | plt.plot(x.data.numpy(),predict) 151 | plt.show() 152 | ``` 153 | 154 | 输出如下: 155 | 156 | ![](../images/1.2/线性回归2.png) 157 | 158 | 注意: 159 | 160 | `model.eval()`表示设置模型为评估模式,即预测模式 161 | 162 | `model.train(mode=True)` 表示设置模型为训练模式 163 | 164 | 在当前的线性回归中,上述并无区别 165 | 166 | 但是在其他的一些模型中,**训练的参数和预测的参数会不相同**,到时候就需要具体告诉程序我们是在进行训练还是预测,比如模型中存在**Dropout**,**BatchNorm**的时候 167 | 168 | ## 2. 在GPU上运行代码 169 | 170 | 当模型太大,或者参数太多的情况下,为了加快训练速度,经常会使用GPU来进行训练 171 | 172 | 此时我们的代码需要稍作调整: 173 | 174 | 1. 判断GPU是否可用`torch.cuda.is_available()` 175 | 176 | ```python 177 | torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 178 | >>device(type='cuda', index=0) #使用gpu 179 | >>device(type='cpu') #使用cpu 180 | ``` 181 | 182 | 183 | 184 | 2. 把模型参数和input数据转化为cuda的支持类型 185 | 186 | ```python 187 | model.to(device) 188 | x_true.to(device) 189 | ``` 190 | 191 | 192 | 193 | 3. 在GPU上计算结果也为cuda的数据类型,需要转化为numpy或者torch的cpu的tensor类型 194 | 195 | ```python 196 | predict = predict.cpu().detach().numpy() 197 | ``` 198 | 199 | `detach()`的效果和data的相似,但是`detach()`是深拷贝,data是取值,是浅拷贝 200 | 201 | 202 | 203 | 修改之后的代码如下: 204 | 205 | ```python 206 | import torch 207 | from torch import nn 208 | from torch import optim 209 | import numpy as np 210 | from matplotlib import pyplot as plt 211 | import time 212 | 213 | # 1. 定义数据 214 | x = torch.rand([50,1]) 215 | y = x*3 + 0.8 216 | 217 | #2 .定义模型 218 | class Lr(nn.Module): 219 | def __init__(self): 220 | super(Lr,self).__init__() 221 | self.linear = nn.Linear(1,1) 222 | 223 | def forward(self, x): 224 | out = self.linear(x) 225 | return out 226 | 227 | # 2. 实例化模型,loss,和优化器 228 | 229 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 230 | x,y = x.to(device),y.to(device) 231 | 232 | model = Lr().to(device) 233 | criterion = nn.MSELoss() 234 | optimizer = optim.SGD(model.parameters(), lr=1e-3) 235 | 236 | #3. 训练模型 237 | for i in range(300): 238 | out = model(x) 239 | loss = criterion(y,out) 240 | 241 | optimizer.zero_grad() 242 | loss.backward() 243 | optimizer.step() 244 | if (i+1) % 20 == 0: 245 | print('Epoch[{}/{}], loss: {:.6f}'.format(i,30000,loss.data)) 246 | 247 | #4. 模型评估 248 | model.eval() # 249 | predict = model(x) 250 | predict = predict.cpu().detach().numpy() #转化为numpy数组 251 | plt.scatter(x.cpu().data.numpy(),y.cpu().data.numpy(),c="r") 252 | plt.plot(x.cpu().data.numpy(),predict,) 253 | plt.show() 254 | 255 | ``` 256 | 257 | ## 3. 常见的优化算法介绍 258 | 259 | ### 3.1 梯度下降算法(batch gradient descent BGD) 260 | 261 | 每次迭代都需要把所有样本都送入,这样的好处是每次迭代都顾及了全部的样本,做的是全局最优化,但是有可能达到局部最优。 262 | 263 | ### 3.2 随机梯度下降法 (Stochastic gradient descent SGD) 264 | 265 | 针对梯度下降算法训练速度过慢的缺点,提出了随机梯度下降算法,随机梯度下降算法算法是从样本中随机抽出一组,训练后按梯度更新一次,然后再抽取一组,再更新一次,在样本量及其大的情况下,可能不用训练完所有的样本就可以获得一个损失值在可接受范围之内的模型了。 266 | 267 | torch中的api为:`torch.optim.SGD()` 268 | 269 | ### 3.3 小批量梯度下降 (Mini-batch gradient descent MBGD) 270 | 271 | SGD相对来说要快很多,但是也有存在问题,由于单个样本的训练可能会带来很多噪声,使得SGD并不是每次迭代都向着整体最优化方向,因此在刚开始训练时可能收敛得很快,但是训练一段时间后就会变得很慢。在此基础上又提出了小批量梯度下降法,它是每次从样本中随机抽取一小批进行训练,而不是一组,这样即保证了效果又保证的速度。 272 | 273 | ### 3.4 动量法 274 | 275 | mini-batch SGD算法虽然这种算法能够带来很好的训练速度,但是在到达最优点的时候并不能够总是真正到达最优点,而是在最优点附近徘徊。 276 | 277 | 另一个缺点就是mini-batch SGD需要我们挑选一个合适的学习率,当我们采用小的学习率的时候,会导致网络在训练的时候收敛太慢;当我们采用大的学习率的时候,会导致在训练过程中优化的幅度跳过函数的范围,也就是可能跳过最优点。我们所希望的仅仅是网络在优化的时候网络的损失函数有一个很好的收敛速度同时又不至于摆动幅度太大。 278 | 279 | 所以Momentum优化器刚好可以解决我们所面临的问题,它主要是基于梯度的移动指数加权平均,对网络的梯度进行平滑处理的,让梯度的摆动幅度变得更小。 280 | $$ 281 | \begin{align*} 282 | &gradent = 0.8\nabla w + 0.2 history\_gradent &,\nabla w 表示当前一次的梯度\\ 283 | &w = w - \alpha* gradent &,\alpha表示学习率 284 | \end{align*} 285 | $$ 286 | 287 | (注:t+1的的histroy_gradent 为第t次的gradent) 288 | 289 | ### 3.5 AdaGrad 290 | 291 | AdaGrad算法就是将每一个参数的每一次迭代的梯度取平方累加后在开方,用全局学习率除以这个数,作为学习率的动态更新,从而达到**自适应学习率**的效果 292 | $$ 293 | \begin{align*} 294 | &gradent = history\_gradent + (\nabla w)^2 \\ 295 | &w = w - \frac{\alpha}{\sqrt{gradent}+\delta} \nabla w ,&\delta为小常数,为了数值稳定大约设置为10^{-7} 296 | \end{align*} 297 | $$ 298 | 299 | ### 3.6 RMSProp 300 | 301 | Momentum优化算法中,虽然初步解决了优化中摆动幅度大的问题,为了进一步优化损失函数在更新中存在摆动幅度过大的问题,并且进一步加快函数的收敛速度,RMSProp算法对参数的梯度使用了平方加权平均数。 302 | $$ 303 | \begin{align*} 304 | & gradent = 0.8*history\_gradent + 0.2*(\nabla w)^2 \\ 305 | & w = w - \frac{\alpha}{\sqrt{gradent}+\delta} \nabla w 306 | \end{align*} 307 | $$ 308 | 309 | ### 3.7 Adam 310 | 311 | Adam(Adaptive Moment Estimation)算法是将Momentum算法和RMSProp算法结合起来使用的一种算法,能够达到防止梯度的摆幅多大,同时还能够加开收敛速度 312 | $$ 313 | \begin{align*} 314 | & 1. 需要初始化梯度的累积量和平方累积量 \\ 315 | & v_w = 0,s_w = 0 \\ 316 | & 2. 第 t 轮训练中,我们首先可以计算得到Momentum和RMSProp的参数更新:\\ 317 | & v_w = 0.8v + 0.2 \nabla w \qquad,Momentum计算的梯度\\ 318 | & s_w = 0.8*s + 0.2*(\nabla w)^2 \qquad,RMSProp计算的梯度\\ 319 | & 3. 对其中的值进行处理后,得到:\\ 320 | & w = w - \frac{\alpha}{\sqrt{s_w}+\delta} v_w 321 | \end{align*} 322 | $$ 323 | torch中的api为:`torch.optim.Adam()` 324 | 325 | 326 | 327 | ### 3.8 效果演示: 328 | 329 | ![](../images/1.2/优化器方法.gif) 330 | 331 | ​ -------------------------------------------------------------------------------- /1.2 pytorch/1.2.6 pytorch中的数据加载.md: -------------------------------------------------------------------------------- 1 | # Pytorch中的数据加载 2 | 3 | ## 目标 4 | 5 | 1. 知道数据加载的目的 6 | 2. 知道pytorch中Dataset的使用方法 7 | 3. 知道pytorch中DataLoader的使用方法 8 | 4. 知道pytorch中的自带数据集如何获取 9 | 10 | 11 | 12 | ## 1. 模型中使用数据加载器的目的 13 | 14 | 在前面的线性回归模型中,我们使用的数据很少,所以直接把全部数据放到模型中去使用。 15 | 16 | 但是在深度学习中,数据量通常是都非常多,非常大的,如此大量的数据,不可能一次性的在模型中进行向前的计算和反向传播,经常我们会对整个数据进行随机的打乱顺序,把数据处理成一个个的batch,同时还会对数据进行预处理。 17 | 18 | 所以,接下来我们来学习pytorch中的数据加载的方法 19 | 20 | 21 | 22 | ## 2. 数据集类 23 | 24 | ### 2.1 Dataset基类介绍 25 | 26 | 在torch中提供了数据集的基类`torch.utils.data.Dataset`,继承这个基类,我们能够非常快速的实现对数据的加载。 27 | 28 | `torch.utils.data.Dataset`的源码如下: 29 | 30 | ```python 31 | class Dataset(object): 32 | """An abstract class representing a Dataset. 33 | 34 | All other datasets should subclass it. All subclasses should override 35 | ``__len__``, that provides the size of the dataset, and ``__getitem__``, 36 | supporting integer indexing in range from 0 to len(self) exclusive. 37 | """ 38 | 39 | def __getitem__(self, index): 40 | raise NotImplementedError 41 | 42 | def __len__(self): 43 | raise NotImplementedError 44 | 45 | def __add__(self, other): 46 | return ConcatDataset([self, other]) 47 | ``` 48 | 49 | 可知:我们需要在自定义的数据集类中继承Dataset类,同时还需要实现两个方法: 50 | 51 | 1. `__len__`方法,能够实现通过全局的`len()`方法获取其中的元素个数 52 | 2. `__getitem__`方法,能够通过传入索引的方式获取数据,例如通过`dataset[i]`获取其中的第`i`条数据 53 | 54 | 55 | 56 | ### 2.2 数据加载案例 57 | 58 | 下面通过一个例子来看看如何使用Dataset来加载数据 59 | 60 | 数据来源:`http://archive.ics.uci.edu/ml/datasets/SMS+Spam+Collection` 61 | 62 | 数据介绍:SMS Spam Collection是用于骚扰短信识别的经典数据集,完全来自真实短信内容,包括4831条正常短信和747条骚扰短信。正常短信和骚扰短信保存在一个文本文件中。 每行完整记录一条短信内容,每行开头通过ham和spam标识正常短信和骚扰短信 63 | 64 | 数据实例: 65 | 66 | ![](../images/1.2/dataset数据示例.png) 67 | 68 | 实现如下: 69 | 70 | ```python 71 | from torch.utils.data import Dataset,DataLoader 72 | import pandas as pd 73 | 74 | data_path = r"data\SMSSpamCollection" 75 | 76 | class CifarDataset(Dataset): 77 | def __init__(self): 78 | lines = open(data_path,"r") 79 | #对数据进行处理,前4个为label,后面的为短信内容 80 | lines = [[i[:4].strip(),i[4:].strip()] for i in lines] 81 | #转化为dataFrame 82 | self.df = pd.DataFrame(lines,columns=["label","sms"]) 83 | 84 | def __getitem__(self, index): 85 | single_item = self.df.iloc[index,:] 86 | return single_item.values[0],single_item.values[1] 87 | 88 | def __len__(self): 89 | return self.df.shape[0] 90 | ``` 91 | 92 | 之后对Dataset进行实例化,可以跌倒获取其中的数据 93 | 94 | ```python 95 | d = CifarDataset() 96 | for i in range(len(d)): 97 | print(i,d[i]) 98 | ``` 99 | 100 | 输出如下: 101 | 102 | ```python 103 | .... 104 | 5571 ('ham', 'Pity, * was in mood for that. So...any other suggestions?') 105 | 5572 ('ham', "The guy did some bitching but I acted like i'd be interested in buying something else next week and he gave it to us for free") 106 | 5573 ('ham', 'Rofl. Its true to its name') 107 | ``` 108 | 109 | 110 | 111 | ## 3. 迭代数据集 112 | 113 | 使用上述的方法能够进行数据的读取,但是其中还有很多内容没有实现: 114 | 115 | - 批处理数据(Batching the data) 116 | - 打乱数据(Shuffling the data) 117 | - 使用多线程 `multiprocessing` 并行加载数据。 118 | 119 | 在pytorch中`torch.utils.data.DataLoader`提供了上述的所用方法 120 | 121 | `DataLoader`的使用方法示例: 122 | 123 | ```python 124 | from torch.utils.data import DataLoader 125 | 126 | dataset = CifarDataset() 127 | data_loader = DataLoader(dataset=dataset,batch_size=10,shuffle=True,num_workers=2) 128 | 129 | #遍历,获取其中的每个batch的结果 130 | for index, (label, context) in enumerate(data_loader): 131 | print(index,label,context) 132 | print("*"*100) 133 | ``` 134 | 135 | 其中参数含义: 136 | 137 | 1. dataset:提前定义的dataset的实例 138 | 2. batch_size:传入数据的batch的大小,常用128,256等等 139 | 3. shuffle:bool类型,表示是否在每次获取数据的时候提前打乱数据 140 | 4. `num_workers`:加载数据的线程数 141 | 142 | 143 | 144 | 数据迭代器的返回结果如下: 145 | 146 | ``` 147 | 555 ('spam', 'ham', 'spam', 'ham', 'ham', 'ham', 'ham', 'spam', 'ham', 'ham') ('URGENT! We are trying to contact U. Todays draw shows that you have won a £800 prize GUARANTEED. Call 09050003091 from....", 'swhrt how u dey,hope ur ok, tot about u 2day.love n miss.take care.') 148 | *********************************************************************************** 149 | 556 ('ham', 'ham', 'ham', 'ham', 'ham', 'ham', 'ham', 'ham', 'ham', 'spam') ('He telling not to tell any one. If so treat for me hi hi hi', 'Did u got that persons story', "Don kn....1000 cash prize or a prize worth £5000') 150 | ``` 151 | 152 | 注意: 153 | 154 | 1. `len(dataset) = 数据集的样本数` 155 | 2. `len(dataloader) = math.ceil(样本数/batch_size) 即向上取整` 156 | 157 | 158 | 159 | ## 4 pytorch自带的数据集 160 | 161 | pytorch中自带的数据集由两个上层api提供,分别是`torchvision`和`torchtext` 162 | 163 | 其中: 164 | 165 | 1. `torchvision`提供了对图片数据处理相关的api和数据 166 | - 数据位置:`torchvision.datasets`,例如:`torchvision.datasets.MNIST`(手写数字图片数据) 167 | 2. `torchtext`提供了对文本数据处理相关的API和数据 168 | - 数据位置:`torchtext.datasets`,例如:`torchtext.datasets.IMDB(电影`评论文本数据) 169 | 170 | 下面我们以Mnist手写数字为例,来看看pytorch如何加载其中自带的数据集 171 | 172 | 使用方法和之前一样: 173 | 174 | 1. 准备好Dataset实例 175 | 2. 把dataset交给dataloder 打乱顺序,组成batch 176 | 177 | ### 4.1 torchversion.datasets 178 | 179 | `torchversoin.datasets`中的数据集类(比如`torchvision.datasets.MNIST`),都是继承自`Dataset` 180 | 181 | 意味着:直接对`torchvision.datasets.MNIST`进行实例化就可以得到`Dataset`的实例 182 | 183 | 但是MNIST API中的参数需要注意一下: 184 | 185 | ` torchvision.datasets.MNIST(root='/files/', train=True, download=True, transform=)` 186 | 187 | 1. `root`参数表示数据存放的位置 188 | 2. `train:`bool类型,表示是使用训练集的数据还是测试集的数据 189 | 3. `download:`bool类型,表示是否需要下载数据到root目录 190 | 4. `transform:`实现的对图片的处理函数 191 | 192 | 193 | 194 | ### 4.2 MNIST数据集的介绍 195 | 196 | 数据集的原始地址:`http://yann.lecun.com/exdb/mnist/` 197 | 198 | MNIST是由`Yann LeCun`等人提供的免费的图像识别的数据集,其中包括60000个训练样本和10000个测试样本,其中图拍了的尺寸已经进行的标准化的处理,都是黑白的图像,大小为`28X28` 199 | 200 | 201 | 202 | 执行代码,下载数据,观察数据类型: 203 | 204 | ```python 205 | import torchvision 206 | 207 | dataset = torchvision.datasets.MNIST(root="./data",train=True,download=True,transform=None) 208 | 209 | print(dataset[0]) 210 | ``` 211 | 212 | 下载的数据如下: 213 | 214 | ![](../images/1.2/MNIST-dataset.png) 215 | 216 | 代码输出结果如下: 217 | 218 | ```python 219 | Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz 220 | Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz 221 | Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz 222 | Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz 223 | Processing... 224 | Done! 225 | (, tensor(5)) 226 | ``` 227 | 228 | 229 | 230 | 可以其中数据集返回了两条数据,可以猜测为图片的数据和目标值 231 | 232 | 返回值的第0个为Image类型,可以调用show() 方法打开,发现为手写数字5 233 | 234 | ```python 235 | import torchvision 236 | 237 | dataset = torchvision.datasets.MNIST(root="./data",train=True,download=True,transform=None) 238 | 239 | print(dataset[0]) 240 | 241 | img = dataset[0][0] 242 | img.show() #打开图片 243 | ``` 244 | 245 | 图片如下: 246 | 247 | ![](../images/1.2/MNIST-dataset-5.png) 248 | 249 | 由上可知:返回值为`(图片,目标值)`,这个结果也可以通过观察源码得到 -------------------------------------------------------------------------------- /1.2 pytorch/1.2.7 使用pytorch实现手写数字识别.md: -------------------------------------------------------------------------------- 1 | # 使用Pytorch实现手写数字识别 2 | 3 | ## 目标 4 | 5 | 1. 知道如何使用Pytorch完成神经网络的构建 6 | 2. 知道Pytorch中激活函数的使用方法 7 | 3. 知道Pytorch中`torchvision.transforms`中常见图形处理函数的使用 8 | 4. 知道如何训练模型和如何评估模型 9 | 10 | 11 | 12 | ## 1. 思路和流程分析 13 | 14 | 流程: 15 | 16 | 1. 准备数据,这些需要准备DataLoader 17 | 2. 构建模型,这里可以使用torch构造一个深层的神经网络 18 | 3. 模型的训练 19 | 4. 模型的保存,保存模型,后续持续使用 20 | 5. 模型的评估,使用测试集,观察模型的好坏 21 | 22 | ## 2. 准备训练集和测试集 23 | 24 | 准备数据集的方法前面已经讲过,但是通过前面的内容可知,调用MNIST返回的结果中图形数据是一个Image对象,需要对其进行处理 25 | 26 | 为了进行数据的处理,接下来学习`torchvision.transfroms`的方法 27 | 28 | ### 2.1 `torchvision.transforms`的图形数据处理方法 29 | 30 | #### 2.1.1 `torchvision.transforms.ToTensor` 31 | 32 | 把一个取值范围是`[0,255]`的`PIL.Image`或者`shape`为`(H,W,C)`的`numpy.ndarray`,转换成形状为`[C,H,W]` 33 | 34 | 其中`(H,W,C)`意思为`(高,宽,通道数)`,黑白图片的通道数只有1,其中每个像素点的取值为[0,255],彩色图片的通道数为(R,G,B),每个通道的每个像素点的取值为[0,255],三个通道的颜色相互叠加,形成了各种颜色 35 | 36 | 示例如下: 37 | 38 | ```python 39 | from torchvision import transforms 40 | import numpy as np 41 | 42 | data = np.random.randint(0, 255, size=12) 43 | img = data.reshape(2,2,3) 44 | print(img.shape) 45 | img_tensor = transforms.ToTensor()(img) # 转换成tensor 46 | print(img_tensor) 47 | print(img_tensor.shape) 48 | ``` 49 | 50 | 输出如下: 51 | 52 | ```python 53 | shape:(2, 2, 3) 54 | img_tensor:tensor([[[215, 171], 55 | [ 34, 12]], 56 | 57 | [[229, 87], 58 | [ 15, 237]], 59 | 60 | [[ 10, 55], 61 | [ 72, 204]]], dtype=torch.int32) 62 | new shape:torch.Size([3, 2, 2]) 63 | 64 | ``` 65 | 66 | 注意: 67 | 68 | `transforms.ToTensor`对象中有`__call__`方法,所以可以对其示例能够传入数据获取结果 69 | 70 | #### 2.1.2 `torchvision.transforms.Normalize(mean, std)` 71 | 72 | 给定均值:mean,shape和图片的通道数相同(指的是每个通道的均值),方差:std,和图片的通道数相同(指的是每个通道的方差),将会把`Tensor`规范化处理。 73 | 74 | 即:`Normalized_image=(image-mean)/std`。 75 | 76 | 例如: 77 | 78 | ```python 79 | from torchvision import transforms 80 | import numpy as np 81 | import torchvision 82 | 83 | data = np.random.randint(0, 255, size=12) 84 | img = data.reshape(2,2,3) 85 | img = transforms.ToTensor()(img) # 转换成tensor 86 | print(img) 87 | print("*"*100) 88 | 89 | norm_img = transforms.Normalize((10,10,10), (1,1,1))(img) #进行规范化处理 90 | 91 | print(norm_img) 92 | ``` 93 | 94 | 输出如下: 95 | 96 | ``` 97 | tensor([[[177, 223], 98 | [ 71, 182]], 99 | 100 | [[153, 120], 101 | [173, 33]], 102 | 103 | [[162, 233], 104 | [194, 73]]], dtype=torch.int32) 105 | *************************************************************************************** 106 | tensor([[[167, 213], 107 | [ 61, 172]], 108 | 109 | [[143, 110], 110 | [163, 23]], 111 | 112 | [[152, 223], 113 | [184, 63]]], dtype=torch.int32) 114 | ``` 115 | 116 | 注意:在sklearn中,默认上式中的std和mean为数据每列的std和mean,sklearn会在标准化之前算出每一列的std和mean。 117 | 118 | 但是在api:Normalize中并没有帮我们计算,所以我们需要手动计算 119 | 120 | 1. 当mean为全部数据的均值,std为全部数据的std的时候,才是进行了标准化。 121 | 122 | 2. 如果mean(x)不是全部数据的mean的时候,std(y)也不是的时候,Normalize后的数据分布满足下面的关系 123 | $$ 124 | \begin{align*} 125 | &new\_mean = \frac{mean-x}{y}&, mean为原数据的均值,x为传入的均值x \\ 126 | &new\_std = \frac{std}{y} &,y为传入的标准差y\\ 127 | \end{align*} 128 | $$ 129 | 130 | 131 | #### 2.1.3 `torchvision.transforms.Compose(transforms)` 132 | 133 | 将多个`transform`组合起来使用。 134 | 135 | 例如 136 | 137 | 138 | 139 | ```python 140 | transforms.Compose([ 141 | torchvision.transforms.ToTensor(), #先转化为Tensor 142 | torchvision.transforms.Normalize(mean,std) #在进行正则化 143 | ]) 144 | ``` 145 | 146 | 147 | 148 | ### 2.2 准备MNIST数据集的Dataset和DataLoader 149 | 150 | 准备训练集 151 | 152 | ```python 153 | import torchvision 154 | 155 | #准备数据集,其中0.1307,0.3081为MNIST数据的均值和标准差,这样操作能够对其进行标准化 156 | #因为MNIST只有一个通道(黑白图片),所以元组中只有一个值 157 | dataset = torchvision.datasets.MNIST('/data', train=True, download=True, 158 | transform=torchvision.transforms.Compose([ 159 | torchvision.transforms.ToTensor(), 160 | torchvision.transforms.Normalize( 161 | (0.1307,), (0.3081,)) 162 | ])) 163 | #准备数据迭代器 164 | train_dataloader = torch.utils.data.DataLoader(dataset,batch_size=64,shuffle=True) 165 | ``` 166 | 167 | 准备测试集 168 | 169 | ```python 170 | import torchvision 171 | 172 | #准备数据集,其中0.1307,0.3081为MNIST数据的均值和标准差,这样操作能够对其进行标准化 173 | #因为MNIST只有一个通道(黑白图片),所以元组中只有一个值 174 | dataset = torchvision.datasets.MNIST('/data', train=False, download=True, 175 | transform=torchvision.transforms.Compose([ 176 | torchvision.transforms.ToTensor(), 177 | torchvision.transforms.Normalize( 178 | (0.1307,), (0.3081,)) 179 | ])) 180 | #准备数据迭代器 181 | train_dataloader = torch.utils.data.DataLoader(dataset,batch_size=64,shuffle=True) 182 | ``` 183 | 184 | ## 3. 构建模型 185 | 186 | 补充:**全连接层**:当前一层的神经元和前一层的神经元相互链接,其核心操作就是$y = wx​$,即矩阵的乘法,实现对前一层的数据的变换 187 | 188 | 模型的构建使用了一个三层的神经网络,其中包括两个全连接层和一个输出层,第一个全连接层会经过激活函数的处理,将处理后的结果交给下一个全连接层,进行变换后输出结果 189 | 190 | 那么在这个模型中有两个地方需要注意: 191 | 192 | 1. 激活函数如何使用 193 | 2. 每一层数据的形状 194 | 3. 模型的损失函数 195 | 196 | ### 3.1 激活函数的使用 197 | 198 | 前面介绍了激活函数的作用,常用的激活函数为Relu激活函数,他的使用非常简单 199 | 200 | Relu激活函数由`import torch.nn.functional as F`提供,`F.relu(x)`即可对x进行处理 201 | 202 | 例如: 203 | 204 | ```python 205 | In [30]: b 206 | Out[30]: tensor([-2, -1, 0, 1, 2]) 207 | 208 | In [31]: import torch.nn.functional as F 209 | 210 | In [32]: F.relu(b) 211 | Out[32]: tensor([0, 0, 0, 1, 2]) 212 | ``` 213 | 214 | 215 | 216 | ### 3.2 模型中数据的形状(【添加形状变化图形】) 217 | 218 | 1. 原始输入数据为的形状:`[batch_size,1,28,28]` 219 | 2. 进行形状的修改:`[batch_size,28*28]` ,(全连接层是在进行矩阵的乘法操作) 220 | 3. 第一个全连接层的输出形状:`[batch_size,28]`,这里的28是个人设定的,你也可以设置为别的 221 | 4. 激活函数不会修改数据的形状 222 | 5. 第二个全连接层的输出形状:`[batch_size,10]`,因为手写数字有10个类别 223 | 224 | 构建模型的代码如下: 225 | 226 | ```python 227 | import torch 228 | from torch import nn 229 | import torch.nn.functional as F 230 | 231 | class MnistNet(nn.Module): 232 | def __init__(self): 233 | super(MnistNet,self).__init__() 234 | self.fc1 = nn.Linear(28*28*1,28) #定义Linear的输入和输出的形状 235 | self.fc2 = nn.Linear(28,10) #定义Linear的输入和输出的形状 236 | 237 | def forward(self,x): 238 | x = x.view(-1,28*28*1) #对数据形状变形,-1表示该位置根据后面的形状自动调整 239 | x = self.fc1(x) #[batch_size,28] 240 | x = F.relu(x) #[batch_size,28] 241 | x = self.fc2(x) #[batch_size,10] 242 | 243 | ``` 244 | 245 | 可以发现:pytorch在构建模型的时候`形状上`并不会考虑`batch_size` 246 | 247 | ### 3.3 模型的损失函数 248 | 249 | 首先,我们需要明确,当前我们手写字体识别的问题是一个多分类的问题,所谓多分类对比的是之前学习的2分类 250 | 251 | 回顾之前的课程,我们在逻辑回归中,我们使用sigmoid进行计算对数似然损失,来定义我们的2分类的损失。 252 | 253 | - 在2分类中我们有正类和负类,正类的概率为$P(x) = \frac{1}{1+e^{-x}} = \frac{e^x}{1+e^x}$,那么负类的概率为$1-P(x)​$ 254 | 255 | - 将这个结果进行计算对数似然损失$-\sum y log(P(x))​$就可以得到最终的损失 256 | 257 | 那么在多分类的过程中我们应该怎么做呢? 258 | 259 | - 多分类和2分类中唯一的区别是我们不能够再使用sigmoid函数来计算当前样本属于某个类别的概率,而应该使用softmax函数。 260 | 261 | - softmax和sigmoid的区别在于我们需要去计算样本属于每个类别的概率,需要计算多次,而sigmoid只需要计算一次 262 | 263 | softmax的公式如下: 264 | $$ 265 | \sigma(z)_j = \frac{e^{z_j}}{\sum^K_{k=1}e^{z_K}} ,j=1 \cdots k 266 | $$ 267 | 268 | 269 | 例如下图: 270 | 271 | ![](../images/1.2/softmax.png) 272 | 273 | 假如softmax之前的输出结果是`2.3, 4.1, 5.6`,那么经过softmax之后的结果是多少呢? 274 | $$ 275 | Y1 = \frac{e^{2.3}}{e^{2.3}+e^{4.1}+e^{5.6}} \\ 276 | Y2 = \frac{e^{4.1}}{e^{2.3}+e^{4.1}+e^{5.6}} \\ 277 | Y3 = \frac{e^{5.6}}{e^{2.3}+e^{4.1}+e^{5.6}} \\ 278 | $$ 279 | 280 | 281 | 对于这个softmax输出的结果,是在[0,1]区间,我们可以把它当做概率 282 | 283 | 和前面2分类的损失一样,多分类的损失只需要再把这个结果进行对数似然损失的计算即可 284 | 285 | 即: 286 | $$ 287 | \begin{align*} 288 | & J = -\sum Y log(P) &, 其中 P = \frac{e^{z_j}}{\sum^K_{k=1}e^{z_K}} ,Y表示真实值 289 | \end{align*} 290 | $$ 291 | 最后,会计算每个样本的损失,即上式的平均值 292 | 293 | 我们把softmax概率传入对数似然损失得到的损失函数称为**交叉熵损失** 294 | 295 | 在pytorch中有两种方法实现交叉熵损失 296 | 297 | 1. ``` 298 | criterion = nn.CrossEntropyLoss() 299 | loss = criterion(input,target) 300 | ``` 301 | 302 | 2. ``` 303 | #1. 对输出值计算softmax和取对数 304 | output = F.log_softmax(x,dim=-1) 305 | #2. 使用torch中带权损失 306 | loss = F.nll_loss(output,target) 307 | ``` 308 | 309 | 带权损失定义为:$l_n = -\sum w_{i} x_{i}$,其实就是把$log(P)$作为$x_i$,把真实值Y作为权重 310 | 311 | 312 | 313 | ## 4. 模型的训练 314 | 315 | 训练的流程: 316 | 317 | 1. 实例化模型,设置模型为训练模式 318 | 2. 实例化优化器类,实例化损失函数 319 | 3. 获取,遍历dataloader 320 | 4. 梯度置为0 321 | 5. 进行向前计算 322 | 6. 计算损失 323 | 7. 反向传播 324 | 8. 更新参数 325 | 326 | ```python 327 | mnist_net = MnistNet() 328 | optimizer = optim.Adam(mnist_net.parameters(),lr= 0.001) 329 | def train(epoch): 330 | mode = True 331 | mnist_net.train(mode=mode) #模型设置为训练模型 332 | 333 | train_dataloader = get_dataloader(train=mode) #获取训练数据集 334 | for idx,(data,target) in enumerate(train_dataloader): 335 | optimizer.zero_grad() #梯度置为0 336 | output = mnist_net(data) #进行向前计算 337 | loss = F.nll_loss(output,target) #带权损失 338 | loss.backward() #进行反向传播,计算梯度 339 | optimizer.step() #参数更新 340 | if idx % 10 == 0: 341 | print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( 342 | epoch, idx * len(data), len(train_dataloader.dataset), 343 | 100. * idx / len(train_dataloader), loss.item())) 344 | ``` 345 | 346 | 347 | 348 | ## 5. 模型的保存和加载 349 | 350 | ### 5.1 模型的保存 351 | 352 | ```python 353 | torch.save(mnist_net.state_dict(),"model/mnist_net.pt") #保存模型参数 354 | torch.save(optimizer.state_dict(), 'results/mnist_optimizer.pt') #保存优化器参数 355 | ``` 356 | 357 | 358 | 359 | ### 5.2 模型的加载 360 | 361 | ```python 362 | mnist_net.load_state_dict(torch.load("model/mnist_net.pt")) 363 | optimizer.load_state_dict(torch.load("results/mnist_optimizer.pt")) 364 | ``` 365 | 366 | ## 6. 模型的评估 367 | 368 | 评估的过程和训练的过程相似,但是: 369 | 370 | 1. 不需要计算梯度 371 | 2. 需要收集损失和准确率,用来计算平均损失和平均准确率 372 | 3. 损失的计算和训练时候损失的计算方法相同 373 | 4. 准确率的计算: 374 | - 模型的输出为[batch_size,10]的形状 375 | - 其中最大值的位置就是其预测的目标值(预测值进行过sotfmax后为概率,sotfmax中分母都是相同的,分子越大,概率越大) 376 | - 最大值的位置获取的方法可以使用`torch.max`,返回最大值和最大值的位置 377 | - 返回最大值的位置后,和真实值(`[batch_size]`)进行对比,相同表示预测成功 378 | 379 | ```python 380 | def test(): 381 | test_loss = 0 382 | correct = 0 383 | mnist_net.eval() #设置模型为评估模式 384 | test_dataloader = get_dataloader(train=False) #获取评估数据集 385 | with torch.no_grad(): #不计算其梯度 386 | for data, target in test_dataloader: 387 | output = mnist_net(data) 388 | test_loss += F.nll_loss(output, target, reduction='sum').item() 389 | pred = output.data.max(1, keepdim=True)[1] #获取最大值的位置,[batch_size,1] 390 | correct += pred.eq(target.data.view_as(pred)).sum() #预测准备样本数累加 391 | test_loss /= len(test_dataloader.dataset) #计算平均损失 392 | print('\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format( 393 | test_loss, correct, len(test_dataloader.dataset), 394 | 100. * correct / len(test_dataloader.dataset))) 395 | ``` 396 | 397 | 398 | 399 | ## 7. 完整的代码如下: 400 | 401 | ```python 402 | import torch 403 | from torch import nn 404 | from torch import optim 405 | import torch.nn.functional as F 406 | import torchvision 407 | 408 | train_batch_size = 64 409 | test_batch_size = 1000 410 | img_size = 28 411 | 412 | def get_dataloader(train=True): 413 | assert isinstance(train,bool),"train 必须是bool类型" 414 | 415 | #准备数据集,其中0.1307,0.3081为MNIST数据的均值和标准差,这样操作能够对其进行标准化 416 | #因为MNIST只有一个通道(黑白图片),所以元组中只有一个值 417 | dataset = torchvision.datasets.MNIST('/data', train=train, download=True, 418 | transform=torchvision.transforms.Compose([ 419 | torchvision.transforms.ToTensor(), 420 | torchvision.transforms.Normalize((0.1307,), (0.3081,)),])) 421 | #准备数据迭代器 422 | batch_size = train_batch_size if train else test_batch_size 423 | dataloader = torch.utils.data.DataLoader(dataset,batch_size=batch_size,shuffle=True) 424 | return dataloader 425 | 426 | class MnistNet(nn.Module): 427 | def __init__(self): 428 | super(MnistNet,self).__init__() 429 | self.fc1 = nn.Linear(28*28*1,28) 430 | self.fc2 = nn.Linear(28,10) 431 | 432 | def forward(self,x): 433 | x = x.view(-1,28*28*1) 434 | x = self.fc1(x) #[batch_size,28] 435 | x = F.relu(x) #[batch_size,28] 436 | x = self.fc2(x) #[batch_size,10] 437 | # return x 438 | return F.log_softmax(x,dim=-1) 439 | 440 | mnist_net = MnistNet() 441 | optimizer = optim.Adam(mnist_net.parameters(),lr= 0.001) 442 | # criterion = nn.NLLLoss() 443 | # criterion = nn.CrossEntropyLoss() 444 | train_loss_list = [] 445 | train_count_list = [] 446 | 447 | def train(epoch): 448 | mode = True 449 | mnist_net.train(mode=mode) 450 | train_dataloader = get_dataloader(train=mode) 451 | print(len(train_dataloader.dataset)) 452 | print(len(train_dataloader)) 453 | for idx,(data,target) in enumerate(train_dataloader): 454 | optimizer.zero_grad() 455 | output = mnist_net(data) 456 | loss = F.nll_loss(output,target) #对数似然损失 457 | loss.backward() 458 | optimizer.step() 459 | if idx % 10 == 0: 460 | print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( 461 | epoch, idx * len(data), len(train_dataloader.dataset), 462 | 100. * idx / len(train_dataloader), loss.item())) 463 | 464 | train_loss_list.append(loss.item()) 465 | train_count_list.append(idx*train_batch_size+(epoch-1)*len(train_dataloader)) 466 | torch.save(mnist_net.state_dict(),"model/mnist_net.pkl") 467 | torch.save(optimizer.state_dict(), 'results/mnist_optimizer.pkl') 468 | 469 | 470 | def test(): 471 | test_loss = 0 472 | correct = 0 473 | mnist_net.eval() 474 | test_dataloader = get_dataloader(train=False) 475 | with torch.no_grad(): 476 | for data, target in test_dataloader: 477 | output = mnist_net(data) 478 | test_loss += F.nll_loss(output, target, reduction='sum').item() 479 | pred = output.data.max(1, keepdim=True)[1] #获取最大值的位置,[batch_size,1] 480 | correct += pred.eq(target.data.view_as(pred)).sum() 481 | test_loss /= len(test_dataloader.dataset) 482 | print('\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format( 483 | test_loss, correct, len(test_dataloader.dataset), 484 | 100. * correct / len(test_dataloader.dataset))) 485 | 486 | 487 | if __name__ == '__main__': 488 | 489 | test() 490 | for i in range(5): #模型训练5轮 491 | train(i) 492 | test() 493 | ``` 494 | 495 | 496 | 497 | -------------------------------------------------------------------------------- /1.2 pytorch/README.md: -------------------------------------------------------------------------------- 1 | # 1.2 Pytorch -------------------------------------------------------------------------------- /1.3 循环神经网络/1.3.1 循环神经网络基础.md: -------------------------------------------------------------------------------- 1 | # 循环神经网络和自然语言处理介绍 2 | 3 | ## 目标 4 | 5 | 1. 知道`token`和`tokenization` 6 | 2. 知道`N-gram`的概念和作用 7 | 3. 知道文本向量化表示的方法 8 | 9 | 10 | 11 | 12 | 13 | ## 1. 文本的`tokenization` 14 | 15 | ### 1.1 概念和工具的介绍 16 | 17 | `tokenization`就是通常所说的分词,分出的每一个词语我们把它称为`token`。 18 | 19 | 常见的分词工具很多,比如: 20 | 21 | - `jieba分词:https://github.com/fxsjy/jieba` 22 | - 清华大学的分词工具THULAC:`https://github.com/thunlp/THULAC-Python` 23 | 24 | ### 1.2 中英文分词的方法 25 | 26 | - 把句子转化为词语 27 | - 比如:`我爱深度学习` 可以分为`[我,爱, 深度学习]` 28 | - 把句子转化为单个字 29 | - 比如:`我爱深度学习`的token是`[我,爱,深,度,学,习]` 30 | 31 | 32 | 33 | ## 2. `N-garm`表示方法 34 | 35 | 前面我们说,句子可以用但个字,词来表示,但是有的时候,我们可以用2个、3个或者多个词来表示。 36 | 37 | `N-gram`一组一组的词语,其中的`N`表示能够被一起使用的词的数量 38 | 39 | 例如: 40 | 41 | ```python 42 | In [59]: text = "深度学习(英语:deep learning)是机器学习的分支,是一种以人工神经网络为架构,对数据进行表征学习的算法。" 43 | 44 | In [60]: cuted = jieba.lcut(text) 45 | 46 | In [61]: [cuted[i:i+2] for i in range(len(cuted)-1)] #N-gram 中n=2时 47 | Out[61]:[['深度', '学习'], 48 | ['学习', '('], 49 | ['(', '英语'], 50 | ['英语', ':'], 51 | [':', 'deep'], 52 | ['deep', ' '], 53 | [' ', 'learning'], 54 | ['learning', ')'], 55 | [')', '是'], 56 | ['是', '机器'], 57 | ['机器', '学习'], 58 | ['学习', '的'], 59 | ['的', '分支'], 60 | ['分支', ','], 61 | [',', '是'], 62 | ['是', '一种'], 63 | ['一种', '以'], 64 | ['以', '人工神经网络'], 65 | ['人工神经网络', '为'], 66 | ['为', '架构'], 67 | ['架构', ','], 68 | [',', '对'], 69 | ['对', '数据'], 70 | ['数据', '进行'], 71 | ['进行', '表征'], 72 | ['表征', '学习'], 73 | ['学习', '的'], 74 | ['的', '算法'], 75 | ['算法', '。']] 76 | ``` 77 | 78 | 在传统的机器学习中,使用N-gram方法往往能够取得非常好的效果,但是在深度学习比如RNN中会自带N-gram的效果。 79 | 80 | 81 | 82 | ## 3. 向量化 83 | 84 | 因为文本不能够直接被模型计算,所以需要将其转化为向量 85 | 86 | 把文本转化为向量有两种方法: 87 | 88 | 1. 转化为one-hot编码 89 | 2. 转化为word embedding 90 | 91 | ### 3.1 one-hot 编码 92 | 93 | 在one-hot编码中,每一个token使用一个长度为N的向量表示,N表示词典的数量 94 | 95 | 即:把待处理的文档进行分词或者是N-gram处理,然后进行去重得到词典,假设我们有一个文档:`深度学习`,那么进行one-hot处理后的结果如下: 96 | 97 | | token | one-hot encoding | 98 | | ----- | ---------------- | 99 | | 深 | 1000 | 100 | | 度 | 0100 | 101 | | 学 | 0010 | 102 | | 习 | 0001 | 103 | 104 | 105 | 106 | ### 3.2 word embedding 107 | 108 | word embedding是深度学习中表示文本常用的一种方法。和one-hot编码不同,word embedding使用了浮点型的稠密矩阵来表示token。根据词典的大小,我们的向量通常使用不同的维度,例如100,256,300等。其中向量中的每一个值是一个参数,其初始值是随机生成的,之后会在训练的过程中进行学习而获得。 109 | 110 | 如果我们文本中有20000个词语,如果使用one-hot编码,那么我们会有20000*20000的矩阵,其中大多数的位置都为0,但是如果我们使用word embedding来表示的话,只需要20000\* 维度,比如20000\*300 111 | 112 | 形象的表示就是: 113 | 114 | | token | num | vector | 115 | | ----- | ---- | ------------------------------------------------- | 116 | | 词1 | 0 | `[w11,w12,w13...w1N]` ,其中N表示维度(dimension) | 117 | | 词2 | 1 | `[w21,w22,w23...w2N] ` | 118 | | 词3 | 2 | `[w31,w23,w33...w3N] ` | 119 | | ... | …. | ... | 120 | | 词m | m | `[wm1,wm2,wm3...wmN]`,其中m表示词典的大小 | 121 | 122 | 我们会把所有的文本转化为向量,把句子用向量来表示 123 | 124 | 但是在这中间,**我们会先把token使用数字来表示,再把数字使用向量来表示。** 125 | 126 | 即:`token---> num ---->vector` 127 | 128 | ![](../images/1.3/word_embedding.png) 129 | 130 | ### 3.3 word embedding API 131 | 132 | `torch.nn.Embedding(num_embeddings,embedding_dim)` 133 | 134 | 参数介绍: 135 | 136 | 1. `num_embeddings`:词典的大小 137 | 2. `embedding_dim`:embedding的维度 138 | 139 | 使用方法: 140 | 141 | ```python 142 | embedding = nn.Embedding(vocab_size,300) #实例化 143 | input_embeded = embedding(input) #进行embedding的操作 144 | ``` 145 | 146 | ### 3.4 数据的形状变化 147 | 148 | 思考:每个batch中的每个句子有10个词语,经过形状为[20,4]的Word emebedding之后,原来的句子会变成什么形状? 149 | 150 | 每个词语用长度为4的向量表示,所以,最终句子会变为`[batch_size,10,4]`的形状。 151 | 152 | 增加了一个维度,这个维度是embedding的dim 153 | 154 | -------------------------------------------------------------------------------- /1.3 循环神经网络/1.3.2 文本情感分类.md: -------------------------------------------------------------------------------- 1 | # 文本情感分类 2 | 3 | ## 目标 4 | 5 | 1. 知道文本处理的基本方法 6 | 2. 能够使用数据实现情感分类的 7 | 8 | 9 | 10 | ## 1. 案例介绍 11 | 12 | 为了对前面的word embedding这种常用的文本向量化的方法进行巩固,这里我们会完成一个文本情感分类的案例 13 | 14 | 现在我们有一个经典的数据集`IMDB`数据集,地址:`http://ai.stanford.edu/~amaas/data/sentiment/`,这是一份包含了5万条流行电影的评论数据,其中训练集25000条,测试集25000条。数据格式如下: 15 | 16 | 下图左边为名称,其中名称包含两部分,分别是序号和情感评分,(1-4为neg,5-10为pos),右边为评论内容 17 | 18 | ![](../images/1.3/样本名称.png) 19 | 20 | 根据上述的样本,需要使用pytorch完成模型,实现对评论情感进行预测 21 | 22 | 23 | 24 | ## 2. 思路分析 25 | 26 | 首先可以把上述问题定义为分类问题,情感评分分为1-10,10个类别(也可以理解为回归问题,这里当做分类问题考虑)。那么根据之前的经验,我们的大致流程如下: 27 | 28 | 1. 准备数据集 29 | 2. 构建模型 30 | 3. 模型训练 31 | 4. 模型评估 32 | 33 | 知道思路之后,那么我们一步步来完成上述步骤 34 | 35 | ## 3. 准备数据集 36 | 37 | 准备数据集和之前的方法一样,实例化dataset,准备dataloader,最终我们的数据可以处理成如下格式: 38 | 39 | ![](../images/1.3/情感分类-data加载1.png) 40 | 41 | 其中有两点需要注意: 42 | 43 | 1. 如何完成基础打Dataset的构建和Dataloader的准备 44 | 2. 每个batch中文本的长度不一致的问题如何解决 45 | 3. 每个batch中的文本如何转化为数字序列 46 | 47 | ### 3.1 基础Dataset的准备 48 | 49 | ```python 50 | import torch 51 | from torch.utils.data import DataLoader,Dataset 52 | import os 53 | import re 54 | 55 | data_base_path = r"data\aclImdb" 56 | 57 | #1. 定义tokenize的方法 58 | def tokenize(text): 59 | # fileters = '!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~\t\n' 60 | fileters = ['!','"','#','$','%','&','\(','\)','\*','\+',',','-','\.','/',':',';','<','=','>','\?','@' 61 | ,'\[','\\','\]','^','_','`','\{','\|','\}','~','\t','\n','\x97','\x96','”','“',] 62 | text = re.sub("<.*?>"," ",text,flags=re.S) 63 | text = re.sub("|".join(fileters)," ",text,flags=re.S) 64 | return [i.strip() for i in text.split()] 65 | 66 | #2. 准备dataset 67 | class ImdbDataset(Dataset): 68 | def __init__(self,mode): 69 | super(ImdbDataset,self).__init__() 70 | if mode=="train": 71 | text_path = [os.path.join(data_base_path,i) for i in ["train/neg","train/pos"]] 72 | else: 73 | text_path = [os.path.join(data_base_path,i) for i in ["test/neg","test/pos"]] 74 | 75 | self.total_file_path_list = [] 76 | for i in text_path: 77 | self.total_file_path_list.extend([os.path.join(i,j) for j in os.listdir(i)]) 78 | 79 | 80 | def __getitem__(self, idx): 81 | cur_path = self.total_file_path_list[idx] 82 | 83 | cur_filename = os.path.basename(cur_path) 84 | label = int(cur_filename.split("_")[-1].split(".")[0]) -1 #处理标题,获取label,转化为从[0-9] 85 | text = tokenize(open(cur_path).read().strip()) #直接按照空格进行分词 86 | return label,text 87 | 88 | def __len__(self): 89 | return len(self.total_file_path_list) 90 | 91 | # 2. 实例化,准备dataloader 92 | dataset = ImdbDataset(mode="train") 93 | dataloader = DataLoader(dataset=dataset,batch_size=2,shuffle=True) 94 | 95 | #3. 观察数据输出结果 96 | for idx,(label,text) in enumerate(dataloader): 97 | print("idx:",idx) 98 | print("table:",label) 99 | print("text:",text) 100 | break 101 | ``` 102 | 103 | 输出如下: 104 | 105 | ```python 106 | idx: 0 107 | table: tensor([3, 1]) 108 | text: [('I', 'Want'), ('thought', 'a'), ('this', 'great'), ('was', 'recipe'), ('a', 'for'), ('great', 'failure'), ('idea', 'Take'), ('but', 'a'), ('boy', 's'), ('was', 'y'), ('it', 'plot'), ('poorly', 'add'), ('executed', 'in'), ('We', 'some'), ('do', 'weak'), ('get', 'completely'), ('a', 'undeveloped'), ('broad', 'characters'), ('sense', 'and'), ('of', 'than'), ('how', 'throw'), ('complex', 'in'), ('and', 'the'), ('challenging', 'worst'), ('the', 'special'), ('backstage', 'effects'), ('operations', 'a'), ('of', 'horror'), ('a', 'movie'), ('show', 'has'), ('are', 'known'), ('but', 'Let'), ('virtually', 'stew'), ('no', 'for'), ...('show', 'somehow'), ('rather', 'destroy'), ('than', 'every'), ('anything', 'copy'), ('worth', 'of'), ('watching', 'this'), ('for', 'film'), ('its', 'so'), ('own', 'it'), ('merit', 'will')] 109 | ``` 110 | 111 | 明显,其中的text内容出现对应,和想象的不太相似,出现问题的原因在于`Dataloader`中的参数`collate_fn` 112 | 113 | `collate_fn`的默认值为torch自定义的`default_collate`,`collate_fn`的作用就是对每个batch进行处理,而默认的`default_collate`处理出错。 114 | 115 | 解决问题的思路: 116 | 117 | 手段1:考虑先把数据转化为数字序列,观察其结果是否符合要求,之前使用DataLoader并未出现类似错误 118 | 119 | 手段2:考虑自定义一个`collate_fn`,观察结果 120 | 121 | 这里使用方式2,自定义一个`collate_fn`,然后观察结果: 122 | 123 | ```python 124 | def collate_fn(batch): 125 | #batch是list,其中是一个一个元组,每个元组是dataset中__getitem__的结果 126 | batch = list(zip(*batch)) 127 | labes = torch.tensor(batch[0],dtype=torch.int32) 128 | texts = batch[1] 129 | del batch 130 | return labes,texts 131 | dataloader = DataLoader(dataset=dataset,batch_size=2,shuffle=True,collate_fn=collate_fn) 132 | 133 | #此时输出正常 134 | for idx,(label,text) in enumerate(dataloader): 135 | print("idx:",idx) 136 | print("table:",label) 137 | print("text:",text) 138 | break 139 | ``` 140 | 141 | 142 | 143 | ### 3.2 文本序列化 144 | 145 | > 再介绍word embedding的时候,我们说过,不会直接把文本转化为向量,而是先转化为数字,再把数字转化为向量,那么这个过程该如何实现呢? 146 | 147 | 这里我们可以考虑把文本中的每个**词语和其对应的数字,使用字典保存**,同时实现方法**把句子通过字典映射为包含数字的列表**。 148 | 149 | 实现文本序列化之前,考虑以下几点: 150 | 151 | 1. 如何使用字典把词语和数字进行对应 152 | 2. 不同的词语出现的次数不尽相同,是否需要对高频或者低频词语进行过滤,以及总的词语数量是否需要进行限制 153 | 3. 得到词典之后,如何把句子转化为数字序列,如何把数字序列转化为句子 154 | 4. 不同句子长度不相同,每个batch的句子如何构造成相同的长度(可以对短句子进行填充,填充特殊字符) 155 | 5. 对于新出现的词语在词典中没有出现怎么办(可以使用特殊字符代理) 156 | 157 | 思路分析: 158 | 159 | 1. 对所有句子进行分词 160 | 2. 词语存入字典,根据次数对词语进行过滤,并统计次数 161 | 3. 实现文本转数字序列的方法 162 | 4. 实现数字序列转文本方法 163 | 164 | ```python 165 | import numpy as np 166 | 167 | class Word2Sequence(): 168 | UNK_TAG = "UNK" 169 | PAD_TAG = "PAD" 170 | 171 | UNK = 0 172 | PAD = 1 173 | 174 | def __init__(self): 175 | self.dict = { 176 | self.UNK_TAG :self.UNK, 177 | self.PAD_TAG :self.PAD 178 | } 179 | self.fited = False 180 | 181 | def to_index(self,word): 182 | """word -> index""" 183 | assert self.fited == True,"必须先进行fit操作" 184 | return self.dict.get(word,self.UNK) 185 | 186 | def to_word(self,index): 187 | """index -> word""" 188 | assert self.fited , "必须先进行fit操作" 189 | if index in self.inversed_dict: 190 | return self.inversed_dict[index] 191 | return self.UNK_TAG 192 | 193 | def __len__(self): 194 | return self(self.dict) 195 | 196 | def fit(self, sentences, min_count=1, max_count=None, max_feature=None): 197 | """ 198 | :param sentences:[[word1,word2,word3],[word1,word3,wordn..],...] 199 | :param min_count: 最小出现的次数 200 | :param max_count: 最大出现的次数 201 | :param max_feature: 总词语的最大数量 202 | :return: 203 | """ 204 | count = {} 205 | for sentence in sentences: 206 | for a in sentence: 207 | if a not in count: 208 | count[a] = 0 209 | count[a] += 1 210 | 211 | # 比最小的数量大和比最大的数量小的需要 212 | if min_count is not None: 213 | count = {k: v for k, v in count.items() if v >= min_count} 214 | if max_count is not None: 215 | count = {k: v for k, v in count.items() if v <= max_count} 216 | 217 | # 限制最大的数量 218 | if isinstance(max_feature, int): 219 | count = sorted(list(count.items()), key=lambda x: x[1]) 220 | if max_feature is not None and len(count) > max_feature: 221 | count = count[-int(max_feature):] 222 | for w, _ in count: 223 | self.dict[w] = len(self.dict) 224 | else: 225 | for w in sorted(count.keys()): 226 | self.dict[w] = len(self.dict) 227 | 228 | self.fited = True 229 | # 准备一个index->word的字典 230 | self.inversed_dict = dict(zip(self.dict.values(), self.dict.keys())) 231 | 232 | def transform(self, sentence,max_len=None): 233 | """ 234 | 实现吧句子转化为数组(向量) 235 | :param sentence: 236 | :param max_len: 237 | :return: 238 | """ 239 | assert self.fited, "必须先进行fit操作" 240 | if max_len is not None: 241 | r = [self.PAD]*max_len 242 | else: 243 | r = [self.PAD]*len(sentence) 244 | if max_len is not None and len(sentence)>max_len: 245 | sentence=sentence[:max_len] 246 | for index,word in enumerate(sentence): 247 | r[index] = self.to_index(word) 248 | return np.array(r,dtype=np.int64) 249 | 250 | def inverse_transform(self,indices): 251 | """ 252 | 实现从数组 转化为文字 253 | :param indices: [1,2,3....] 254 | :return:[word1,word2.....] 255 | """ 256 | sentence = [] 257 | for i in indices: 258 | word = self.to_word(i) 259 | sentence.append(word) 260 | return sentence 261 | 262 | if __name__ == '__main__': 263 | w2s = Word2Sequence() 264 | w2s.fit([ 265 | ["你", "好", "么"], 266 | ["你", "好", "哦"]]) 267 | 268 | print(w2s.dict) 269 | print(w2s.fited) 270 | print(w2s.transform(["你","好","嘛"])) 271 | print(w2s.transform(["你好嘛"],max_len=10)) 272 | ``` 273 | 274 | 完成了`wordsequence`之后,接下来就是保存现有样本中的数据字典,方便后续的使用。 275 | 276 | 实现对IMDB数据的处理和保存 277 | 278 | ```python 279 | #1. 对IMDB的数据记性fit操作 280 | def fit_save_word_sequence(): 281 | from wordSequence import Word2Sequence 282 | 283 | ws = Word2Sequence() 284 | train_path = [os.path.join(data_base_path,i) for i in ["train/neg","train/pos"]] 285 | total_file_path_list = [] 286 | for i in train_path: 287 | total_file_path_list.extend([os.path.join(i, j) for j in os.listdir(i)]) 288 | for cur_path in tqdm(total_file_path_list,ascii=True,desc="fitting"): 289 | ws.fit(tokenize(open(cur_path).read().strip())) 290 | ws.build_vocab() 291 | # 对wordSequesnce进行保存 292 | pickle.dump(ws,open("./model/ws.pkl","wb")) 293 | 294 | #2. 在dataset中使用wordsequence 295 | ws = pickle.load(open("./model/ws.pkl","rb")) 296 | 297 | def collate_fn(batch): 298 | MAX_LEN = 500 299 | #MAX_LEN = max([len(i) for i in texts]) #取当前batch的最大值作为batch的最大长度 300 | 301 | batch = list(zip(*batch)) 302 | labes = torch.tensor(batch[0],dtype=torch.int) 303 | 304 | texts = batch[1] 305 | #获取每个文本的长度 306 | lengths = [len(i) if len(i) 思考:前面我们自定义了MAX_LEN作为句子的最大长度,如果我们需要把每个batch中的最长的句子长度作为当前batch的最大长度,该如何实现? 340 | 341 | 342 | 343 | ## 4. 构建模型 344 | 345 | 这里我们只练习使用word embedding,所以模型只有一层,即: 346 | 347 | 1. 数据经过word embedding 348 | 2. 数据通过全连接层返回结果,计算`log_softmax` 349 | 350 | ```python 351 | import torch 352 | import torch.nn as nn 353 | import torch.nn.functional as F 354 | from torch import optim 355 | from build_dataset import get_dataloader,ws,MAX_LEN 356 | 357 | class IMDBModel(nn.Module): 358 | def __init__(self,max_len): 359 | super(IMDBModel,self).__init__() 360 | self.embedding = nn.Embedding(len(ws),300,padding_idx=ws.PAD) #[N,300] 361 | self.fc = nn.Linear(max_len*300,10) #[max_len*300,10] 362 | 363 | def forward(self, x): 364 | embed = self.embedding(x) #[batch_size,max_len,300] 365 | embed = embed.view(x.size(0),-1) 366 | out = self.fc(embed) 367 | return F.log_softmax(out,dim=-1) 368 | ``` 369 | 370 | 371 | 372 | ## 5. 模型的训练和评估 373 | 374 | 训练流程和之前相同 375 | 376 | 1. 实例化模型,损失函数,优化器 377 | 2. 遍历dataset_loader,梯度置为0,进行向前计算 378 | 3. 计算损失,反向传播优化损失,更新参数 379 | 380 | ```python 381 | train_batch_size = 128 382 | test_batch_size = 1000 383 | imdb_model = IMDBModel(MAX_LEN) 384 | optimizer = optim.Adam(imdb_model.parameters()) 385 | criterion = nn.CrossEntropyLoss() 386 | 387 | def train(epoch): 388 | mode = True 389 | imdb_model.train(mode) 390 | train_dataloader =get_dataloader(mode,train_batch_size) 391 | for idx,(target,input,input_lenght) in enumerate(train_dataloader): 392 | optimizer.zero_grad() 393 | output = imdb_model(input) 394 | loss = F.nll_loss(output,target) #traget需要是[0,9],不能是[1-10] 395 | loss.backward() 396 | optimizer.step() 397 | if idx %10 == 0: 398 | print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( 399 | epoch, idx * len(input), len(train_dataloader.dataset), 400 | 100. * idx / len(train_dataloader), loss.item())) 401 | 402 | torch.save(imdb_model.state_dict(), "model/mnist_net.pkl") 403 | torch.save(optimizer.state_dict(), 'model/mnist_optimizer.pkl') 404 | 405 | def test(): 406 | test_loss = 0 407 | correct = 0 408 | mode = False 409 | imdb_model.eval() 410 | test_dataloader = get_dataloader(mode, test_batch_size) 411 | with torch.no_grad(): 412 | for target, input, input_lenght in test_dataloader: 413 | output = imdb_model(input) 414 | test_loss += F.nll_loss(output, target,reduction="sum") 415 | pred = torch.max(output,dim=-1,keepdim=False)[-1] 416 | correct = pred.eq(target.data).sum() 417 | test_loss = test_loss/len(test_dataloader.dataset) 418 | print('\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format( 419 | test_loss, correct, len(test_dataloader.dataset), 420 | 100. * correct / len(test_dataloader.dataset))) 421 | 422 | if __name__ == '__main__': 423 | test() 424 | for i in range(3): 425 | train(i) 426 | test() 427 | 428 | ``` 429 | 430 | 这里我们仅仅使用了一层全连接层,其分类效果不会很好,这里重点是理解常见的模型流程和word embedding的使用方法 -------------------------------------------------------------------------------- /1.3 循环神经网络/1.3.3 循环神经网络.md: -------------------------------------------------------------------------------- 1 | # 循环神经网络 2 | 3 | ## 目标 4 | 5 | 1. 能够说出循环神经网络的概念和作用 6 | 2. 能够说出循环神经网络的类型和应用场景 7 | 3. 能够说出LSTM的作用和原理 8 | 4. 能够说出GRU的作用和原理 9 | 10 | 11 | 12 | ## 1. 循环神经网络的介绍 13 | 14 | > 为什么有了神经网络还需要有循环神经网络? 15 | 16 | 在普通的神经网络中,信息的传递是单向的,这种限制虽然使得网络变得更容易学习,但在一定程度上也减弱了神经网络模型的能力。特别是在很多现实任务中,网络的输出不仅和当前时刻的输入相关,也和其过去一段时间的输出相关。此外,普通网络难以处理时序数据,比如视频、语音、文本等,时序数据的长度一般是不固定的,而前馈神经网络要求输入和输出的维数都是固定的,不能任意改变。因此,当处理这一类和时序相关的问题时,就需要一种能力更强的模型。 17 | 18 | 循环神经网络(Recurrent Neural Network,RNN)是一类具有短期记忆能力的神经网络。在循环神经网络中,神经元不但可以接受其它神经元的信息,也可以接受自身的信息,形成具有环路的网络结构。换句话说:神经元的输出可以在下一个时间步直接作用到自身( 19 | 20 | 入) 21 | 22 | ![](../images/1.3/RNN图.png) 23 | 24 | 通过简化图,我们看到RNN比传统的神经网络多了一个循环圈,这个循环表示的就是在下一个时间步(**Time Step**)上会返回作为输入的一部分,我们把RNN在时间点上展开,得到的图形如下: 25 | 26 | ![](../images/1.3/RNN展开.png) 27 | 28 | 或者是: 29 | 30 | ![](../images/1.3/基础的RNN展开图.png) 31 | 32 | 在不同的时间步,RNN的输入都将与之前的时间状态有关,$t_n$时刻网络的输出结果是该时刻的输入和所有历史共同作用的结果,这就达到了对时间序列建模的目的。 33 | 34 | RNN的不同表示和功能可以通过下图看出: 35 | 36 | ![](../images/1.3/RNN功能.png) 37 | 38 | - 图1:固定长度的输入和输出 (e.g. 图像分类) 39 | - 图2:序列输出 (e.g.图像转文字) 40 | - 图3:数列输入 (e.g. 文本分类) 41 | - 图4:异步的序列输入和输出(e.g.文本翻译). 42 | - 图5:同步的序列输入和输出 (e.g. 根据视频的每一帧来对视频进行分类) 43 | 44 | 45 | 46 | ## 2. LSTM和GRU 47 | 48 | ### 2.1 LSTM的基础介绍 49 | 50 | 假如现在有这样一个需求,根据现有文本预测下一个词语,比如`天上的云朵漂浮在__`,通过间隔不远的位置就可以预测出来词语是`天上`,但是对于其他一些句子,可能需要被预测的词语在前100个词语之前,那么此时由于间隔非常大,随着间隔的增加可能会导致真实的预测值对结果的影响变的非常小,而无法非常好的进行预测(RNN中的长期依赖问题(long-Term Dependencies)) 51 | 52 | 那么为了解决这个问题需要**LSTM**(**Long Short-Term Memory网络**) 53 | 54 | LSTM是一种RNN特殊的类型,可以学习长期依赖信息。在很多问题上,LSTM都取得相当巨大的成功,并得到了广泛的应用。 55 | 56 | 一个LSMT的单元就是下图中的一个绿色方框中的内容: 57 | 58 | ![](../images/1.3/LSTM1.jpg) 59 | 60 | 61 | 62 | 其中$\sigma$表示sigmod函数,其他符号的含义: 63 | 64 | ![](../images/1.3/LSTM2.jpg) 65 | 66 | 67 | 68 | ### 2.2 LSTM的核心 69 | 70 | ![](../images/1.3/LSTM3.png) 71 | 72 | LSTM的核心在于单元(细胞)中的状态,也就是上图中最上面的那根线。 73 | 74 | 但是如果只有上面那一条线,那么没有办法实现信息的增加或者删除,所以在LSTM是通过一个叫做`门`的结构实现,门可以选择让信息通过或者不通过。 75 | 76 | 这个门主要是通过sigmoid和点乘(`pointwise multiplication`)实现的 77 | 78 | ![](../images/1.3/LSTM4.png) 79 | 80 | 81 | 82 | 我们都知道,$sigmoid$的取值范围是在(0,1)之间,如果接近0表示不让任何信息通过,如果接近1表示所有的信息都会通过 83 | 84 | 85 | 86 | ### 2.3 逐步理解LSTM 87 | 88 | #### 2.3.1 遗忘门 89 | 90 | 遗忘门通过sigmoid函数来决定哪些信息会被遗忘 91 | 92 | 在下图就是$h_{t-1}和x_t$进行合并(concat)之后乘上权重和偏置,通过sigmoid函数,输出0-1之间的一个值,这个值会和前一次的细胞状态($C_{t-1}​$)进行点乘,从而决定遗忘或者保留 93 | 94 | ![](../images/1.3/易王门.png) 95 | 96 | 97 | 98 | #### 2.3.2 输入门 99 | 100 | ![](../images/1.3/输入门.png) 101 | 102 | 下一步就是决定哪些新的信息会被保留,这个过程有两步: 103 | 104 | 1. 一个被称为`输入门`的sigmoid 层决定哪些信息会被更新 105 | 2. `tanh`会创造一个新的候选向量$\widetilde{C}_{t}$,后续可能会被添加到细胞状态中 106 | 107 | 例如: 108 | 109 | `我昨天吃了苹果,今天我想吃菠萝`,在这个句子中,通过遗忘门可以遗忘`苹果`,同时更新新的主语为`菠萝` 110 | 111 | 112 | 113 | 现在就可以更新旧的细胞状态$C_{t-1}$为新的$C_{ t }​$ 了。 114 | 115 | 更新的构成很简单就是: 116 | 117 | 1. 旧的细胞状态和遗忘门的结果相乘 118 | 2. 然后加上 输入门和tanh相乘的结果 119 | 120 | ![](../images/1.3/LSTM-update.png) 121 | 122 | 123 | 124 | #### 2.3.3 输出门 125 | 126 | 最后,我们需要决定什么信息会被输出,也是一样这个输出经过变换之后会通过sigmoid函数的结果来决定那些细胞状态会被输出。 127 | 128 | ![](../images/1.3/输出门.png) 129 | 130 | 步骤如下: 131 | 132 | 1. 前一次的输出和当前时间步的输入的组合结果通过sigmoid函数进行处理得到$O_t$ 133 | 2. 更新后的细胞状态$C_t$会经过tanh层的处理,把数据转化到(-1,1)的区间 134 | 3. tanh处理后的结果和$O_t$进行相乘,把结果输出同时传到下一个LSTM的单元 135 | 136 | 137 | 138 | ### 2.4 GRU,LSTM的变形 139 | 140 | GRU(Gated Recurrent Unit),是一种LSTM的变形版本, 它将遗忘和输入门组合成一个“更新门”。它还合并了单元状态和隐藏状态,并进行了一些其他更改,由于他的模型比标准LSTM模型简单,所以越来越受欢迎。 141 | 142 | ![](../images/1.3/GRU.png) 143 | 144 | 145 | 146 | LSTM内容参考地址:https://colah.github.io/posts/2015-08-Understanding-LSTMs/ 147 | 148 | ## 3. 双向LSTM 149 | 150 | 单向的 RNN,是根据前面的信息推出后面的,但有时候只看前面的词是不够的, 可能需要预测的词语和后面的内容也相关,那么此时需要一种机制,能够让模型不仅能够从前往后的具有记忆,还需要从后往前需要记忆。此时双向LSTM就可以帮助我们解决这个问题 151 | 152 | ![](../images/1.3/bidir_lstm.png) 153 | 154 | 由于是双向LSTM,所以每个方向的LSTM都会有一个输出,最终的输出会有2部分,所以往往需要concat的操作 -------------------------------------------------------------------------------- /1.3 循环神经网络/1.3.4 循环神经网络实现情感分类.md: -------------------------------------------------------------------------------- 1 | # 循环神经网络实现文本情感分类 2 | 3 | ## 目标 4 | 5 | 1. 知道LSTM和GRU的使用方法及输入输出的格式 6 | 2. 能够应用LSTM和GRU实现文本情感分类 7 | 8 | 9 | 10 | ## 1. Pytorch中LSTM和GRU模块使用 11 | 12 | ### 1.1 LSTM介绍 13 | 14 | LSTM和GRU都是由`torch.nn`提供 15 | 16 | 通过观察文档,可知LSMT的参数, 17 | 18 | `torch.nn.LSTM(input_size,hidden_size,num_layers,batch_first,dropout,bidirectional)` 19 | 20 | 1. `input_size `:输入数据的形状,即embedding_dim 21 | 2. `hidden_size`:隐藏层神经元的数量,即每一层有多少个LSTM单元 22 | 3. `num_layer` :即RNN的中LSTM单元的层数 23 | 4. `batch_first`:默认值为False,输入的数据需要`[seq_len,batch,feature]`,如果为True,则为`[batch,seq_len,feature]` 24 | 5. `dropout`:dropout的比例,默认值为0。dropout是一种训练过程中让部分参数随机失活的一种方式,能够提高训练速度,同时能够解决过拟合的问题。这里是在LSTM的最后一层,对每个输出进行dropout 25 | 6. `bidirectional`:是否使用双向LSTM,默认是False 26 | 27 | 实例化LSTM对象之后,**不仅需要传入数据,还需要前一次的h_0(前一次的隐藏状态)和c_0(前一次memory)** 28 | 29 | 即:`lstm(input,(h_0,c_0))` 30 | 31 | LSTM的默认输出为`output, (h_n, c_n)` 32 | 33 | 1. `output`:`(seq_len, batch, num_directions * hidden_size)`--->batch_first=False 34 | 2. `h_n`:`(num_layers * num_directions, batch, hidden_size)` 35 | 3. `c_n`: `(num_layers * num_directions, batch, hidden_size)` 36 | 37 | ## 1.2 LSTM使用示例 38 | 39 | 假设数据输入为 input ,形状是`[10,20]`,假设embedding的形状是`[100,30]` 40 | 41 | 则LSTM使用示例如下: 42 | 43 | ```python 44 | batch_size =10 45 | seq_len = 20 46 | embedding_dim = 30 47 | word_vocab = 100 48 | hidden_size = 18 49 | num_layer = 2 50 | 51 | #准备输入数据 52 | input = torch.randint(low=0,high=100,size=(batch_size,seq_len)) 53 | #准备embedding 54 | embedding = torch.nn.Embedding(word_vocab,embedding_dim) 55 | lstm = torch.nn.LSTM(embedding_dim,hidden_size,num_layer) 56 | 57 | #进行mebed操作 58 | embed = embedding(input) #[10,20,30] 59 | 60 | #转化数据为batch_first=False 61 | embed = embed.permute(1,0,2) #[20,10,30] 62 | 63 | #初始化状态, 如果不初始化,torch默认初始值为全0 64 | h_0 = torch.rand(num_layer,batch_size,hidden_size) 65 | c_0 = torch.rand(num_layer,batch_size,hidden_size) 66 | output,(h_1,c_1) = lstm(embed,(h_0,c_0)) 67 | #output [20,10,1*18] 68 | #h_1 [2,10,18] 69 | #c_1 [2,10,18] 70 | ``` 71 | 72 | 输出如下 73 | 74 | ```python 75 | In [122]: output.size() 76 | Out[122]: torch.Size([20, 10, 18]) 77 | 78 | In [123]: h_1.size() 79 | Out[123]: torch.Size([2, 10, 18]) 80 | 81 | In [124]: c_1.size() 82 | Out[124]: torch.Size([2, 10, 18]) 83 | ``` 84 | 85 | 通过前面的学习,我们知道,最后一次的h_1应该和output的最后一个time step的输出是一样的 86 | 87 | 通过下面的代码,我们来验证一下: 88 | 89 | ```python 90 | In [179]: a = output[-1,:,:] 91 | 92 | In [180]: a.size() 93 | Out[180]: torch.Size([10, 18]) 94 | 95 | In [183]: b.size() 96 | Out[183]: torch.Size([10, 18]) 97 | In [184]: a == b 98 | Out[184]: 99 | tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 100 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 101 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 102 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 103 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 104 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 105 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 106 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 107 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 108 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], 109 | dtype=torch.uint8) 110 | ``` 111 | 112 | ### 1.3 GRU的使用示例 113 | 114 | GRU模块`torch.nn.GRU`,和LSTM的参数相同,含义相同,具体可参考文档 115 | 116 | 但是输入只剩下`gru(input,h_0)`,输出为`output, h_n` 117 | 118 | 其形状为: 119 | 120 | 1. `output`:`(seq_len, batch, num_directions * hidden_size)` 121 | 2. `h_n`:`(num_layers * num_directions, batch, hidden_size)` 122 | 123 | 大家可以使用上述代码,观察GRU的输出形式 124 | 125 | ### 1.4 双向LSTM 126 | 127 | 如果需要使用双向LSTM,则在实例化LSTM的过程中,需要把LSTM中的bidriectional设置为True,同时h_0和c_0使用num_layer*2 128 | 129 | 观察效果,输出为 130 | 131 | ```python 132 | batch_size =10 #句子的数量 133 | seq_len = 20 #每个句子的长度 134 | embedding_dim = 30 #每个词语使用多长的向量表示 135 | word_vocab = 100 #词典中词语的总数 136 | hidden_size = 18 #隐层中lstm的个数 137 | num_layer = 2 #多少个隐藏层 138 | 139 | input = torch.randint(low=0,high=100,size=(batch_size,seq_len)) 140 | embedding = torch.nn.Embedding(word_vocab,embedding_dim) 141 | lstm = torch.nn.LSTM(embedding_dim,hidden_size,num_layer,bidirectional=True) 142 | 143 | embed = embedding(input) #[10,20,30] 144 | 145 | #转化数据为batch_first=False 146 | embed = embed.permute(1,0,2) #[20,10,30] 147 | h_0 = torch.rand(num_layer*2,batch_size,hidden_size) 148 | c_0 = torch.rand(num_layer*2,batch_size,hidden_size) 149 | output,(h_1,c_1) = lstm(embed,(h_0,c_0)) 150 | 151 | In [135]: output.size() 152 | Out[135]: torch.Size([20, 10, 36]) 153 | 154 | In [136]: h_1.size() 155 | Out[136]: torch.Size([4, 10, 18]) 156 | 157 | In [137]: c_1.size() 158 | Out[137]: torch.Size([4, 10, 18]) 159 | ``` 160 | 161 | 在单向LSTM中,最后一个time step的输出的前hidden_size个和最后一层隐藏状态h_1的输出相同,那么双向LSTM呢? 162 | 163 | 双向LSTM中: 164 | 165 | **output:按照正反计算的结果顺序在第2个维度进行拼接,正向第一个拼接反向的最后一个输出** 166 | 167 | **hidden state:按照得到的结果在第0个维度进行拼接,正向第一个之后接着是反向第一个** 168 | 169 | 1. 前向的LSTM中,最后一个time step的输出的前hidden_size个和最后一层向前传播h_1的输出相同 170 | 171 | - 示例: 172 | 173 | - ```python 174 | #-1是前向LSTM的最后一个,前18是前hidden_size个 175 | In [188]: a = output[-1,:,:18] #前项LSTM中最后一个time step的output 176 | 177 | In [189]: b = h_1[-2,:,:] #倒数第二个为前向 178 | 179 | In [190]: a.size() 180 | Out[190]: torch.Size([10, 18]) 181 | 182 | In [191]: b.size() 183 | Out[191]: torch.Size([10, 18]) 184 | 185 | In [192]: a == b 186 | Out[192]: 187 | tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 188 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 189 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 190 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 191 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 192 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 193 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 194 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 195 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 196 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], 197 | dtype=torch.uint8) 198 | ``` 199 | 200 | 201 | 202 | 2. 后向LSTM中,最后一个time step的输出的后hidden_size个和最后一层后向传播的h_1的输出相同 203 | 204 | - 示例 205 | 206 | - ```python 207 | #0 是反向LSTM的最后一个,后18是后hidden_size个 208 | In [196]: c = output[0,:,18:] #后向LSTM中的最后一个输出 209 | 210 | In [197]: d = h_1[-1,:,:] #后向LSTM中的最后一个隐藏层状态 211 | 212 | In [198]: c == d 213 | Out[198]: 214 | tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 215 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 216 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 217 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 218 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 219 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 220 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 221 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 222 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 223 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], 224 | dtype=torch.uint8) 225 | ``` 226 | 227 | 228 | 229 | ### 1.4 LSTM和GRU的使用注意点 230 | 231 | 1. 第一次调用之前,需要初始化隐藏状态,如果不初始化,默认创建全为0的隐藏状态 232 | 2. 往往会使用LSTM or GRU 的输出的最后一维的结果,来代表LSTM、GRU对文本处理的结果,其形状为`[batch, num_directions*hidden_size]`。 233 | 1. 并不是所有模型都会使用最后一维的结果 234 | 2. 如果实例化LSTM的过程中,batch_first=False,则`output[-1] or output[-1,:,:]`可以获取最后一维 235 | 3. 如果实例化LSTM的过程中,batch_first=True,则`output[:,-1,:]`可以获取最后一维 236 | 3. 如果结果是`(seq_len, batch_size, num_directions * hidden_size)`,需要把它转化为`(batch_size,seq_len, num_directions * hidden_size)`的形状,不能够不是view等变形的方法,需要使用`output.permute(1,0,2)`,即交换0和1轴,实现上述效果 237 | 4. 使用双向LSTM的时候,往往会分别使用每个方向最后一次的output,作为当前数据经过双向LSTM的结果 238 | - 即:`torch.cat([h_1[-2,:,:],h_1[-1,:,:]],dim=-1)` 239 | - 最后的表示的size是`[batch_size,hidden_size*2]` 240 | 5. 上述内容在GRU中同理 241 | 242 | 243 | 244 | ## 2. 使用LSTM完成文本情感分类 245 | 246 | 在前面,我们使用了word embedding去实现了toy级别的文本情感分类,那么现在我们在这个模型中添加上LSTM层,观察分类效果。 247 | 248 | 为了达到更好的效果,对之前的模型做如下修改 249 | 250 | 1. MAX_LEN = 200 251 | 2. 构建dataset的过程,把数据转化为2分类的问题,pos为1,neg为0,否则25000个样本完成10个类别的划分数据量是不够的 252 | 3. 在实例化LSTM的时候,使用dropout=0.5,在model.eval()的过程中,dropout自动会为0 253 | 254 | ### 2.1 修改模型 255 | 256 | ```python 257 | class IMDBLstmmodel(nn.Module): 258 | def __init__(self): 259 | super(IMDBLstmmodel,self).__init__() 260 | self.hidden_size = 64 261 | self.embedding_dim = 200 262 | self.num_layer = 2 263 | self.bidriectional = True 264 | self.bi_num = 2 if self.bidriectional else 1 265 | self.dropout = 0.5 266 | #以上部分为超参数,可以自行修改 267 | 268 | self.embedding = nn.Embedding(len(ws),self.embedding_dim,padding_idx=ws.PAD) #[N,300] 269 | self.lstm = nn.LSTM(self.embedding_dim,self.hidden_size,self.num_layer,bidirectional=True,dropout=self.dropout) 270 | #使用两个全连接层,中间使用relu激活函数 271 | self.fc = nn.Linear(self.hidden_size*self.bi_num,20) 272 | self.fc2 = nn.Linear(20,2) 273 | 274 | 275 | def forward(self, x): 276 | x = self.embedding(x) 277 | x = x.permute(1,0,2) #进行轴交换 278 | h_0,c_0 = self.init_hidden_state(x.size(1)) 279 | _,(h_n,c_n) = self.lstm(x,(h_0,c_0)) 280 | 281 | #只要最后一个lstm单元处理的结果,这里多去的hidden state 282 | out = torch.cat([h_n[-2, :, :], h_n[-1, :, :]], dim=-1) 283 | out = self.fc(out) 284 | out = F.relu(out) 285 | out = self.fc2(out) 286 | return F.log_softmax(out,dim=-1) 287 | 288 | def init_hidden_state(self,batch_size): 289 | h_0 = torch.rand(self.num_layer * self.bi_num, batch_size, self.hidden_size).to(device) 290 | c_0 = torch.rand(self.num_layer * self.bi_num, batch_size, self.hidden_size).to(device) 291 | return h_0,c_0 292 | ``` 293 | 294 | 295 | 296 | ### 2.2 完成训练和测试代码 297 | 298 | 为了提高程序的运行速度,可以考虑把模型放在gup上运行,那么此时需要处理一下几点: 299 | 300 | 1. `device = torch.device("cuda" if torch.cuda.is_available() else "cpu")` 301 | 2. `model.to(device)` 302 | 3. 除了上述修改外,涉及计算的所有tensor都需要转化为CUDA的tensor 303 | 1. 初始化的`h_0,c_0` 304 | 2. 训练集和测试集的`input,traget` 305 | 4. 在最后可以通过`tensor.cpu()`转化为torch的普通tensor 306 | 307 | ```python 308 | train_batch_size = 64 309 | test_batch_size = 5000 310 | # imdb_model = IMDBModel(MAX_LEN) #基础model 311 | imdb_model = IMDBLstmmodel().to(device) #在gpu上运行,提高运行速度 312 | # imdb_model.load_state_dict(torch.load("model/mnist_net.pkl")) 313 | optimizer = optim.Adam(imdb_model.parameters()) 314 | criterion = nn.CrossEntropyLoss() 315 | 316 | def train(epoch): 317 | mode = True 318 | imdb_model.train(mode) 319 | train_dataloader =get_dataloader(mode,train_batch_size) 320 | for idx,(target,input,input_lenght) in enumerate(train_dataloader): 321 | target = target.to(device) 322 | input = input.to(device) 323 | optimizer.zero_grad() 324 | output = imdb_model(input) 325 | loss = F.nll_loss(output,target) #traget需要是[0,9],不能是[1-10] 326 | loss.backward() 327 | optimizer.step() 328 | if idx %10 == 0: 329 | pred = torch.max(output, dim=-1, keepdim=False)[-1] 330 | acc = pred.eq(target.data).cpu().numpy().mean()*100. 331 | 332 | print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}\t ACC: {:.6f}'.format(epoch, idx * len(input), len(train_dataloader.dataset), 333 | 100. * idx / len(train_dataloader), loss.item(),acc)) 334 | 335 | torch.save(imdb_model.state_dict(), "model/mnist_net.pkl") 336 | torch.save(optimizer.state_dict(), 'model/mnist_optimizer.pkl') 337 | 338 | def test(): 339 | mode = False 340 | imdb_model.eval() 341 | test_dataloader = get_dataloader(mode, test_batch_size) 342 | with torch.no_grad(): 343 | for idx,(target, input, input_lenght) in enumerate(test_dataloader): 344 | target = target.to(device) 345 | input = input.to(device) 346 | output = imdb_model(input) 347 | test_loss = F.nll_loss(output, target,reduction="mean") 348 | pred = torch.max(output,dim=-1,keepdim=False)[-1] 349 | correct = pred.eq(target.data).sum() 350 | acc = 100. * pred.eq(target.data).cpu().numpy().mean() 351 | print('idx: {} Test set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format(idx,test_loss, correct, target.size(0),acc)) 352 | 353 | if __name__ == "__main__": 354 | test() 355 | for i in range(10): 356 | train(i) 357 | test() 358 | 359 | ``` 360 | 361 | ### 2.3 模型训练的最终输出 362 | 363 | ``` 364 | ... 365 | Train Epoch: 9 [20480/25000 (82%)] Loss: 0.017165 ACC: 100.000000 366 | Train Epoch: 9 [21120/25000 (84%)] Loss: 0.021572 ACC: 98.437500 367 | Train Epoch: 9 [21760/25000 (87%)] Loss: 0.058546 ACC: 98.437500 368 | Train Epoch: 9 [22400/25000 (90%)] Loss: 0.045248 ACC: 98.437500 369 | Train Epoch: 9 [23040/25000 (92%)] Loss: 0.027622 ACC: 98.437500 370 | Train Epoch: 9 [23680/25000 (95%)] Loss: 0.097722 ACC: 95.312500 371 | Train Epoch: 9 [24320/25000 (97%)] Loss: 0.026713 ACC: 98.437500 372 | Train Epoch: 9 [15600/25000 (100%)] Loss: 0.006082 ACC: 100.000000 373 | idx: 0 Test set: Avg. loss: 0.8794, Accuracy: 4053/5000 (81.06%) 374 | idx: 1 Test set: Avg. loss: 0.8791, Accuracy: 4018/5000 (80.36%) 375 | idx: 2 Test set: Avg. loss: 0.8250, Accuracy: 4087/5000 (81.74%) 376 | idx: 3 Test set: Avg. loss: 0.8380, Accuracy: 4074/5000 (81.48%) 377 | idx: 4 Test set: Avg. loss: 0.8696, Accuracy: 4027/5000 (80.54%) 378 | ``` 379 | 380 | 可以看到模型的测试准确率稳定在81%左右。 381 | 382 | 大家可以把上述代码改为GRU,或者多层LSTM继续尝试,观察效果 -------------------------------------------------------------------------------- /1.3 循环神经网络/1.3.5 神经网络中的序列化容器.md: -------------------------------------------------------------------------------- 1 | # Pytorch中的序列化容器 2 | 3 | ## 目标 4 | 5 | 1. 知道梯度消失和梯度爆炸的原理和解决方法 6 | 2. 能够使用`nn.Sequential`完成模型的搭建 7 | 3. 知道`nn.BatchNorm1d`的使用方法 8 | 4. 知道`nn.Dropout`的使用方法 9 | 10 | ## 1. 梯度消失和梯度爆炸 11 | 12 | 在使用pytorch中的序列化 容器之前,我们先来了解一下常见的梯度消失和梯度爆炸的问题 13 | 14 | ### 1.1 梯度消失 15 | 16 | 假设我们有四层极简神经网络:每层只有一个神经元 17 | 18 | ![](../images/1.3/梯度消失.png) 19 | 20 | $获取w1的梯度有:▽w1 = x1*f(a1)’*w2*f(b1)’*w3*▽out$ 21 | 22 | 假设我们使用sigmoid激活函数,即f为sigmoid函数,sigmoid的导数如下图 23 | 24 | ![](../images/1.3/sigmoid导数.png) 25 | 26 | 假设每层都取得sigmoid导函数的最大值1/4,那么在反向传播时,$X1=0.5,w1=w2=w3=0.5​$ 27 | 28 | $\nabla w1< \frac{1}{2} * \frac{1}{4}* \frac{1}{2}* \frac{1}{4}*\frac{1}{2}*\nabla out = \frac{1}{2^7} \nabla out$ 29 | 30 | 当权重初始过小或使用`易饱和神经元(sigmoid,tanh,) sigmoid在y=0,1处梯度接近0,而无法更新参数`,时神经网络在反向传播时也会呈现指数倍缩小,产生“消失”现象。 31 | 32 | 33 | 34 | ### 1.2 梯度爆炸 35 | 36 | 假设$X2=2,w1=w2=w3=2​$ 37 | 38 | $\nabla w1 = f'{a}*2*f‘{a}*x2\nabla out = 2^3f'(a)^2 \nabla out $ 39 | 40 | 当权重初始过大时,梯度神经网络在反向传播时也会呈现指数倍放大,产生“爆炸”现象。 41 | 42 | 43 | 44 | ### 1.3 解决梯度消失或者梯度爆炸的经验 45 | 46 | 1. **替换易训练神经元** 47 | 48 | ![](../images/1.3/替换激活函数.png) 49 | 50 | 2. **改进梯度优化算法:**使用adam等算法 51 | 3. **使用batch normalization** 52 | 53 | 54 | 55 | ## 2. `nn.Sequential` 56 | 57 | `nn.Sequential`是一个有序的容器,其中传入的是构造器类(各种用来处理input的类),最终input会被Sequential中的构造器类依次执行 58 | 59 | 例如: 60 | 61 | ```python 62 | layer = nn.Sequential( 63 | nn.Linear(input_dim, n_hidden_1), 64 | nn.ReLU(True), #inplace=False 是否对输入进行就地修改,默认为False 65 | nn.Linear(n_hidden_1, n_hidden_2), 66 | nn.ReLU(True), 67 | nn.Linear(n_hidden_2, output_dim) # 最后一层不需要添加激活函数 68 | ) 69 | ``` 70 | 71 | 在上述就够中,可以直接调用layer(x),得到输出 72 | 73 | x的被执行顺序就是Sequential中定义的顺序: 74 | 75 | 1. 被隐层1执行,形状变为[batch_size,n_hidden_1] 76 | 2. 被relu执行,形状不变 77 | 3. 被隐层2执行,形状变为[batch_size,n_hidden_2] 78 | 4. 被relu执行,形状不变 79 | 5. 被最后一层执行,形状变为[batch_size,output_dim] 80 | 81 | 82 | 83 | ## 3. `nn.BatchNorm1d` 84 | 85 | `batch normalization` 翻译成中文就是批规范化,即在每个batch训练的过程中,对参数进行归一化的处理,从而达到加快训练速度的效果。 86 | 87 | 以sigmoid激活函数为例,他在反向传播的过程中,在值为0,1的时候,梯度接近0,导致参数被更新的幅度很小,训练速度慢。但是如果对数据进行归一化之后,就会尽可能的把数据拉倒[0-1]的范围,从而让参数更新的幅度变大,提高训练的速度。 88 | 89 | batchNorm一般会放到激活函数之后,即对输入进行激活处理之后再进入batchNorm 90 | 91 | ```python 92 | layer = nn.Sequential( 93 | nn.Linear(input_dim, n_hidden_1), 94 | 95 | nn.ReLU(True), 96 | nn.BatchNorm1d(n_hidden_1) 97 | 98 | nn.Linear(n_hidden_1, n_hidden_2), 99 | nn.ReLU(True), 100 | nn.BatchNorm1d(n_hidden_2) 101 | 102 | nn.Linear(n_hidden_2, output_dim) 103 | ) 104 | ``` 105 | 106 | ## 4. `nn.Dropout` 107 | 108 | dropout在前面已经介绍过,可以理解为对参数的随机失活 109 | 110 | 1. 增加模型的稳健性 111 | 2. 可以解决过拟合的问题(增加模型的泛化能力) 112 | 3. 可以理解为训练后的模型是多个模型的组合之后的结果,类似随机森林。 113 | 114 | ```python 115 | layer = nn.Sequential( 116 | nn.Linear(input_dim, n_hidden_1), 117 | nn.ReLU(True), 118 | nn.BatchNorm1d(n_hidden_1) 119 | nn.Dropout(0.3) #0.3 为dropout的比例,默认值为0.5 120 | 121 | nn.Linear(n_hidden_1, n_hidden_2), 122 | nn.ReLU(True), 123 | nn.BatchNorm1d(n_hidden_2) 124 | nn.Dropout(0.3) 125 | 126 | nn.Linear(n_hidden_2, output_dim) 127 | ) 128 | ``` 129 | 130 | -------------------------------------------------------------------------------- /1.3 循环神经网络/README.md: -------------------------------------------------------------------------------- 1 | # 1.3 循环神经网络 2 | 3 | -------------------------------------------------------------------------------- /2.1 项目准备/2.1.1 走进聊天机器人.md: -------------------------------------------------------------------------------- 1 | # 走进聊天机器人 2 | 3 | ## 学习目标 4 | 5 | 1. 知道常见的bot的分类 6 | 2. 知道企业中常见的流程和方法 7 | 8 | 9 | 10 | ## 1. 目前企业中的常见的聊天机器人 11 | 12 | 1. QA BOT(问答机器人):回答问题 13 | 1. 代表 :智能客服、 14 | 2. 比如:提问和回答 15 | 2. TASK BOT (任务机器人):帮助人们做事情 16 | 1. 代表:siri 17 | 2. 比如:设置明天早上9点的闹钟 18 | 3. CHAT BOT(聊天机器人):通用、开放聊天 19 | 1. 代表:微软小冰 20 | 21 | 22 | 23 | ### 2. 常见的聊天机器人怎么实现的 24 | 25 | ##### 2.1 问答机器人的常见实现手段 26 | 27 | 1. 信息检索、搜索 (简单,效果一般,对数据问答对的要求高) 28 | 29 | 关键词:tfidf、SVM、朴素贝叶斯、RNN、CNN 30 | 31 | 2. 知识图谱(相对复杂,效果好,很多论文) 32 | 33 | 在图形数据库中存储知识和知识间的关系、把问答转化为查询语句、能够实现推理 34 | 35 | ##### 2.2 任务机器人的常见实现思路 36 | 37 | 1. 语音转文字 38 | 2. 意图识别、领域识别、文本分类 39 | 3. 槽位填充:比如买机票的机器人 使用命令体识别填充 `从{位置}到{位置}的票`2个位置的 40 | 4. 回话管理、回话策略 41 | 5. 自然语言生成 42 | 6. 文本转语音 43 | 44 | ##### 2.3 闲聊机器人的常见实现思路 45 | 46 | 1. 信息检索(简单、能够回答的话术有限) 47 | 2. seq2seq 和变种(答案覆盖率高,但是不能保证答案的通顺等) 48 | 49 | 50 | 51 | ## 3. 企业中的聊天机器人是如何实现的 52 | 53 | ### 3.1 阿里小蜜-电商智能助理是如何实现的 54 | 55 | 参考地址:`https://juejin.im/entry/59e96f946fb9a04510499c7f` 56 | 57 | 58 | 59 | ##### 3.1.1 主要交互过程 60 | 61 | ![img](../images/2.1/小蜜的交互过程.png) 62 | 63 | 从图可以看出: 64 | 65 | 1. 输入:语音转化为文本,进行理解之后根据上下文得到语义的表示 66 | 2. 输出:根据语义的表是和生成方法得到文本,再把文本转化为语音输出 67 | 68 | ##### 3.1.2 技术架构 69 | 70 | ![](../images/2.1/小蜜的架构.png) 71 | 72 | 可以看出其流程为: 73 | 74 | 1. 判断用户意图 75 | 2. 如果意图为面向目标:可能是问答型或者是任务型 76 | 3. 如果非面向目标:可能是语聊型 77 | 78 | ##### 3.1.3 检索模型流程(小蜜还用了其他的模型,这里以此为例) 79 | 80 | ![](../images/2.1/小蜜的检索流程.png) 81 | 82 | 通过上图可知,小蜜的检索式回答的流程大致为: 83 | 84 | 1. 对问题进行处理 85 | 2. 根据问题进行召回,使用了提前准备的结构化的语料和训练的模型 86 | 3. 对召回的结果进行组长和日志记录 87 | 4. 对召回的结果进行相似度计算,情感分析和属性识别 88 | 5. 返回组装的结果 89 | 90 | ### 3.2 58同城智能客服帮帮如何实现的 91 | 92 | 参考地址:`http://www.6aiq.com/article/1536149308075?p=1&m=0` 93 | 94 | #### 3.2.1 58客服体系 95 | 96 | ![](http://img.6aiq.com/e/f86c10dc05ec49d29510f94ada96251c.webp?imageView2/2/w/768/format/jpg/interlace/0/q) 97 | 98 | 58的客服主要用户为公司端和个人端,智能客服主要实现自动回答,如果回答不好会转到人工客服,其中自动回答需要覆盖的问题包括:业务咨询、投诉建议等 99 | 100 | 101 | 102 | #### 3.2.2 58智能客服整体架构 103 | 104 | ![](http://img.6aiq.com/e/0ceaeb9e8ff4455d99a46638c181aa1b.webp?imageView2/2/w/768/format/jpg/interlace/0/q) 105 | 106 | 整体来看,58的客服架构分为三个部分 107 | 108 | 1. 基础服务,实现基础的NLP的功能和意图识别 109 | 2. 应用对话部分实现不同意图的模型,同时包括编辑运营等内容 110 | 3. 提供对外的接口 111 | 112 | #### 3.2.3 业务咨询服务流程 113 | 114 | ##### 大致流程 115 | 116 | ![](http://img.6aiq.com/e/c24373f56b114a1aaf5d7cbb98c2f4b2.webp?imageView2/2/w/768/format/jpg/interlace/0/q) 117 | 118 | KB-bot的流程大致为: 119 | 120 | 1. 对问题进行基础处理 121 | 2. 对答案通过tfidf等方法进行召回 122 | 3. 对答案通过规则、深度神经网络等方法进行重排序 123 | 4. 返回答案排序列表 124 | 125 | ##### 使用融合的模型 126 | 127 | ![](http://img.6aiq.com/e/22289626302e48808feff6b8ca7b65a3.webp?imageView2/2/w/768/format/jpg/interlace/0/q) 128 | 129 | 在问答模型的深度网络模型中使用了多套模型进行融合来获取结果 130 | 131 | 1. 在模型层应用了 FastText、TextCNN 和 Bi-LSTM 等模型 132 | 2. 在特征层尝试使用了单字、词、词性、词语属性等多种特征 133 | 134 | 通过以上两个模型来组合获取相似的问题,返回相似问题ID对应的答案 135 | 136 | 137 | 138 | #### 3.2.4 58的闲聊机器人 139 | 140 | ![](http://img.6aiq.com/e/b332e68b8acf4a5684fa73abaecaa252.webp?imageView2/2/w/768/format/jpg/interlace/0/q) 141 | 142 | 58同城的闲聊机器人使用三种方法包括: 143 | 144 | 1. 基于模板匹配的方法 145 | 2. 基于搜索的方式获取(上上图) 146 | 3. 使用seq2seq的神经网络来实现 147 | 148 | #### 3.2.5 解决不了转人工服务 149 | 150 | ​ 智能客服解决不了的可以使用人工客服来实现 151 | 152 | ![](http://img.6aiq.com/e/02c5f53a7e924c8f81822d68e538914e.webp?imageView2/2/w/768/format/jpg/interlace/0/q) 153 | 154 | -------------------------------------------------------------------------------- /2.1 项目准备/2.1.2 需求分析和流程介绍.md: -------------------------------------------------------------------------------- 1 | # 需求分析和流程介绍 2 | 3 | ## 目标 4 | 5 | 1. 能够说出实现聊天机器人的需求 6 | 2. 能够说出实现聊天机器人的流程 7 | 8 | 9 | 10 | ## 1. 需求分析 11 | 12 | 在黑马头条的小智同学板块实现聊天机器人,能够起到`智能客服`的效果,能够为使用app的用户解决基础的问题,而不用额外的人力。 13 | 14 | 但是由于语料的限制,所以这里使用了编程相关的问题,能够回答类似:`python是什么`,`python有什么优势`等问题 15 | 16 | ## 2. 效果演示 17 | 18 | ![](..\images\2.1\app截图.png) 19 | 20 | ## 3. 实现流程 21 | 22 | #### 3.1 整体架构 23 | 24 | ![](..\images\2.1\整体架构.png) 25 | 26 | 整个流程的描述如下: 27 | 28 | 1. 接受用户的问题之后,对问题进行基础的处理 29 | 2. 对处理后的问题进行分类,判断其意图 30 | 3. 如果用户希望闲聊,那么调用闲聊模型返回结果 31 | 4. 如果用户希望咨询问题,那么调用问答模型返回结果 32 | 33 | #### 3.2 闲聊模型 34 | 35 | 闲聊模型使用了seq2seq模型实现 36 | 37 | 包含: 38 | 39 | 1. 对数据的embedding 40 | 2. 编码层 41 | 3. attention机制的处理 42 | 4. 解码层 43 | 44 | ![](..\images\2.1\chabot.png) 45 | 46 | #### 3.4 问答模型 47 | 48 | 问答模型使用了召回和排序的机制来实现,保证获取的速度的同时保证了准确率 49 | 50 | 1. 问题分析:对问题进行基础的处理,包括分词,词性的获取,词向量的获取 51 | 2. 问题的召回:通过机器学习的方法进行海选,海选出大致满足要求的相似问题的前K个 52 | 3. 问题的排序:通过深度学习的模型对问题计算准确率,进行排序 53 | 4. 设置阈值,返回结果 54 | 55 | ![](..\images\2.1\QAbot.png) -------------------------------------------------------------------------------- /2.1 项目准备/2.1.3 环境准备.md: -------------------------------------------------------------------------------- 1 | # 环境准备 2 | 3 | ## 目标 4 | 5 | 1. 能够使用anaconda创建虚拟环境 6 | 2. 能够安装fasttext 7 | 3. 能够安装pysparnn 8 | 9 | 10 | 11 | ## 1. Anaconda环境准备 12 | 13 | 1. 下载地址:`https://mirror.tuna.tsinghua.edu.cn/help/anaconda/` 14 | 15 | 2. 下载对应电脑版本软件,安装 16 | 1. windows :双击exe文件 17 | 2. unix:给sh文件添加可执行权限,执行sh文件 18 | 3. 添加到环境变量 19 | 1. windows安装过程中勾选 20 | 2. unix:`export PATH="/root/miniconda3/bin:$PATH"` 21 | 4. 创建虚拟环境 22 | 1. `conda create -n 名字 python=3.6(版本)` 23 | 2. 查看所有虚拟环境: `conda env list` 24 | 5. 切换到虚拟环境 25 | 1. `conda activate 名字` 26 | 6. 退出虚拟环境 27 | 1. `conda deactivate 名字` 28 | 29 | ## 2. fasttext安装 30 | 31 | 文档地址:`https://fasttext.cc/docs/en/support.html` 32 | 33 | github地址:` 词语 词性(不要和jieba默认的词性重复) 15 | 16 | ![](../images/2.1/词典.png) 17 | 18 | 19 | 20 | #### 1.1 词典来源 21 | 22 | 1. 各种输入法的词典 23 | 24 | 例如:`https://pinyin.sogou.com/dict/cate/index/97?rf=dictindex` 25 | 26 | 例如:`https://shurufa.baidu.com/dict_list?cid=211` 27 | 28 | 2. 手动收集,根据目前的需求,我们可以手动收集如下词典 29 | 1. 机构名称,例如:`传智`,`传智播客`,`黑马程序员` 30 | 2. 课程名词,例如:`python`,`人工智能+python`,`c++`等 31 | 32 | #### 1.2 词典处理 33 | 34 | 输入法的词典都是特殊格式,需要使用特殊的工具才能够把它转化为文本格式 35 | 36 | 工具名称:`深蓝词库转换.exe` 37 | 38 | 下载地址:`https://github.com/studyzy/imewlconverter` 39 | 40 | #### 1.3 对多个词典文件内容进行合并 41 | 42 | 下载使用不同平台的多个词典之后,把所有的txt文件合并到一起供之后使用 43 | 44 | 45 | 46 | ## 2. 准备停用词 47 | 48 | #### 2.1 什么是停用词? 49 | 50 | 对句子进行分词之后,句子中不重要的词 51 | 52 | #### 2.2 停用词的准备 53 | 54 | 常用停用词下载地址:`https://github.com/goto456/stopwords` 55 | 56 | #### 2.3 手动筛选和合并 57 | 58 | 对于停用词的具体内容,不同场景下可能需要保留和去除的词语不一样 59 | 60 | 比如:词语`哪个`,很多场景可以删除,但是在判断语义的时候则不行 61 | 62 | 63 | 64 | ## 3. 问答对的准备 65 | 66 | #### 3.1 现有问答对的样式 67 | 68 | 问答对有两部分,一部分是咨询老师整理的问答对,一部分是excel中的问答对, 69 | 70 | 最终我们需要把问答对分别整理到两个txt文档中,如下图(左边是问题,右边是答案): 71 | 72 | ![](..\images\2.1\问答对.png) 73 | 74 | Excel中的问答对如下图: 75 | 76 | ![](..\images\2.1\excel中的问题.png) 77 | 78 | 79 | 80 | #### 3.2 excel中问答对的处理 81 | 82 | Excel中的问答对直接使用pandas就能够处理 83 | 84 | ```python 85 | python_qa_path = "./data/Python短问答-11月汇总.xlsx" 86 | def load_duanwenda(): 87 | import pandas as pd 88 | ret = pd.read_excel(python_qa_path) 89 | column_list = ret.columns 90 | assert '问题' in column_list and "答案" in column_list,"excel 中必须包含问题和答案" 91 | for q,a in zip(ret["问题"],ret["答案"]): 92 | q = re.sub("\s+"," ",q) 93 | q = q.strip() 94 | print(q,a) 95 | ``` 96 | 97 | ## 4. 相似问答对的采集 98 | 99 | #### 4.1 采集相似问答对的目的 100 | 101 | 后续在判断问题相似度的时候,需要有语料用来进行模型的训练,输入两个句子,输出相似度,这个语料不好获取,所以决定从百度知道入手,采集百度知道上面的相似问题,如下图所示: 102 | 103 | ![](..\images\2.1\百度相似问题搜索.png) 104 | 105 | 上面采集的数据会存在部分噪声,部分问题搜索到的结果语义上并不是太相似 106 | 107 | #### 4.2 手动构造数据 108 | 109 | 根据前面的问答对的内容,把问题大致分为了若干类型,对不同类型的问题设计模板,然后构造问题,问题模块如下: 110 | 111 | ``` 112 | templete = [ 113 | #概念 114 | ["{kc}","什么是{kc}","{kc}是什么","给我介绍一下{kc}","{kc}可以干什么","能简单说下什么是{kc}吗","我想了解{kc}"], 115 | 116 | #课程优势 117 | ["{kc}课程有什么特点","{jgmc}的{kc}课程有什么特点","{jgmc}的{kc}课程有什么优势","为什么我要来{jgmc}学习{kc}","{jgmc}的{kc}课程有什么优势","为什么要到{jgmc}学习{kc}","{jgmc}的{kc}跟其他机构有什么区别?","为什么选择{jgmc}来学习{kc}?"], 118 | 119 | #语言优势 120 | #["{kc}","什么是{kc}","{kc}是什么","给我介绍一下{kc}","{kc}可以干什么","能简单说下什么是{kc}吗"], 121 | #特点 122 | ["{kc}有什么特点","{kc}有什么优势","{kc}有什么亮点","{kc}有那些亮点","{kc}有那些优势","{kc}有那些特点","{kc}的亮点是什么","{kc}的优势是什么","{kc}的特点是什么"], 123 | #发展前景 124 | ["{kc}的发展怎么样?","{kc}的前景怎么样?","{kc}的发展前景如何?","{kc}的未来怎样","{kc}的前景好么" ], 125 | #就业 126 | ["{kc}好就业么","{kc}就业机会多么","{kc}的岗位多吗","{kc}工作好找吗","{kc}的市场需求怎么样","{kc}的就业环境怎么样"], 127 | #就业方向 128 | ["{kc}学完以后能具体从事哪方面工作?","{kc}的就业岗位有哪些?","{kc}课程学完应聘哪方面工作?","{kc}可以从事哪方面工作?",], 129 | #用途 130 | ["{kc}学完可以做什么","{kc}能干什么","学{kc}能干什么","能举例说下{kc}能做什么吗?","{kc}毕业了能干什么","{kc}主要应用在什么领域"], 131 | #就业薪资 132 | ["{kc}学完工资多少","学完{kc}能拿多少钱","{kc}的就业薪资多少","{kc}就业的平均是工资多少"], 133 | #学习难度 134 | ["{kc}简单么","{kc}容易么","{kc}课程容易么","{kc}上手快么","{kc}课程难么"], 135 | #校区 136 | ["在那些城市开设了{kc}","哪里可以学习{kc}","学习{kc}可以去那些城市","{kc}在哪里开班了"], 137 | #学费 138 | ["{kc}学费","{kc}多少钱","{kc}的学费多少","{kc}是怎么收费的?","学习{kc}要花多少钱","{kc}是怎么收费的","{kc}课程的价格","{kc}课程的价格是多少"], 139 | #适合人群 140 | ["什么人可以学{kc}","哪些人可以学{kc}","学习{kc}有什么要求","学习{kc}需要那些条件","没有基础可以学{kc}吗","学历低可以学习{kc}吗?","成绩不好可以学习{kc}吗?","什么样的人适合学习{kc}?"], 141 | #学习时间 142 | ["{kc}需要学多久","{kc}需要多久才能就业","{kc}需要学习多长时间","{kc}的学时是多少","{kc}的课时是多少","{kc}课时","{kc}课时长度","{kc}的课程周期?","0基础学{kc}多久能才就业"], 143 | #学习内容 144 | ["{kc}学什么","{kc}学习那些内容","我们在{kc}中学习那些内容","在{kc}中大致都学习什么内容"], 145 | #项目内容 146 | ["{kc}的项目有哪些","{kc}有哪些项目","{kc}上课都有哪些实战","{kc}做什么项目?","{kc}项目有多少个?","{kc}课程中有项目吗?"], 147 | #学习某课程的好处 148 | ["为什么要学习{kc}?","学习{kc}有哪些好处?","学习{kc}的理由?","为什么我要来学习{kc}"], 149 | #上课时间 150 | ["上课时间","你们那边每天的上课时间是怎样的呢?"], 151 | #英语要求 152 | ["学习{kc}对英语有要求么","来{jgmc}学习对英语有要求吗?"] 153 | ] 154 | ``` 155 | 156 | 其中大括号的内容`kc`表示课程,`jgmc`表示机构名称 157 | 158 | 接下来,需要完成两件事 159 | 160 | 1. 最终我们会把前面准备好的课程字典和机构名称字典中的词语放入大括号中 161 | 2. 把kc相同的内容构造成相似问题 -------------------------------------------------------------------------------- /2.1 项目准备/2.1.5 文本分词.md: -------------------------------------------------------------------------------- 1 | # 文本分词 2 | 3 | ## 目标 4 | 5 | 1. 完成停用词的准备 6 | 2. 完成分词方法的封装 7 | 8 | 9 | 10 | ## 1. 准备词典和停用词 11 | 12 | ### 1.1 准备词典 13 | 14 | ![](../images/2.1/词典.png) 15 | 16 | ### 1.2 准备停用词 17 | 18 | ```python 19 | stopwords = set([i.strip() for i in open(config.stopwords_path).readlines()]) 20 | ``` 21 | 22 | 23 | 24 | ## 2. 准备按照单个字切分句子的方法 25 | 26 | ```python 27 | def _cut_by_word(sentence): 28 | # 对中文按照字进行处理,对英文不分为字母 29 | sentence = re.sub("\s+"," ",sentence) 30 | sentence = sentence.strip() 31 | result = [] 32 | temp = "" 33 | for word in sentence: 34 | if word.lower() in letters: 35 | temp += word.lower() 36 | else: 37 | if temp != "": #不是字母 38 | result.append(temp) 39 | temp = "" 40 | if word.strip() in filters: #标点符号 41 | continue 42 | else: #是单个字 43 | result.append(word) 44 | if temp != "": #最后的temp中包含字母 45 | result.append(temp) 46 | return result 47 | ``` 48 | 49 | 50 | 51 | 52 | 53 | ## 3. 完成分词方法的封装 54 | 55 | lib 下创建`cut_sentence.py`文件,完成分词方法的构建 56 | 57 | ```python 58 | import logging 59 | import jieba 60 | import jieba.posseg as psg 61 | import config 62 | import re 63 | import string 64 | 65 | #关闭jieba log输出 66 | jieba.setLogLevel(logging.INFO) 67 | #加载词典 68 | jieba.load_userdict(config.keywords_path) 69 | #单字分割,英文部分 70 | letters = string.ascii_lowercase 71 | #单字分割 去除的标点 72 | filters= [",","-","."," "] 73 | #停用词 74 | stopwords = set([i.strip() for i in open(config.stopwords_path).readlines()]) 75 | 76 | 77 | def cut(sentence,by_word=False,use_stopwords=False,with_sg=False): 78 | assert by_word!=True or with_sg!=True,"根据word切分时候无法返回词性" 79 | if by_word: 80 | return _cut_by_word(sentence) 81 | else: 82 | ret = psg.lcut(sentence) 83 | if use_stopwords: 84 | ret = [(i.word,i.flag) for i in ret if i.word not in stopwords] 85 | if not with_sg: 86 | ret = [i.word for i in ret] 87 | return ret 88 | 89 | 90 | def _cut_by_word(sentence): 91 | # 对中文按照字进行处理,对英文不分为字母 92 | sentence = re.sub("\s+"," ",sentence) 93 | sentence = sentence.strip() 94 | result = [] 95 | temp = "" 96 | for word in sentence: 97 | if word.lower() in letters: 98 | temp += word.lower() 99 | else: 100 | if temp != "": #不是字母 101 | result.append(temp) 102 | temp = "" 103 | if word.strip() in filters: #标点符号 104 | continue 105 | else: #是单个字 106 | result.append(word) 107 | if temp != "": #最后的temp中包含字母 108 | result.append(temp) 109 | return result 110 | ``` 111 | 112 | -------------------------------------------------------------------------------- /2.1 项目准备/2.1.6 动手练习.md: -------------------------------------------------------------------------------- 1 | # 动手练习 2 | 3 | ## 目标 4 | 5 | 1. 动手准备好词典 6 | 2. 动手准备问答对 7 | 3. 动手构造问答对 8 | 4. 对问题进行分词,单独存储 9 | 10 | -------------------------------------------------------------------------------- /2.1 项目准备/README.md: -------------------------------------------------------------------------------- 1 | # 2.1 项目准备 -------------------------------------------------------------------------------- /2.2 fasttext文本分类/2.2.1 分类的目的和方法.md: -------------------------------------------------------------------------------- 1 | # 分类的目的和分类的方法 2 | 3 | ## 目标 4 | 5 | 1. 能够说出项目中进行文本的目的 6 | 2. 能够说出意图识别的方法 7 | 3. 能够说出常见的分类的方法 8 | 9 | ## 1. 文本分类的目的 10 | 11 | 回顾之前的流程,我们可以发现文本分类的目的就是为了进行**意图识别** 12 | 13 | 在当前我们的项目的下,我们只有两种意图需要被识别出来,所以对应的是**2分类**的问题 14 | 15 | 可以想象,如果我们的聊天机器人有多个功能,那么我们需要分类的类别就有多个,这样就是一个**多分类**的问题。例如,如果希望聊天机器人能够播报当前的时间,那么我们就需要准备关于询问时间的语料,同时其目标值就是一个新的类别。在训练后,通过这个新的模型,判断出用户询问的是当前的时间这个类别,那么就返回当前的时间。 16 | 17 | 同理,如果还希望聊天机器人能够播报未来某一天的天气,那么这个机器人就还需要增加一个新的进行分类的意图,重新进行训练 18 | 19 | 20 | 21 | ## 2. 机器学习中常见的分类方法 22 | 23 | 在前面的机器学习的课程中我们学习了**朴素贝叶斯**,**决策树**等方法都能够帮助我们进行文本的分类,那么我们具体该怎么做呢? 24 | 25 | ### 2.1 步骤 26 | 27 | 1. 特征工程:对文本进行处理,转化为能够被计算的向量来表示。我们可以考虑使用所有词语的出现次数,也可以考虑使用tfidf这种方法来处理 28 | 2. 对模型进行训练 29 | 3. 对模型进行评估 30 | 31 | ### 2.2 优化 32 | 33 | 使用机器学习的方法进行文本分类的时候,为了让结果更好,我们经常从两个角度出发 34 | 35 | 1. 特征工程的过程中处理的更加细致,比如文本中类似**你,我,他**这种词语可以把它剔除;某些词语出现的次数太少,可能并不具有代表意义;某些词语出现的次数太多,可能导致影响的程度过大等等都是我们可以考虑的地方 36 | 2. 使用不同的算法进行训练,获取不同算法的结果,选择最好的,或者是使用集成学习方法 37 | 38 | ## 3. 深度学习实现文本分类 39 | 40 | 前面我们简单回顾了使用机器学习如何来进行文本分类,那么使用深度学习该如何实现呢? 41 | 42 | 在深度学习中我们常见的操作就是: 43 | 44 | 1. 对文本进行embedding的操作,转化为向量 45 | 2. 之后再通过多层的神经网络进行线性和非线性的变化得到结果 46 | 3. 变换后的结果和目标值进行计算得到损失函数,比如对数似然损失等 47 | 4. 通过最小化损失函数,去更新原来模型中的参数 48 | 49 | -------------------------------------------------------------------------------- /2.2 fasttext文本分类/2.2.1 分类的目的和方法md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/2.2 fasttext文本分类/2.2.1 分类的目的和方法md -------------------------------------------------------------------------------- /2.2 fasttext文本分类/2.2.2 fasttext实现文本分类.md: -------------------------------------------------------------------------------- 1 | # fastText实现文本分类 2 | 3 | ## 目标 4 | 5 | 1. 知道fastext是什么 6 | 2. 能够应用fasttext进行文本分类 7 | 3. 能够完成项目中意图识别的代码 8 | 9 | 10 | 11 | ## 1. fastText的介绍 12 | 13 | 文档地址:`https://fasttext.cc/docs/en/support.html` 14 | 15 | fastText is a library for efficient learning of word representations and sentence classification. 16 | 17 | fastText是一个单词表示学习和文本分类的库 18 | 19 | 优点:在标准的多核CPU上, 在10分钟之内能够训练10亿词级别语料库的词向量,能够在1分钟之内给30万多类别的50多万句子进行分类。 20 | 21 | fastText 模型输入一个词的序列(一段文本或者一句话),输出这个词序列属于不同类别的概率。 22 | 23 | 24 | 25 | ## 2. 安装和基本使用 26 | 27 | ### 2.1 安装步骤: 28 | 29 | 1. 下载 `git clone https://github.com/facebookresearch/fastText.git` 30 | 2. cd `cd fastText` 31 | 3. 安装 `python setup.py install` 32 | 33 | #### 2.2 基本使用 34 | 35 | 1. 把数据准备为需要的格式 36 | 37 | 2. 进行模型的训练、保存和加载、预测 38 | 39 | ```python 40 | #1. 训练 41 | model = fastText.train_supervised("./data/text_classify.txt",wordNgrams=1,epoch=20) 42 | #2. 保存 43 | model.save_model("./data/ft_classify.model") 44 | #3. 加载 45 | model = fastText.load_model("./data/ft_classify.model") 46 | 47 | textlist = [句子1,句子2] 48 | #4. 预测,传入句子列表 49 | ret = model.predict(textlist) 50 | ``` 51 | 52 | 53 | 54 | ## 3. 意图识别实现 55 | 56 | ### 3.1 数据准备 57 | 58 | 数据准备最终需要的形式如下: 59 | 60 | ![](../images/2.2/数据准备1.png) 61 | 62 | ![](../images/2.2/数据准备2.png) 63 | 64 | 65 | 66 | 以上格式是fastText要求的格式,其中chat、QA字段可以自定义,就是目标值,`__label__`之前的为特征值,需要使用`\t`进行分隔,特征值需要进行分词,`__label__`后面的是目标值 67 | 68 | 69 | 70 | #### 3.1.1 准备特征文本 71 | 72 | 使用之前通过模板构造的样本和通过爬虫抓取的百度上的相似问题, 73 | 74 | #### 3.1.2 准备闲聊文本 75 | 76 | 使用小黄鸡的语料,地址:https://github.com/fateleak/dgk_lost_conv/tree/master/results 77 | 78 | #### 3.1.3 把文本转化为需要的格式 79 | 80 | 对两部分文本进行分词、合并,转化为需要的格式 81 | 82 | ```python 83 | def prepar_data(): 84 | #小黄鸡 作为闲聊 85 | xiaohaungji = "./corpus/recall/小黄鸡未分词.conv" 86 | handle_chat_corpus(xiaohaungji) 87 | # mongodb中的数据,问题和相似问题作为 问答 88 | handle_mongodb_corpus() 89 | 90 | def keywords_in_line(line): 91 | """相似问题中去除关键字不在其中的句子 92 | """ 93 | keywords_list = ["传智播客","传智","黑马程序员","黑马","python" 94 | "人工智能","c语言","c++","java","javaee","前端","移动开发","ui", 95 | "ue","大数据","软件测试","php","h5","产品经理","linux","运维","go语言", 96 | "区块链","影视制作","pmp","项目管理","新媒体","小程序","前端"] 97 | for keyword in keywords_list: 98 | if keyword in line: 99 | return True 100 | return False 101 | 102 | 103 | def handle_chat_corpus(path): 104 | chat_num = 0 105 | with open("./corpus/recall/text_classify.txt","a") as f: 106 | for line in open(path,"r"): 107 | if line.strip() == "E" or len(line.strip())<1: 108 | continue 109 | elif keywords_in_line(line): 110 | continue 111 | elif line.startswith("M"): 112 | line = line[2:] 113 | line = re.sub("\s+"," ",line) 114 | line_cuted = " ".join(jieba_cut(line.strip())).strip() 115 | lable = "\t__label__{}\n".format("chat") 116 | f.write(line_cuted+lable) 117 | chat_num +=1 118 | print(chat_num) 119 | 120 | def handle_QA_corpus(): 121 | 122 | by_hand_data_path = "./corpus/recall/手动构造的问题.json" #手动构造的数据 123 | by_hand_data = json.load(open(by_hand_data_path)) 124 | 125 | qa_num = 0 126 | 127 | f = open("./corpus/recall/text_classify.txt","a") 128 | for i in by_hand_data: 129 | for j in by_hand_data[i]: 130 | for x in j: 131 | x = re.sub("\s+", " ", x) 132 | line_cuted = " ".join(jieba_cut(x.strip())).strip() 133 | lable = "\t__label__{}\n".format("QA") 134 | f.write(line_cuted + lable) 135 | qa_num+=1 136 | 137 | #mogodb导出的数据 138 | for line in open("./corpus/recall/爬虫抓取的问题.csv"): 139 | line = re.sub("\s+", " ", line) 140 | line_cuted = " ".join(jieba_cut(line.strip())) 141 | lable = "\t__label__{}\n".format("QA") 142 | f.write(line_cuted + lable) 143 | qa_num += 1 144 | 145 | f.close() 146 | print(qa_num) 147 | ``` 148 | 149 | #### 3.1.4 思考: 150 | 151 | 是否可以把文本分割为单个字作为特征呢? 152 | 153 | 修改上述代码,准备一份以单个字作为特征的符合要求的文本 154 | 155 | ### 3.2 模型的训练 156 | 157 | ```python 158 | import logging 159 | import fastText 160 | import pickle 161 | 162 | logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.DEBUG) 163 | 164 | 165 | ft_model = fastText.train_supervised("./data/text_classify.txt",wordNgrams=1,epoch=20) 166 | ft_model.save_model("./data/ft_classify.model") 167 | ``` 168 | 169 | 训练完成后看看测试的结果 170 | 171 | ```python 172 | ft_model = fastText.load_model("./data/ft_classify.model") 173 | 174 | textlist = [ 175 | # "人工智能 和 人工智障 有 啥 区别", #QA 176 | # "我 来 学 python 是不是 脑袋 有 问题 哦", #QA 177 | # "什么 是 python", #QA 178 | # "人工智能 和 python 有 什么 区别", #QA 179 | # "为什么 要 学 python", #QA 180 | # "python 该 怎么 学", #CHAT 181 | # "python", #QA 182 | "jave", #CHAT 183 | "php", #QA 184 | "理想 很 骨感 ,现实 很 丰满", 185 | "今天 天气 真好 啊", 186 | "你 怎么 可以 这样 呢", 187 | "哎呀 , 我 错 了", 188 | ] 189 | ret = ft_model.predict(textlist) 190 | print(ret) 191 | ``` 192 | 193 | #### 3.2.2 模型的准确率该如何观察呢? 194 | 195 | 观察准备去,首先需要对文本进行划分,分为训练集和测试集,之后再使用测试集观察模型的准确率 196 | 197 | 198 | 199 | ### 3.3 模型的封装 200 | 201 | 为了在项目中更好的使用模型,需要对模型进行简单的封装,输入文本,返回结果 202 | 203 | 这里我们可以使用把单个字作为特征和把词语作为特征的手段结合起来实现 204 | 205 | ```python 206 | """ 207 | 构造模型进行预测 208 | """ 209 | import fastText 210 | import config 211 | from lib import cut 212 | 213 | 214 | class Classify: 215 | def __init__(self): 216 | self.ft_word_model = fastText.load_model(config.fasttext_word_model_path) 217 | self.ft_model = fastText.load_model(config.fasttext_model_path) 218 | 219 | def is_qa(self,sentence_info): 220 | python_qs_list = [" ".join(sentence_info["cuted_sentence"])] 221 | result = self.ft_mode.predict(python_qs_list) 222 | 223 | python_qs_list = [" ".join(cut(sentence_info["sentence"],by_word=True))] 224 | words_result = self.ft_word_mode.predict(python_qs_list) 225 | 226 | acc,word_acc = self.get_qa_prob(result,words_result) 227 | if acc>0.95 or word_acc>0.95: 228 | #是QA 229 | return True 230 | else: 231 | return False 232 | 233 | def get_qa_prob(self,result,words_result): 234 | label, acc, word_label, word_acc = zip(*result, *words_result) 235 | label = label[0] 236 | acc = acc[0] 237 | word_label = word_label[0] 238 | word_acc = word_acc[0] 239 | if label == "__label__chat": 240 | acc = 1 - acc 241 | if word_label == "__label__chat": 242 | word_acc = 1 - word_acc 243 | return acc,word_acc 244 | ``` 245 | 246 | -------------------------------------------------------------------------------- /2.2 fasttext文本分类/2.2.3 fasttext的原理剖析.md: -------------------------------------------------------------------------------- 1 | # fastText的原理剖析 2 | 3 | ## 目标 4 | 5 | 1. 能够说出fasttext的架构 6 | 2. 能够说出fasttext速度快的原因 7 | 3. 能够说出fastText中层次化的softmax是如何实现的 8 | 9 | 10 | 11 | ## 1. fastText的模型架构 12 | 13 | fastText的架构非常简单,有三层:输入层、隐含层、输出层(Hierarchical Softmax) 14 | 15 | 输入层:是对文档embedding之后的向量,包含有N-garm特征 16 | 17 | 隐藏层:是对输入数据的求和平均 18 | 19 | 输出层:是文档对应标签 20 | 21 | 如下图所示: 22 | 23 | ![](../images/2.2/fasttext原理.jpg) 24 | 25 | ### 1.1 N-garm的理解 26 | 27 | ### 1.1.1 bag of word 28 | 29 | ![](../images/2.2/bow模型.png) 30 | 31 | bag of word 又称为bow,称为词袋。是一种只统计词频的手段。 32 | 33 | 例如:在机器学习的课程中通过朴素贝叶斯来预测文本的类别,我们学习的countVectorizer和TfidfVectorizer都可以理解为一种bow模型。 34 | 35 | ### 1.1.2 N-gram模型 36 | 37 | 但是在很多情况下,词袋模型是不满足我们的需求的。 38 | 39 | 例如:`我爱她` 和`她爱我`在词袋模型下面,概率完全相同,但是其含义确实差别非常大。 40 | 41 | 为了解决这个问题,就有了N-gram模型,它不仅考虑词频,还会考虑当前词前面的词语,比如`我爱`,`她爱`。 42 | 43 | N-gram模型的描述是:第n个词出现与前n-1个词相关,而与其他任何词不相关。(当然在很多场景下和前n-1个词也会相关,但是为了简化问题,经常会这样去计算) 44 | 45 | 例如:`I love deep learning`这个句子,在n=2的情况下,可以表示为`{i love},{love deep},{deep learning},`n=3的情况下,可以表示为`{I love deep},{love deep learning}`。 46 | 47 | 在n=2的情况下,这个模型被称为Bi-garm(二元n-garm模型) 48 | 49 | 在n=3 的情况下,这个模型被称为Tri-garm(三元n-garm模型) 50 | 51 | 具体可以参考 [ed3book chapter3 ](http://web.stanford.edu/~jurafsky/slp3/ed3book.pdf) 52 | 53 | 所以在fasttext的输入层,不仅有分词之后的词语,还有包含有N-gram的组合词语一起作为输入 54 | 55 | 56 | 57 | ## 2. fastText中的层次化的softmax-对传统softmax的优化方法1 58 | 59 | 为了提高效率,在fastText中计算分类标签的概率的时候,不再是使用传统的softmax来进行多分类的计算,而是使用的哈夫曼树(Huffman,也成为霍夫曼树),使用层次化的softmax(Hierarchial softmax)来进行概率的计算。 60 | 61 | ### 2.1 哈夫曼树和哈夫曼编码 62 | 63 | ![](../images/2.2/Inked哈夫曼树.jpg) 64 | 65 | #### 2.1.1 哈夫曼树的定义 66 | 67 | 哈夫曼树概念:给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。 68 | 69 | 哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。 70 | 71 | #### 2.1.2 哈夫曼树的相关概念 72 | 73 | **二叉树**:每个节点最多有2个子树的有序树,两个子树分别称为左子树、右子树。有序的意思是:树有左右之分,不能颠倒 74 | 75 | **叶子节点**:一棵树当中没有子结点的结点称为叶子结点,简称“叶子” 76 | 77 | **路径和路径长度**:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。 78 | 79 | **结点的权及带权路径长度**:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的**乘积**。 80 | 81 | **树的带权路径长度**:树的带权路径长度规定为所有叶子结点的带权路径长度之和 82 | 83 | **树的高度**:树中结点的最大层次。包含n个结点的二叉树的高度至少为**log2 (n+1)**。 84 | 85 | #### 2.1.3 哈夫曼树的构造算法 86 | 87 | 1. 把$\{W_1,W_2,W_3 \dots W_n\}​$看成n棵树的森林 88 | 2. 在森林中选择两个根节点权值最小的树进行合并,作为一颗新树的左右子树,新树的根节点权值为左右子树的和 89 | 3. 删除之前选择出的子树,把新树加入森林 90 | 4. 重复2-3步骤,直到森林只有一棵树为止,概树就是所求的哈夫曼树 91 | 92 | 例如:圆圈中的表示每个词语出现的次数,以这些词语为叶子节点构造的哈夫曼树过程如下: 93 | 94 | ![](../images/2.2/哈夫曼树构造过程.png) 95 | 96 | 97 | 98 | 可见: 99 | 100 | 1. 权重越大,距离根节点越近 101 | 2. 叶子的个数为n,构造哈夫曼树中新增的节点的个数为n-1 102 | 103 | #### 2.2.1 哈夫曼编码 104 | 105 | 在数据通信中,需要将传送的文字转换成二进制的字符串,用0,1码的不同排列来表示字符。 106 | 107 | 例如,需传送的报文为`AFTER DATA EAR ARE ART AREA`,这里用到的字符集为`A,E,R,T,F,D`,各字母出现的次数为{8,4,5,3,1,1}。现要求为这些字母设计编码。要区别6个字母,最简单的二进制编码方式是等长编码,固定采用3位二进制,可分别用`000、001、010、011、100、101`对`A,E,R,T,F,D`进行编码发送 108 | 109 | 但是很明显,上述的编码的方式并不是最优的,即整理传送的字节数量并不是最少的。 110 | 111 | 为了提高数据传送的效率,同时为了保证`任一字符的编码都不是另一个字符编码的前缀,这种编码称为前缀编码[前缀编码]`,可以使用哈夫曼树生成哈夫曼编码解决问题 112 | 113 | 可用字符集中的每个字符作为叶子结点生成一棵编码二叉树,为了获得传送报文的最短长度,可将每个字符的出现频率作为字符结点的权值赋予该结点上,显然字使用频率越小权值越小,权值越小叶子就越靠下,于是频率小编码长,频率高编码短,这样就保证了此树的最小带权路径长度效果上就是传送报文的最短长度 114 | 115 | 因此,求传送报文的最短长度问题转化为求由字符集中的所有字符作为叶子结点,由字符出现频率作为其权值所产生的哈夫曼树的问题。利用哈夫曼树来设计二进制的前缀编码,既满足前缀编码的条件,又保证报文编码总长最短。 116 | 117 | 下图中`label1 .... label6`分别表示`A,E,R,T,F,D` 118 | 119 | ![](../images/2.2/哈夫曼编码.jpg) 120 | 121 | #### 2.3 梯度计算 122 | 123 | ![](../images/2.2/哈夫曼编码 - 副本.jpg) 124 | 125 | 126 | 127 | 上图中,红色为哈夫曼编码,即label5的哈夫曼编码为1001,那么此时如何定义条件概率$P(Label5|contex)​$呢? 128 | 129 | 以Label5为例,从根节点到Label5中间经历了4次分支,每次分支都可以认为是进行了一次2分类,根据哈夫曼编码,可以把路径中的每个非叶子节点0认为是负类,1认为是正类(也可以把0认为是正类) 130 | 131 | 由机器学习课程中逻辑回归使用sigmoid函数进行2分类的过程中,一个节点被分为正类的概率是$\delta(X^{T}\theta) = \frac{1}{1+e^{-X^T\theta}}$,被分类负类的概率是:$1-\delta(X^T\theta)$,其中$\theta$就是图中非叶子节点对应的参数$\theta$。 132 | 133 | 对于从根节点出发,到达Label5一共经历4次2分类,将每次分类结果的概率写出来就是: 134 | 135 | 1. 第一次:$P(1|X,\theta_1) = \delta(X^T\theta_1) $ ,即从根节点到23节点的概率是在知道X和$\theta_1$的情况下取值为1的概率 136 | 2. 第二次:$P(0|X,\theta_2) =1- \delta(X^T\theta_2) $ 137 | 3. 第三次:$P(0 |X,\theta_3) =1- \delta(X^T\theta_4) $ 138 | 4. 第四次:$P(1|X,\theta_4) = \delta(X^T\theta_4) $ 139 | 140 | 141 | 142 | 但是我们需要求的是$P(Label|contex)$, 他等于前4词的概率的乘积,公式如下($d_j^w​$是第j个节点的哈夫曼编码) 143 | $$ 144 | P(Label|context) = \prod_{j=2}^5P(d_j|X,\theta_{j-1}) 145 | $$ 146 | 147 | 148 | 其中: 149 | $$ 150 | P(d_j|X,\theta_{j-1}) = \left\{ 151 | \begin{aligned} 152 | &\delta(X^T\theta_{j-1}), & d_j=1;\\ 153 | &1-\delta(X^T\theta_{j-1}) & d_j=0; 154 | \end{aligned} 155 | \right. 156 | $$ 157 | 158 | 159 | 或者也可以写成一个整体,把目标值作为指数,之后取log之后会前置: 160 | $$ 161 | P(d_j|X,\theta_{j-1}) = [\delta(X^T\theta_{j-1})]^{d_j} \cdot [1-\delta(X^T\theta_{j-1})]^{1-d_j} 162 | $$ 163 | 164 | 165 | 在机器学习中的逻辑回归中,我们经常把二分类的损失函数(目标函数)定义为对数似然损失,即 166 | $$ 167 | l =-\frac{1}{M} \sum_{label\in labels} log\ P(label|context) 168 | $$ 169 | 170 | 171 | 式子中,求和符号表示的是使用样本的过程中,每一个label对应的概率取对数后的和,之后求取均值。 172 | 173 | 带入前面对$P(label|context)​$的定义得到: 174 | $$ 175 | \begin{align*} 176 | l & = -\frac{1}{M}\sum_{label\in labels}log \prod_{j=2}\{[\delta(X^T\theta_{j-1})]^{d_j} \cdot [1-\delta(X^T\theta_{j-1})]^{1-d_j}\} \\ 177 | & =-\frac{1}{M} \sum_{label\in labels} \sum_{j=2}\{d_j\cdot log[\delta(X^T\theta_{j-1})]+ (1-d_j) \cdot log [1-\delta(X^T\theta_{j-1})]\} 178 | \end{align*} 179 | $$ 180 | 有了损失函数之后,接下来就是对其中的$X,\theta$进行求导,并更新,最终还需要更新最开始的每个词语词向量 181 | 182 | **层次化softmax的好处**:传统的softmax的时间复杂度为L(Labels的数量),但是使用层次化softmax之后时间复杂度的log(L) (二叉树高度和宽度的近似),从而在多分类的场景提高了效率 183 | 184 | ## 3. fastText中的negative sampling(负采样)-对传统softmax的优化方法2 185 | 186 | negative sampling,即每次从除当前label外的其他label中,随机的选择几个作为负样本。具体的采样方法: 187 | 188 | 如果所有的label为$V​$,那么我们就将一段长度为1的线段分成$V​$份,每份对应所有label中的一类label。当然每个词对应的线段长度是不一样的,高频label对应的线段长,低频label对应的线段短。每个label的线段长度由下式决定: 189 | $$ 190 | len(w) = \frac{count(label)^{\alpha}}{\sum_{w \in labels} count(labels)^{\alpha}},a在fasttext中为0.75,即负采样的数量和原来词频的平方根成正比 191 | $$ 192 | 在采样前,我们将这段长度为1的线段划分成$M​$等份,这里$M>>V​$,这样可以保证每个label对应的线段都会划分成对应的小块。而M份中的每一份都会落在某一个label对应的线段上。在采样的时候,我们只需要从$M​$个位置中采样出neg个位置就行,此时采样到的每一个位置对应到的线段所属的词就是我们的负例。 193 | 194 | ![](../images/2.2/负采样.png) 195 | 196 | 简单的理解就是,从原来所有的样本中,等比例的选择neg个负样本作(遇到自己则跳过),作为训练样本,添加到训练数据中,和正例样本一起来进行训练。 197 | 198 | Negative Sampling也是采用了二元逻辑回归来求解模型参数,通过负采样,我们得到了neg个负例,将正例定义为$label_0​$,负例定义为$label_i,i=1,2,3...neg​$ 199 | 200 | 定义正例的概率为$P\left( label_{0}|\text {context}\right)=\sigma\left(x_{\mathrm{k}}^{T} \theta\right), y_{i}=1​$ 201 | 202 | 则负例的概率为:$P\left( label_{i}|\text {context}\right)=1-\sigma\left(x_{\mathrm{k}}^{T} \theta\right), y_{i}=0,i=1,2,3..neg​$ 203 | 204 | 此时对应的对数似然函数为: 205 | $$ 206 | L=\sum_{i=0}^{n e g} y_{i} \log \left(\sigma\left(x_{label_0}^{T} \theta\right)\right)+\left(1-y_{i}\right) \log \left(1-\sigma\left(x_{label_0}^{T} \theta\right)\right) 207 | $$ 208 | 具体的训练时候损失的计算过程(源代码已经更新): 209 | 210 | ![](../../../../NLP%E8%AF%BE%E4%BB%B6%E7%BC%96%E5%86%99/markdown/doc/images/2.2/fasttext%E8%B4%9F%E9%87%87%E6%A0%B7.png) 211 | 212 | 可以看出:一个neg+1个样本进行了训练,得到了总的损失。 213 | 214 | 之后会使用梯度上升的方法进行梯度计算和参数更新,仅仅每次只用一波样本(一个正例和neg个反例)更新梯度,来进行迭代更新 215 | 216 | 具体的更新伪代码如下: 217 | 218 | ![](../images/2.2/负采样梯度上升.png) 219 | 220 | 其中内部大括号部分为w相关参数的梯度计算过程,e为w的梯度和学习率的乘积,具体参考: 221 | 222 | 223 | 224 | 好处: 225 | 226 | 1. 提高训练速度,选择了部分数据进行计算损失,同时整个对每一个label而言都是一个二分类,损失计算更加简单,只需要让当前label的值的概率尽可能大,其他label的都为反例,概率会尽可能小 227 | 2. 改进效果,增加部分负样本,能够模拟真实场景下的噪声情况,能够让模型的稳健性更强 228 | 229 | 230 | 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /2.2 fasttext文本分类/README.md: -------------------------------------------------------------------------------- 1 | # 2.2 Fasttext文本分类 2 | 3 | -------------------------------------------------------------------------------- /2.3 Seq2Seq模型和闲聊机器人/2.3.1 闲聊机器人的介绍.md: -------------------------------------------------------------------------------- 1 | # 闲聊机器人的介绍 2 | 3 | ## 目标 4 | 5 | 1. 了解闲聊机器人是什么 6 | 7 | 8 | 9 | ## 介绍 10 | 11 | 在项目准备阶段我们知道,用户说了一句话后,会判断其意图,如果是想进行闲聊,那么就会调用闲聊模型返回结果,这是我们会在项目中实现的功能。 12 | 13 | 目前市面上的常见闲聊机器人有`微软小冰`这种类型的模型,很久之前还有`小黄鸡`这种体验更差的模型 14 | 15 | 常见的闲聊模型都是一种seq2seq的结构,在后面的课程中我们会学习并使用seq2seq来实现我们的闲聊机器人 16 | 17 | -------------------------------------------------------------------------------- /2.3 Seq2Seq模型和闲聊机器人/2.3.4 Attention的原理和实现.md: -------------------------------------------------------------------------------- 1 | # Attention的原理和实现 2 | 3 | ## 目标 4 | 5 | 1. 知道Attention的作用 6 | 2. 知道Attention的实现机制 7 | 3. 能够使用代码完成Attention代码的编写 8 | 9 | 10 | 11 | ## 1. Attention的介绍 12 | 13 | 在普通的RNN结构中,Encoder需要把一个句子转化为一个向量,然后在Decoder中使用,这就要求Encoder把源句子中所有的信息都包含进去,但是当句子长度过长的时候,这个要求就很难达到,或者说会产生瓶颈(比如,输入一篇文章等场长内容),当然我们可以使用更深的RNN和大多的单元来解决这个问题,但是这样的代价也很大。那么有没有什么方法能够优化现有的RNN结构呢? 14 | 15 | 为此,Bahdanau等人在2015年提出了`Attenion`机制,`Attention`翻译成为中文叫做注意力,把这种模型称为`Attention based model`。就像我们自己看到一副画,我们能够很快的说出画的主要内容,而忽略画中的背景,因为我们注意的,更关注的往往是其中的主要内容。 16 | 17 | 通过这种方式,在我们的RNN中,我们有通过LSTM或者是GRU得到的所有信息,那么这些信息中只去关注重点,而不需要在Decoder的每个time step使用全部的encoder的信息,这样就可以解决第一段所说的问题了 18 | 19 | 那么现在要讲的`Attention机制`就能够帮助我们解决这个问题 20 | 21 | 22 | 23 | ## 2. Attenion的实现机制 24 | 25 | 假设我们现在有一个文本翻译的需求,即`机器学习`翻译为`machine learning`。那么这个过程通过前面所学习的Seq2Seq就可以实现 26 | 27 | ![](../images/2.3/attention1.png) 28 | 29 | 上图的左边是Encoder,能够得到`hidden_state`在右边使用 30 | 31 | Deocder中**蓝色方框**中的内容,是为了提高模型的训练速度而使用teacher forcing手段,否则的话会把前一次的输出作为下一次的输入(**但是在Attention模型中不再是这样了**) 32 | 33 | 那么整个过程中如果使用Attention应该怎么做呢? 34 | 35 | 在之前我们把encoder的最后一个输出,作为decoder的初始的隐藏状态,现在我们不再这样做 36 | 37 | ### 2.1 Attention的实现过程 38 | 39 | 1. 初始化一个Decoder的隐藏状态$z_0$ 40 | 41 | 2. 这个$z_o$会和encoder第一个time step的output进行match操作(或者是socre操作),得到$\alpha_0^1$ ,这里的match可以使很多中操作,比如: 42 | 43 | - z和h的余弦值 44 | - 是一个神经网络,输入为z和h 45 | - 或者$\alpha = h^T W z​$等 46 | - ![](../images/2.3/Attention2.png) 47 | 48 | 3. encoder中的每个output都和$z_0​$进行计算之后,得到的结果进行softmax,让他们的和为1(可以理解为权重) 49 | 50 | 4. 之后把所有的softmax之后的结果和原来encoder的输出$h_i​$进行相加求和得到$c^0​$ 51 | $$ 52 | 即: c^0 = \sum\hat{\alpha}_0^ih^i 53 | $$ 54 | 55 | - ![](../images/2.3/Attention3.png) 56 | 57 | 5. 得到$c^0​$之后,把它作为decoder的input,同和传入初始化的$z^0​$,得到第一个time step的输出和hidden_state($Z^1​$) 58 | 59 | - ![](../images/2.3/Attention4.png) 60 | 61 | 6. 把$Z_1​$再和所有的encoder的output进行match操作,得到的结果进行softmax之后作为权重和encoder的每个timestep的结果相乘求和得到$c^1​$ 62 | 63 | 7. 再把$c^1​$作为decoder的input,和$Z^1​$作为输入得到下一个输出,如此循环,只到最终decoder的output为终止符 64 | 65 | - ![](../images/2.3/Attention5.png) 66 | 67 | 8. 上述参考:http://speech.ee.ntu.edu.tw/~tlkagk/courses_MLSD15_2.html 68 | 69 | 9. 整个过程写成数学公式如下: 70 | $$ 71 | \begin{align*} 72 | \alpha_{ij} &= \frac{exp(score(h_i,\overline{h}_j))}{\sum exp(score(h_n,\overline{h}_m))} & [attention \quad weight]\\ 73 | c_i &=\sum \alpha_{ij}\overline{h}_s & [context\quad vector] \\ 74 | \alpha_i &= f(c_i,h_i) = tanh(W_c[c_i;h_i]) &[attenton \quad result] 75 | \end{align*} 76 | $$ 77 | 78 | 1. 先计算attention权重 79 | 2. 在计算上下文向量,图中的$c^i​$ 80 | 3. 最后计算结果,往往会把当前的output([batch_size,1,hidden_size])和上下文向量进行拼接然后使用 81 | 82 | ### 2.2 不同Attention的介绍 83 | 84 | 在上述过程中,使用decoder的状态和encoder的状态的计算后的结果作为权重,乘上encoder每个时间步的输出,这需要我们去训练一个合适的match函数,得到的结果就能够在不同的时间步上使用不同的encoder的相关信息,从而达到只关注某一个局部的效果,也就是注意力的效果 85 | 86 | ### 2.2.1 `Soft-Attention 和 Hard-Attention` 87 | 88 | 最开始`Bahdanau`等人提出的Attention机制通常被称为`soft-attention`,所谓的`soft-attention`指的是encoder中输入的每个词语都会计算得到一个注意力的概率。 89 | 90 | 在进行图像捕捉的时候,提出了一种`hard-attenion`的方法,希望直接从input中找到一个和输出的某个词对应的那一个词。但是由于NLP中词语和词语之间往往存在联系,不会只关注某一个词语,所以都会使用soft-attention,所以这里的就不多介绍hard-attention 91 | 92 | ![](../images/2.3/soft-hard attention.jpg) 93 | 94 | ### 2.2.3 `Global-Attention 和Local Attention` 95 | 96 | `Bahdanau`等人提出的`Bahdanau Attention` 被称为`local attention`,后来`Luong`等人提出的`Luong Attention`是一种全局的attenion。 97 | 98 | 所谓全局的attenion指的是:使用的全部的encoder端的输入的attenion的权重 99 | 100 | `local-attenion`就是使用了部分的encoder端的输入的权重(当前时间步上的encoder的hidden state),这样可以减少计算量,特别是当句子的长度比较长的时候。 101 | 102 | 103 | 104 | ### 2.2.4 `Bahdanau Attention和 Luong Attenion`的区别 105 | 106 | 区别在于两个地方: 107 | 108 | 1. attention的计算数据和位置 109 | 110 | 1. `Bahdanau Attention`会使用`前一次的隐藏`状态来计算attention weight,所以我们会在代码中的GRU之前使用attention的操作,同时会把attention的结果和word embedding的结果进行concat,作为GRU的输出(参考的是pytorch Toritul)。Bahdanau使用的是双向的GRU,会使用正反的encoder的output的concat的结果作为encoder output,如下图所示 111 | - ![](../images/2.3/Bahdanau.png) 112 | 2. `Luong Attenion`使用的是`当前一次的decoder的output`来计算得到attention weight,所以在代码中会在GRU的后面进行attention的操作,同时会把`context vector`和gru的结果进行concat的操作,最终的output。Luong使用的是多层GRU,只会使用最后一层的输出(encoder output) 113 | - ![](../images/2.3/Luong.png) 114 | 115 | 2. 计算attention weights的方法不同 116 | 117 | 1. `Bahdanau Attention`的match函数,$a_i^j = v^T_a tanh (W_aZ_{i-1},+U_ah_j)​$,计算出所有的$a_i^j​$之后,在计算softmax,得到$\hat{a}_i^j​$,即$\hat{a}_i^j = \frac{exp(a_i^j)}{\sum exp(a_i^j)}​$ 118 | 119 | 其中 120 | 1. $v_a^T是一个参数矩阵,需要被训练,W_a是实现对Z_{i-1}的形状变化​$, 121 | 2. $U_a实现对h_j的形状变化(矩阵乘法,理解为线性回归,实现数据形状的对齐)​$, 122 | 3. $Z_{i-1}是decoder端前一次的隐藏状态,h_j是encoder的output​$ 123 | 124 | 2. `Luong Attenion`整体比`Bahdanau Attention`更加简单,他使用了三种方法来计算得到权重 125 | 126 | 1. 矩阵乘法:general 127 | - 直接对decoder的隐藏状态进行一个矩阵变换(线性回归),然后和encoder outputs进行矩阵乘法 128 | 2. dot 129 | - 直接对decoder的隐藏状态和encoder outputs进行矩阵乘法 130 | 3. concat 131 | - 把decoder的隐藏状态和encoder的output进行concat,把这个结果使用tanh进行处理后的结果进行对齐计算之后,和encoder outputs进行矩阵乘法 132 | 4. ![](../images/2.3/scores.png) 133 | - $h_t\text{是当前的decoder hidden state,}h_s\text{是所有的encoder 的hidden state(encoder outputs)}$ 134 | 135 | 136 | 最终两个attention的结果区别并不太大,所以以后我们可以考虑使用Luong attention完成代码 137 | 138 | ## 3. Attention的代码实现 139 | 140 | 完成代码之前,我们需要确定我们的思路,通过attention的代码,需要实现计算的是attention weight 141 | 142 | 通过前面的学习,我们知道`attention_weight = f(hidden,encoder_outputs)`,主要就是实现Luong attention中的三种操作 143 | 144 | ![](../images/2.3/attention6.png) 145 | 146 | ```python 147 | class Attention(nn.Module): 148 | def __init__(self,method,batch_size,hidden_size): 149 | super(Attention,self).__init__() 150 | self.method = method 151 | self.hidden_size = hidden_size 152 | 153 | assert self.method in ["dot","general","concat"],"method 只能是 dot,general,concat,当前是{}".format(self.method) 154 | 155 | if self.method == "dot": 156 | pass 157 | elif self.method == "general": 158 | self.Wa = nn.Linear(hidden_size,hidden_size,bias=False) 159 | elif self.method == "concat": 160 | self.Wa = nn.Linear(hidden_size*2,hidden_size,bias=False) 161 | self.Va = nn.Parameter(torch.FloatTensor(batch_size,hidden_size)) 162 | 163 | def forward(self, hidden,encoder_outputs): 164 | """ 165 | :param hidden:[1,batch_size,hidden_size] 166 | :param encoder_outputs: [batch_size,seq_len,hidden_size] 167 | :return: 168 | """ 169 | batch_size,seq_len,hidden_size = encoder_outputs.size() 170 | 171 | hidden = hidden.squeeze(0) #[batch_size,hidden_size] 172 | 173 | if self.method == "dot": 174 | return self.dot_score(hidden,encoder_outputs) 175 | elif self.method == "general": 176 | return self.general_score(hidden,encoder_outputs) 177 | elif self.method == "concat": 178 | return self.concat_score(hidden,encoder_outputs) 179 | 180 | def _score(self,batch_size,seq_len,hidden,encoder_outputs): 181 | # 速度太慢 182 | # [batch_size,seql_len] 183 | attn_energies = torch.zeros(batch_size,seq_len).to(config.device) 184 | for b in range(batch_size): 185 | for i in range(seq_len): 186 | #encoder_output : [batch_size,seq_len,hidden_size] 187 | #deocder_hidden :[batch_size,hidden_size] 188 | #torch.Size([256, 128]) torch.Size([128]) torch.Size([256, 24, 128]) torch.Size([128]) 189 | # print("attn size:",hidden.size(),hidden[b,:].size(),encoder_output.size(),encoder_output[b,i].size()) 190 | attn_energies[b,i] = hidden[b,:].dot(encoder_outputs[b,i]) #dot score 191 | return F.softmax(attn_energies).unsqueeze(1) # [batch_size,1,seq_len] 192 | 193 | def dot_score(self,hidden,encoder_outputs): 194 | """ 195 | dot attention 196 | :param hidden:[batch_size,hidden_size] --->[batch_size,hidden_size,1] 197 | :param encoder_outputs: [batch_size,seq_len,hidden_size] 198 | :return: 199 | """ 200 | #hiiden :[hidden_size] -->[hidden_size,1] ,encoder_output:[seq_len,hidden_size] 201 | 202 | 203 | hidden = hidden.unsqueeze(-1) 204 | attn_energies = torch.bmm(encoder_outputs, hidden) 205 | attn_energies = attn_energies.squeeze(-1) #[batch_size,seq_len,1] ==>[batch_size,seq_len] 206 | 207 | return F.softmax(attn_energies).unsqueeze(1) # [batch_size,1,seq_len] 208 | 209 | def general_score(self,hidden,encoder_outputs): 210 | """ 211 | general attenion 212 | :param batch_size:int 213 | :param hidden: [batch_size,hidden_size] 214 | :param encoder_outputs: [batch_size,seq_len,hidden_size] 215 | :return: 216 | """ 217 | x = self.Wa(hidden) #[batch_size,hidden_size] 218 | x = x.unsqueeze(-1) #[batch_size,hidden_size,1] 219 | attn_energies = torch.bmm(encoder_outputs,x).squeeze(-1) #[batch_size,seq_len,1] 220 | return F.softmax(attn_energies,dim=-1).unsqueeze(1) # [batch_size,1,seq_len] 221 | 222 | def concat_score(self,hidden,encoder_outputs): 223 | """ 224 | concat attention 225 | :param batch_size:int 226 | :param hidden: [batch_size,hidden_size] 227 | :param encoder_outputs: [batch_size,seq_len,hidden_size] 228 | :return: 229 | """ 230 | #需要先进行repeat操作,变成和encoder_outputs相同的形状,让每个batch有seq_len个hidden_size 231 | x = hidden.repeat(1,encoder_outputs.size(1),1) ##[batch_size,seq_len,hidden_size] 232 | x = torch.tanh(self.Wa(torch.cat([x,encoder_outputs],dim=-1))) #[batch_size,seq_len,hidden_size*2] --> [batch_size,seq_len,hidden_size] 233 | #va [batch_size,hidden_size] ---> [batch_size,hidden_size,1] 234 | attn_energis = torch.bmm(x,self.Va.unsqueeze(2)) #[batch_size,seq_len,1] 235 | attn_energis = attn_energis.squeeze(-1) 236 | # print("concat attention:",attn_energis.size(),encoder_outputs.size()) 237 | return F.softmax(attn_energis,dim=-1).unsqueeze(1) #[batch_size,1,seq_len] 238 | ``` 239 | 240 | 241 | 242 | 完成了`attention weight`的计算之后,需要再对代码中`forward_step`的内容进行修改 243 | 244 | ```python 245 | def forward_step(self,decoder_input,decoder_hidden,encoder_outputs): 246 | """ 247 | :param decoder_input:[batch_size,1] 248 | :param decoder_hidden: [1,batch_size,hidden_size] 249 | :param encoder_outputs: encoder中所有的输出,[batch_size,seq_len,hidden_size] 250 | :return: out:[batch_size,vocab_size],decoder_hidden:[1,batch_size,didden_size] 251 | """ 252 | embeded = self.embedding(decoder_input) #embeded: [batch_size,1 , embedding_dim] 253 | 254 | #TODO 可以把embeded的结果和前一次的context(初始值为全0tensor) concate之后作为结果 255 | #rnn_input = torch.cat((embeded, last_context.unsqueeze(0)), 2) 256 | 257 | # gru_out:[256,1, 128] decoder_hidden: [1, batch_size, hidden_size] 258 | gru_out,decoder_hidden = self.gru(embeded,decoder_hidden) 259 | gru_out = gru_out.squeeze(1) 260 | 261 | #TODO 注意:如果是单层,这里使用decoder_hidden没问题(output和hidden相同) 262 | # 如果是多层,可以使用GRU的output作为attention的输入 263 | #开始使用attention 264 | attn_weights = self.attn(decoder_hidden,encoder_outputs) 265 | # attn_weights [batch_size,1,seq_len] * [batch_size,seq_len,hidden_size] 266 | context = attn_weights.bmm(encoder_outputs) #[batch_size,1,hidden_size] 267 | 268 | gru_out = gru_out.squeeze(0) # [batch_size,hidden_size] 269 | context = context.squeeze(1) # [batch_size,hidden_size] 270 | #把output和attention的结果合并到一起 271 | concat_input = torch.cat((gru_out, context), 1) #[batch_size,hidden_size*2] 272 | 273 | concat_output = torch.tanh(self.concat(concat_input)) #[batch_size,hidden_size] 274 | 275 | output = F.log_softmax(self.fc(concat_output),dim=-1) #[batch_Size, vocab_size] 276 | # out = out.squeeze(1) 277 | return output,decoder_hidden,attn_weights 278 | ``` 279 | 280 | 281 | 282 | attetnion的Bahdanau实现可以参考:https://github.com/spro/practical-pytorch/blob/master/seq2seq-translation/seq2seq-translation.ipynb -------------------------------------------------------------------------------- /2.3 Seq2Seq模型和闲聊机器人/2.3.5 BeamSearch的原理和实现.md: -------------------------------------------------------------------------------- 1 | # Beam Search 2 | 3 | ## 目标 4 | 5 | 1. 知道beam search的概念和原理 6 | 2. 能够在代码中使用Beam search 完成预测过程 7 | 8 | 9 | 10 | ## 1. Beam Search的介绍 11 | 12 | > 在进行模型评估的过程中,每次我们选择概率最大的token id作为输出,那么整个输出的句子的概率就是最大的么? 13 | > 14 | > ![](../images/2.3/greedy search.png) 15 | 16 | `Beam search`的又被称作`束集搜索`,是一种seq2seq中用来优化输出结果的算法(不在训练过程中使用)。 17 | 18 | 例如:传统的获取解码器输出的过程中,每次只选择概率最大的那个结果,作为当前时间步的输出,等到输出结束,我们会发现,整个句子可能并不通顺。虽然在每一个时间步上的输出确实是概率最大的,但是整体的概率确不一定最大的,我们经常把它叫做`greedy search[贪心算法]` 19 | 20 | 为了解决上述的问题,可以考虑计算全部的输出的概率乘积,选择最大的哪一个,但是这样的话,意味着如果句子很长,候选词很多,那么需要保存的数据就会非常大,需要计算的数据量就很大 21 | 22 | 那么Beam Search 就是介于上述两种方法的一个这种的方法,假设Beam width=2,表示每次保存的最大的概率的个数,这里每次保存两个,在下一个时间步骤一样,也是保留两个,这样就可以达到约束搜索空间大小的目的,从而提高算法的效率。 23 | 24 | beam width =1 时,就是贪心算法,beam width=候选词的时候,就是计算全部的概率。beam width 是一个超参数。 25 | 26 | 比如在下图中: 27 | 28 | 使用一个树状图来表示每个time step的可能输出,其中的数字表示是条件概率 29 | 30 | 黄色的箭头表示的是一种greedy search,概率并不是最大的 31 | 32 | 如果把beam width设置为2,那么后续可以找到绿色路径的结果,这个结果是最大的 33 | 34 | ![](../images/2.3/greedy search.png) 35 | 36 | 下图是要给beam width=3的例子 37 | 38 | 1. 首先输入`start token `,然后得到四个输出(这里假设一个就四个输出:`x,y,z,`),选择概率最大三个,x,y,w 39 | 2. 然后分别把x,y,z放到下一个time step中作为输入,分别得到三个不同的输出,找到三个输出中概率最大的三个,x,y,y 40 | 3. 继续重复上述步骤,直到获得结束符(概率最大)或者是达到句子的最大长度,那么此时选择概率乘积最大的一个。 41 | 4. 拼接整个路径上概率最大的所有结果,比如这里可能是`,y,y,x,w,` 42 | 43 | ![](../images/2.3/beamsearch.png) 44 | 45 | 46 | 47 | ## 2. Beam serach的实现 48 | 49 | 在上述描述的思路中,我们需要注意以下几个内容: 50 | 51 | 1. 数据该如何保存,每一次的输出的最大的beam width个结果,和之后之前的结果该如何保存 52 | 2. 保存了之后的概率应该如何比较大小,保留下概率最大的三个 53 | 3. 不能够仅仅只保存当前概率最大的信息,还需要有当前概率最大的三个中,前面的路径的输出结果 54 | 55 | ### 2.1 数据结构-堆-的认识 56 | 57 | 对于上面所说的,保留有限个数据,同时需要根据大小来保留,可以使用一种带有优先级的数据结构来实现,这里我们可以使用`堆`这种数据结构 58 | 59 | `堆`是一种优先级的队列,但是他其实并不是队列,我们常说的队列都是`先进先出或者是先进后出`,但是`堆`只根据优先级的高低来取出数据。 60 | 61 | 和`堆`在一起的另外一种数据结构叫做`栈`,有入栈和出栈的操作,可以理解为是一种先进后出的数据结构,关于栈,大家可以下来在了解。 62 | 63 | 在python自带的模块中,有一个叫做`heapq`的模块,提供了堆所有的方法。通过下面的代码我们来了解下heapq的使用方法 64 | 65 | ```python 66 | my_heap = [] #使用列表保存数据 67 | 68 | #往列表中插入数据,优先级使用插入的内容来表示,就是一个比较大小的操作,越大优先级越高 69 | heapq.heappush(my_heap,[29,True,"xiaohong"]) 70 | heapq.heappush(my_heap,[28,False,"xiaowang"]) 71 | heapq.heappush(my_heap,[29,False,"xiaogang"]) 72 | 73 | for i in range(3): 74 | ret= heapq.heappop(my_heap) #pop操作,优先级最小的数据 75 | print(ret) 76 | 77 | #输出如下: 78 | [28, False, 'xiaowang'] 79 | [29, False, 'xiaogang'] 80 | [29, True, 'xiaohong'] 81 | ``` 82 | 83 | 可以发现,输出的顺序并不是数据插入的顺序,而是根据其优先级,从小往大pop(Falseself.beam_width: 108 | heapq.heappop(self.heap) 109 | 110 | def __iter__(self):#让该beam能够被迭代 111 | return iter(self.heap) 112 | ``` 113 | 114 | 实现方法,完成模型eval过程中的beam search搜索 115 | 116 | 思路: 117 | 118 | 1. 构造``开始符号等第一次输入的信息,保存在堆中 119 | 2. 取出堆中的数据,进行forward_step的操作,获得当前时间步的output,hidden 120 | 3. 从output中选择topk(k=beam width)个输出,作为下一次的input 121 | 4. 把下一个时间步骤需要的输入等数据保存在一个新的堆中 122 | 5. 获取新的堆中的优先级最高(概率最大)的数据,判断数据是否是EOS结尾或者是否达到最大长度,如果是,停止迭代 123 | 6. 如果不是,则重新遍历新的堆中的数据 124 | 125 | 126 | 127 | 代码如下 128 | 129 | ```python 130 | # decoder中的新方法 131 | def evaluatoin_beamsearch_heapq(self,encoder_outputs,encoder_hidden): 132 | """使用 堆 来完成beam search,对是一种优先级的队列,按照优先级顺序存取数据""" 133 | 134 | batch_size = encoder_hidden.size(1) 135 | #1. 构造第一次需要的输入数据,保存在堆中 136 | decoder_input = torch.LongTensor([[word_sequence.SOS] * batch_size]).to(config.device) 137 | decoder_hidden = encoder_hidden #需要输入的hidden 138 | 139 | prev_beam = Beam() 140 | prev_beam.add(1,False,[decoder_input],decoder_input,decoder_hidden) 141 | while True: 142 | cur_beam = Beam() 143 | #2. 取出堆中的数据,进行forward_step的操作,获得当前时间步的output,hidden 144 | #这里使用下划线进行区分 145 | for _probility,_complete,_seq,_decoder_input,_decoder_hidden in prev_beam: 146 | #判断前一次的_complete是否为True,如果是,则不需要forward 147 | #有可能为True,但是概率并不是最大 148 | if _complete == True: 149 | cur_beam.add(_probility,_complete,_seq,_decoder_input,_decoder_hidden) 150 | else: 151 | decoder_output_t, decoder_hidden,_ = self.forward_step(_decoder_input, _decoder_hidden,encoder_outputs) 152 | value, index = torch.topk(decoder_output_t, config.beam_width) # [batch_size=1,beam_widht=3] 153 | #3. 从output中选择topk(k=beam width)个输出,作为下一次的input 154 | for m, n in zip(value[0], index[0]): 155 | decoder_input = torch.LongTensor([[n]]).to(config.device) 156 | seq = _seq + [n] 157 | probility = _probility * m 158 | if n.item() == word_sequence.EOS: 159 | complete = True 160 | else: 161 | complete = False 162 | 163 | #4. 把下一个实践步骤需要的输入等数据保存在一个新的堆中 164 | cur_beam.add(probility,complete,seq, 165 | decoder_input,decoder_hidden) 166 | #5. 获取新的堆中的优先级最高(概率最大)的数据,判断数据是否是EOS结尾或者是否达到最大长度,如果是,停止迭代 167 | best_prob,best_complete,best_seq,_,_ = max(cur_beam) 168 | if best_complete == True or len(best_seq)-1 == config.max_len: #减去sos 169 | return self._prepar_seq(best_seq) 170 | else: 171 | #6. 则重新遍历新的堆中的数据 172 | prev_beam = cur_beam 173 | 174 | def _prepar_seq(self,seq):#对结果进行基础的处理,共后续转化为文字使用 175 | if seq[0].item() == word_sequence.SOS: 176 | seq= seq[1:] 177 | if seq[-1].item() == word_sequence.EOS: 178 | seq = seq[:-1] 179 | seq = [i.item() for i in seq] 180 | return seq 181 | ``` 182 | 183 | ### 2.3 修改seq2seq 184 | 185 | 在seq2seq中使用evaluatoin_beamsearch_heapq查看效果,会发现使用beam search的效果比单独使用attention的效果更好 186 | 187 | 使用小黄鸡语料(50万个问答),单个字作为token,5个epoch之后的训练结果,左边为问,右边是回答 188 | 189 | ```python 190 | 你在干什么 >>>>> 你想干啥? 191 | 你妹 >>>>> 不是我 192 | 你叫什么名字 >>>>> 你猜 193 | 你个垃圾 >>>>> 你才是,你 194 | 你是傻逼 >>>>> 是你是傻 195 | 笨蛋啊 >>>>> 我不是,你 196 | ``` 197 | -------------------------------------------------------------------------------- /2.3 Seq2Seq模型和闲聊机器人/2.3.6 闲聊机器人的优化.md: -------------------------------------------------------------------------------- 1 | # 闲聊机器人的优化 2 | 3 | ## 目标 4 | 5 | 1. 知道如何优化模型的效果 6 | 2. 知道常见的优化手段 7 | 8 | 9 | 10 | ## 1. seq2seq中使用`teacher forcing` 11 | 12 | 在前面的seq2seq的案例中,我们介绍了`teacher frocing`是什么,当时我们的输入和输出很相似,所以当时我们的`teacher forcing`是在每个time step中实现的,那么现在我们的输入和输出不同的情况下,该如何使用呢? 13 | 14 | 我们可以在每个batch遍历time step的外层使用`teacher forcing` 15 | 16 | 代码如下: 17 | 18 | ```python 19 | use_teacher_forcing = random.random() > 0.5 20 | if use_teacher_forcing: #使用teacher forcing 21 | for t in range(config.max_len): 22 | decoder_output_t, decoder_hidden, decoder_attn_t = self.forward_step(decoder_input, decoder_hidden, 23 | encoder_outputs) 24 | decoder_outputs[:, t, :] = decoder_output_t 25 | #使用正确的输出作为下一步的输入 26 | decoder_input = target[:, t].unsqueeze(1) # [batch_size,1] 27 | 28 | else:#不适用teacher forcing,使用预测的输出作为下一步的输入 29 | for t in range(config.max_len): 30 | decoder_output_t ,decoder_hidden,decoder_attn_t = self.forward_step(decoder_input,decoder_hidden,encoder_outputs) 31 | decoder_outputs[:,t,:] = decoder_output_t 32 | value, index = torch.topk(decoder_output_t, 1) # index [batch_size,1] 33 | decoder_input = index 34 | ``` 35 | 36 | ## 2. 使用梯度裁剪 37 | 38 | 前面,我们给大家介绍了`梯度消失(梯度过小,在多层计算后导致其值太小而无法计算)`和`梯度爆炸(梯度过大,导致其值在多层的计算后太大而无法计算)`。 39 | 40 | 在常见的深度神经网络中,特别是RNN中,我们经常会使用`梯度裁剪`的手段,来抑制过大的梯度,能够有效防止梯度爆炸。 41 | 42 | 梯度裁剪的实现非常简单,仅仅只需要设置一个阈值,把梯度大于该阈值时设置为该阈值。 43 | 44 | ![](../images/2.3/grad_clip.png) 45 | 46 | 实现代码: 47 | 48 | ```python 49 | loss.backward() 50 | #进行梯度裁剪 51 | nn.utils.clip_grad_norm_(model.parameters(),[5,10,15]) 52 | optimizer.step() 53 | ``` 54 | 55 | ## 3. 其他优化方法 56 | 57 | 1. 根据特定的问题,使用分类模型进行训练,然后再训练单独的回个该为题的为模型 58 | - 比如询问名字,可以使用fasttext先进行意图识别,命中`询问名字`分类后,直接返回名字 59 | - 或者是手动构造和名字相关的很多问题,来进行训练,从而能够更加个性化的回答出结果 60 | 2. 直接对现有的语料进行修改和清洗,把语料中更多的答案进行替换,比如咨询名字的,咨询天气的等,这样能够更大程度上的回答出更加规范的答案 61 | 3. 使用2.4 会讲的搜索模型,不再使用这种生成模型 -------------------------------------------------------------------------------- /2.3 Seq2Seq模型和闲聊机器人/README.md: -------------------------------------------------------------------------------- 1 | # 2.3 Seq2Seq模型和闲聊机器人 -------------------------------------------------------------------------------- /2.4 QA机器人/2.4.1 QA机器人介绍.md: -------------------------------------------------------------------------------- 1 | # 问答机器人介绍 2 | 3 | ## 目标 4 | 5 | 1. 知道问答机器人是什么 6 | 2. 知道问答机器人实现的逻辑 7 | 8 | 9 | 10 | ## 1. 问答机器人 11 | 12 | 在前面的课程中,我们已经对问答机器人介绍过,这里的问答机器人是我们在分类之后,对特定问题进行回答的一种机器人。至于回答的问题的类型,取决于我们的语料。 13 | 14 | 当前我们需要实现的问答机器人是一个回答编程语言(比如`python是什么`,`python难么`等)相关问题的机器人 15 | 16 | 17 | 18 | ## 2. 问答机器人的实现逻辑 19 | 20 | 主要实现逻辑:从现有的问答对中,选择出和问题最相似的问题,并且获取其相似度(一个数值),如果相似度大于阈值,则返回这个最相似的问题对应的答案 21 | 22 | 问答机器人的实现可以大致分为三步步骤: 23 | 24 | 1. 对问题的处理 25 | 2. 对答案进行的机器学习召回 26 | 3. 对召回的结果进行排序 27 | 28 | ### 2.1 对问题的处理 29 | 30 | 对问题的处理过程中,我们可以考虑以下问题: 31 | 32 | 1. 对问题进行基础的清洗,去除特殊符号等 33 | 2. 问题主语的识别,判断问题中是否包含特定的主语,比如`python`等,提取出来之后,方便后续对问题进行过滤。 34 | - 可以看出,不仅需要对用户输入的问题进行处理,获取主语,还需要对现有问答对进行处理 35 | 3. 获取问题的词向量,可以考虑使用词频,tdidf等值,方便召回的时候使用 36 | 37 | ### 2.2 问题的召回 38 | 39 | 召回:可以理解为是一个海选的操作,就是从现有的问答对中选择可能相似的前K个问题。 40 | 41 | 为什么要进行召回? 42 | 43 | > 主要目的是为了后续进行排序的时候,减少需要计算的数据量,比如有10万个问答对,直接通过深度学习肯定是可以获取所有的相似度,但是速度慢。 44 | > 45 | > 所以考虑使用机器学习的方法进行一次海选 46 | 47 | 那么,如何实现召回呢? 48 | 49 | > 前面我们介绍,召回就是选择前K个最相似的问题,所以召回的实现就是想办法通过机器学习的手段计算器相似度。 50 | 51 | 可以思考的方法: 52 | 53 | 1. 使用词袋模型,获取词频矩阵,计算相似度 54 | 2. 使用tfidf,获取tdidf的矩阵,计算相似度 55 | 56 | 上述的方法理论上都可行,知识当候选计算的词语数量太多的时候,需要挨个计算相似度,非常耗时。 57 | 58 | 所以可以考虑以下两点: 59 | 60 | 1. 通过前面获取的主语,对问题进行过滤 61 | 2. 使用聚类的方法,对数据先聚类,再计算某几个类别中的相似度,而不用去计算全部。 62 | 63 | 但是还有一个问题,供大家慢慢思考: 64 | 65 | > 不管是词频,还是tdidf,获取的结果肯定是没有考虑文字顺序的,效果不一定是最好的,那么此时,应该如何让最后召回的效果更好呢? 66 | > 67 | 68 | ### 2.3 问题的排序 69 | 70 | 排序过程,使用了召回的结果作为输入,同时输出的是最相似的那一个。 71 | 72 | 整个过程使用深度学习实现。深度学习虽然训练的速度慢,但是整体效果肯定比机器学习好(机器学习受限于特征工程,数据量等因素,没有办法深入的学会不同问题之间的内在相似度),所以通过自建的模型,获取最后的相似度。 73 | 74 | 使用深度学习的模型这样一个黑匣子,在训练数据足够多的时候,能够学习到用户的各种不同输入的问题,当我们把目标值(相似的问题)给定的情况下,让模型自己去找到这些训练数据目标值和特征值之间相似的表示方法。 75 | 76 | 那么此时,有以下两个问题: 77 | 78 | 1. 使用什么数据,来训练模型,最后返回模型的相似度 79 | 80 | >训练的数据的来源:可以考虑根据现有的问答对去手动构造,但是构造的数据不一定能够覆盖后续用户提问的全部问题。所以可以考虑通过程序去采集网站上相似的问题,比如百度知道的搜索结果。 81 | 82 | 2. 模型该如何构建 83 | 84 | >模型可以有两个输入,输出为一个数值,两个输入的处理方法肯定是一样的。这种网络结构我们经常把它称作孪生神经网络。 85 | 86 | 很明显,我们队输入的数据需要进行编码的操作,比如word embedding + LSTM/GRU/BIGRU等 87 | 88 | 两个编码之后的结果,我们可以进行组合,然后通过一个多层的神经网络,输出一个数字,把这个数值定义为我们的相似度。 89 | 90 | 当然我们的深层的神经网络在最开始的时候也并不是计算的相似度,但是我们的训练数据的目标值是相似度,在N多次的训练之后,确定了输入和输出的表示方法之后,那么最后的模型输出就是相似度了。 91 | 92 | 93 | 94 | 前面我们介绍了问答机器人的实现的大致思路,那么接下来,我们就来一步步的实现它 95 | 96 | -------------------------------------------------------------------------------- /2.4 QA机器人/2.4.2 QA机器人的召回.md: -------------------------------------------------------------------------------- 1 | # 问答机器人的召回 2 | 3 | ## 目标 4 | 5 | 1. 知道召回的目的 6 | 2. 能够说出召回的流程 7 | 3. 能够优化基础的召回逻辑 8 | 9 | 10 | 11 | ## 1. 召回的流程 12 | 13 | 流程如下: 14 | 15 | 1. 准备数据,问答对的数据等 16 | 2. 问题转化为向量 17 | 3. 计算相似度 18 | 19 | 20 | 21 | ## 2. 对现有问答对的准备 22 | 23 | 这里说的问答对,是带有标准答案的问题,后续命中问答对中的问题后,会返回该问题对应的答案 24 | 25 | 为了后续使用方便,我们可以把现有问答对的处理成如下的格式,可以考虑存入数据库或者本地文件: 26 | 27 | ```python 28 | { 29 | "问题1":{ 30 | "主体":["主体1","主体3","主体3"..], 31 | "问题1分词后的句子":["word1","word2","word3"...], 32 | "答案":"答案" 33 | }, 34 | "问题2":{ 35 | ... 36 | } 37 | } 38 | ``` 39 | 40 | 代码如下: 41 | 42 | ```python 43 | # lib/get_qa_dcit.py 44 | def get_qa_dict(): 45 | chuanzhi_q_path = "./问答对/Q.txt" 46 | chuanzhi_a_path = "./问答对/A.txt" 47 | QA_dict = {} 48 | for q,a in zip(open(chuanzhi_q_path).readlines(),open(chuanzhi_a_path).readlines()): 49 | QA_dict[q.strip()] = {} 50 | QA_dict[q.strip()]["ans"] = a.strip() 51 | QA_dict[q.strip()]["entity"] = sentence_entity(q.strip())[-1] 52 | 53 | #准备短问答的问题 54 | python_duan_path = "./data/Python短问答-11月汇总.xlsx" 55 | 56 | ret = pd.read_excel(python_duan_path) 57 | column_list = ret.columns 58 | assert '问题' in column_list and "答案" in column_list, "excel 中必须包含问题和答案" 59 | for q, a in zip(ret["问题"], ret["答案"]): 60 | q = re.sub("\s+", " ", q) 61 | QA_dict[q.strip()] = {} 62 | QA_dict[q.strip()]["ans"] = a 63 | cuted,entiry = sentence_entity(q.strip())[-1] 64 | QA_dict[q.strip()]["entity"] = entiry 65 | QA_dict[q.strip()]["q_cuted"] = cuted 66 | 67 | return QA_dict 68 | 69 | QA_dict = get_qa_dict() 70 | ``` 71 | 72 | ## 3. 把问题转化为向量 73 | 74 | 把问答对中的问题,和用户输出的问题,转化为向量,为后续计算相似度做准备。 75 | 76 | 这里,我们使用tfidf对问答对中的问题进行处理,转化为向量矩阵。 77 | 78 | > TODO,使用单字,使用n-garm,使用BM25,使用word2vec等,让其结果更加准确 79 | 80 | ```python 81 | from sklearn.feature_extraction.text import TfidfVectorizer 82 | from lib import QA_dict 83 | 84 | def build_q_vectors(): 85 | """对问题建立索引""" 86 | lines_cuted= [q["q_cuted"] for q in QA_dict] 87 | tfidf_vectorizer = TfidfVectorizer() 88 | features_vec = tfidf_vectorizer.fit_transform(lines_cuted) 89 | #返回tfidf_vectorizer,后续还需要对用户输入的问题进行同样的处理 90 | return tfidf_vectorizer,features_vec,lines_cuted 91 | ``` 92 | 93 | ## 4. 计算相似度 94 | 95 | 思路很简单。对用户输入的问题使用`tfidf_vectorizer`进行处理,然后和`features_vec`中的每一个结果进行计算,获取相似度。 96 | 97 | 但是由于耗时可能会很久,所以考虑使用其他方法来实现 98 | 99 | ### 4.1 `pysparnn`的介绍 100 | 101 | 官方地址:`https://github.com/facebookresearch/pysparnn` 102 | 103 | `pysparnn`是一个对sparse数据进行相似邻近搜索的python库,这个库是用来实现 高维空间中寻找最相似的数据的。 104 | 105 | ### 4.2 `pysparnn`的使用方法 106 | 107 | pysparnn的使用非常简单,仅仅需要以下步骤,就能够完成从高维空间中寻找相似数据的结果 108 | 109 | 1. 准备源数据和待搜索数据 110 | 2. 对源数据进行向量化,把向量结果和源数据构造搜索的索引 111 | 3. 对待搜索的数据向量化,传入索引,获取结果 112 | 113 | ```python 114 | import pysparnn.cluster_index as ci 115 | 116 | from sklearn.feature_extraction.text import TfidfVectorizer 117 | 118 | #1. 原始数据 119 | data = [ 120 | 'hello world', 121 | 'oh hello there', 122 | 'Play it', 123 | 'Play it again Sam', 124 | ] 125 | 126 | #2. 原始数据向量化 127 | 128 | tv = TfidfVectorizer() 129 | tv.fit(data) 130 | 131 | features_vec = tv.transform(data) 132 | 133 | # 原始数据构造索引 134 | cp = ci.MultiClusterIndex(features_vec, data) 135 | 136 | # 待搜索的数据向量化 137 | search_data = [ 138 | 'oh there', 139 | 'Play it again Frank' 140 | ] 141 | 142 | search_features_vec = tv.transform(search_data) 143 | 144 | #3. 索引中传入带搜索数据,返回结果 145 | cp.search(search_features_vec, k=1, k_clusters=2, return_distance=False) 146 | >> [['oh hello there'], ['Play it again Sam']] 147 | ``` 148 | 149 | 使用注意点: 150 | 151 | 1. 构造索引是需要传入向量和原数据,最终的结果会返回源数据 152 | 2. 传入待搜索的数据时,需要传入一下几个参数: 153 | 1. `search_features_vec`:搜索的句子的向量 154 | 2. `k`:最大的几个结果,k=1,返回最大的一个 155 | 3. `k_clusters`:对数据分为多少类进行搜索 156 | 4. `return_distance`:是否返回距离 157 | 158 | ### 4.3 使用pysparnn完成召回的过程 159 | 160 | ```python 161 | 162 | #构造索引 163 | cp = ci.MultiClusterIndex(features_vec, lines_cuted) 164 | 165 | #对用户输入的句子进行向量化 166 | search_vec = tfidf_vec.transform(ret) 167 | #搜索获取结果,返回最大的8个数据,之后根据`main_entiry`进行过滤结果 168 | cp_search_list = cp.search(search_vec, k=8, k_clusters=10, return_distance=True) 169 | 170 | exist_same_entiry = False 171 | search_lsit = [] 172 | for _temp_call_line in cp_search_list[0]: 173 | cur_entity = QA_dict[_temp_call_line[1]]["main_entity"] 174 | if len(set(main_entity) & set(cur_entity))>0: #命名体的集合存在交集的时候返回 175 | exist_same_entiry = True 176 | search_lsit.append(_temp_call_line[1]) 177 | 178 | if exist_same_entiry: #存在相同的主体的时候 179 | return search_lsit 180 | else: 181 | # print(cp_search_list) 182 | return [i[1] for i in cp_search_list[0]] 183 | 184 | ``` 185 | 186 | 在这个过程中,需要注意,提前把`cp,tfidf_vec`等内容提前准备好,而不应该在每次接收到用户的问题之后重新生成一遍,否则效率会很低 187 | 188 | ### 4.4 `pysparnn`的原理介绍 189 | 190 | 参考地址:`https://nlp.stanford.edu/IR-book/html/htmledition/cluster-pruning-1.html` 191 | 192 | 前面我们使用的`pysparnn`使用的是一种`cluster pruning(簇修剪)`的技术,即,开始的时候对数据进行聚类,后续再有限个类别中进行数据的搜索,根据计算的余弦相似度返回结果。 193 | 194 | 数据预处理过程如下: 195 | 196 | 1. 随机选择$\sqrt{N}$个样本作为leader 197 | 2. 选择非leader的数据(follower),使用余弦相似度计算找到最近的leader 198 | 199 | 当获取到一个问题q的时候,查询过程: 200 | 201 | 1. 计算每个leader和q的相似度,找到最相似的leader 202 | 2. 然后计算问题q和leader所在簇的相似度,找到最相似的k个,作为最终的返回结果 203 | 204 | ![](../images/2.4/pysparnn.png) 205 | 206 | 207 | 208 | 209 | 210 | 在上述的过程中,可以设置两个大于0的数字`b1和b2` 211 | 212 | - b1表示在`数据预处理`阶段,每个follower选择b1个最相似的leader,而不是选择单独一个lader,这样不同的簇是有数据交叉的; 213 | - b2表示在查询阶段,找到最相似的b2个leader,然后再计算不同的leader中下的topk的结果 214 | 215 | 前面的描述就是b1=b2=1的情况,通过增加`b1和b2`的值,我们能够有更大的机会找到更好的结果,但是这样会需要更加大量的计算。 216 | 217 | 在pysparnn中实例化索引的过程中 218 | 219 | 即:`ci.MultiClusterIndex(features, records_data, num_indexes)`中,`num_indexes`能够设置b1的值,默认为2。 220 | 221 | 在搜索的过程中,`cp.search(search_vec, k=8, k_clusters=10, return_distance=True,num_indexes)`,`num_Indexes`可以设置b2的值,默认等于b1的值。 -------------------------------------------------------------------------------- /2.4 QA机器人/2.4.4 QA机器人排序模型.md: -------------------------------------------------------------------------------- 1 | # 问答机器人排序模型 2 | 3 | ## 目标 4 | 5 | 1. 知道模型中排序中的概念和目的 6 | 2. 知道模型中排序的实现方法 7 | 8 | 9 | 10 | ## 1. 排序模型的介绍 11 | 12 | 前面的课程中为了完成一个问答机器人,我们先进行了召回,相当于是通过海选的方法找到呢大致相似的问题。 13 | 14 | 通过现在的排序模型,我们需要精选出最相似的哪一个问题,返回对应的答案 15 | 16 | 17 | 18 | ## 2. 排序模型的实现思路 19 | 20 | 我们需要实现的排序模型是两个输入,即两个问题,输出的是一个相似度。所以和之前的深度学习模型一样,我们需要实现的步骤如下: 21 | 22 | 1. 准备数据 23 | 2. 构建模型 24 | 3. 模型评估 25 | 4. 对外提供接口返回结果 26 | 27 | ### 2.1 准备数据 28 | 29 | 这里的数据,我们使用之前采集的百度问答的相似问题和手动构造的数据。那么,我们需要把他格式化为最终模型需要的格式,即两个输入和输出的相似度。 30 | 31 | #### 2.1.1 两个输入 32 | 33 | 这里的输入,我们可以使用单个字作为特征,也可以使用一个分词之后的词语作为特征。所以在实现准备输入数据方法的过程中,可以提前准备。 34 | 35 | #### 2.1.2 相似度准备 36 | 37 | 这里我们使用每个问题搜索结果的前两页认为他们是相似的,相似度为1,最后两页的结果是不相似的,相似度为0。 38 | 39 | 40 | 41 | ### 2.2 构建模型 42 | 43 | 介绍模型的构建之前,我们先介绍下孪生神经网络(Siamese Network)和其名字的由来。 44 | 45 | Siamese和Chinese有点像。Siamese是古时候泰国的称呼,中文译作暹罗。Siamese在英语中是“孪生”、“连体”的意思。为什么孪生和泰国有关系呢? 46 | 47 | > 十九世纪泰国出生了一对连体婴儿,当时的医学技术无法使两人分离出来,于是两人顽强地生活了一生,1829年被英国商人发现,进入马戏团,在全世界各地表演,1839年他们访问美国北卡罗莱那州后来成为马戏团的台柱,最后成为美国公民。1843年4月13日跟英国一对姐妹结婚,恩生了10个小孩,昌生了12个,姐妹吵架时,兄弟就要轮流到每个老婆家住三天。1874年恩因肺病去世,另一位不久也去世,两人均于63岁离开人间。两人的肝至今仍保存在费城的马特博物馆内。从此之后“暹罗双胞胎”(Siamese twins)就成了连体人的代名词,也因为这对双胞胎让全世界都重视到这项特殊疾病。 48 | 49 | 所以孪生神经网络就是有两个共享权值的网络的组成,或者只用实现一个,另一个直接调用,有两个输入,一个输出。1993年就已经被用来进行支票签名的验证。 50 | 51 | 孪生神经网络通过两个输入,被DNN进行编码,得到向量的表示之后,根据实际的用途来制定损失函数。比如我们需要计算相似度的时候,可以使用余弦相似度,或者使用$exp^{-||h^{left}-h^{right}||}$来确定向量的距离。 52 | 53 | 孪生神经网络被用于有多个输入和一个输出的场景,比如手写字体识别、文本相似度检验、人脸识别等 54 | 55 | 在计算相似度之前,我们可以考虑在传统的孪生神经网络的基础上,在计算相似度之前,把我们的编码之后的向量通过多层神经网络进行非线性的变化,结果往往会更加好,那么此时其网络结构大致如下: 56 | 57 | ![](../images/2.4/deepqa.png) 58 | 59 | 其中Network1和network2为权重参数共享的两个形状相同的网络,用来对输入的数据进行编码,包括(`word-embedding,GRU,biGRU等`),Network3部分是一个深层的神经网络,包含(`batchnorm、dropout、relu、Linear`等层) 60 | 61 | ### 2.3 模型的评估 62 | 63 | 编写预测和评估的代码,预测的过程只需要修改获得结果,不需要上图中的损失计算的过程 64 | 65 | ## 3. 代码实现 66 | 67 | ### 3.1 数据准备 68 | 69 | ##### 3.1.1 对文本进行分词分开存储 70 | 71 | 这里的分词可以对之前的分词方法进行修改 72 | 73 | ```python 74 | def cut_sentence_by_word(sentence): 75 | # 对中文按照字进行处理,对英文不分为字母 76 | letters = string.ascii_lowercase + "+" + "/" # c++,ui/ue 77 | result = [] 78 | temp = "" 79 | for word in line: 80 | if word.lower() in letters: 81 | temp += word.lower() 82 | else: 83 | if temp != "": 84 | result.append(temp) 85 | temp = "" 86 | result.append(word) 87 | if temp != "": 88 | result.append(temp) 89 | return result 90 | 91 | def jieba_cut(sentence,by_word=False,with_sg=False,use_stopwords=False): 92 | if by_word: 93 | return cut_sentence_by_word(sentence) 94 | ret = psg.lcut(sentence) 95 | if use_stopwords: 96 | ret = [(i.word, i.flag) for i in ret if i.word not in stopwords_list] 97 | if not with_sg: 98 | ret = [i[0] for i in ret] 99 | return ret 100 | ``` 101 | 102 | ##### 3.1.2 准备`word Sequence`代码 103 | 104 | 该处的代码和seq2seq中的代码相同,直接使用 105 | 106 | ##### 3.1.3 准备`Dataset`和`DataLoader` 107 | 108 | 和seq2seq中的代码大致相同 109 | 110 | ### 3.2 模型的搭建 111 | 112 | 前面做好了准备工作之后,就需要开始进行模型的搭建。 113 | 114 | 虽然我们知道了整个结构的大致情况,但是我们还是不知道其中具体的细节。 115 | 116 | 2016年`AAAI`会议上,有一篇`Siamese Recurrent Architectures for Learning Sentence Similarity`的论文(地址:https://www.aaai.org/ocs/index.php/AAAI/AAAI16/paper/download/12195/12023)。整个结构如下图: 117 | 118 | ![](../images/2.4/AAAI.png) 119 | 120 | 121 | 122 | 可以看到word 经过embedding之后进行LSTM的处理,然后经过exp来确定相似度,可以看到整个模型是非常简单的,之后很多人在这个结构上增加了更多的层,比如加入attention、dropout、pooling等层。 123 | 124 | 那么这个时候,请思考下面几个问题: 125 | 126 | 1. attention在这个网络结构中该如何实现 127 | 128 | - 之前我们的attention是用在decoder中,让decoder的hidden和encoder的output进行运算,得到attention的weight,再和decoder的output进行计算,作为下一次decoder的输入 129 | 130 | - 那么在当前我们可以把`句子A的output理解为句子B的encoder的output`,那么我们就可以进行attention的计算了 131 | 132 | > 和这个非常相似的有一个attention的变种,叫做`self attention`。前面所讲的Attention是基于source端和target端的隐变量(hidden state)计算Attention的,得到的结果是源端的每个词与目标端每个词之间的依赖关系。`Self Attention`不同,它分别在source端和target端进行,仅与source input或者target input自身相关的Self Attention,捕捉source端或target端自身的词与词之间的依赖关系。 133 | 134 | 2. dropout用在什么地方 135 | 136 | - dropout可以用在很多地方,比如embedding之后 137 | - BiGRU结构中 138 | - 或者是相似度计算之前 139 | 140 | 3. pooling是什么如何使用 141 | 142 | - pooling叫做池化,是一种降采样的技术,用来减少特征(feature)的数量。常用的方法有`max pooling` 或者是`average pooling` 143 | 144 | 145 | #### 3.2.1 编码部分 146 | 147 | ```python 148 | def forward(self, *input): 149 | 150 | sent1, sent2 = input[0], input[1] 151 | #这里使用mask,在后面计算attention的时候,让其忽略pad的位置 152 | mask1, mask2 = sent1.eq(0), sent2.eq(0) 153 | 154 | # embeds: batch_size * seq_len => batch_size * seq_len * batch_size 155 | x1 = self.embeds(sent1) 156 | x2 = self.embeds(sent2) 157 | 158 | # batch_size * seq_len * dim => batch_size * seq_len * hidden_size 159 | output1, _ = self.lstm1(x1) 160 | output2, _ = self.lstm1(x2) 161 | 162 | # 进行Attention的操作,同时进行形状的对齐 163 | # batch_size * seq_len * hidden_size 164 | q1_align, q2_align = self.soft_attention_align(output1, output2, mask1, mask2) 165 | 166 | # 拼接之后再传入LSTM中进行处理 167 | # batch_size * seq_len * (8 * hidden_size) 168 | q1_combined = torch.cat([output1, q1_align, self.submul(output1, q1_align)], -1) 169 | q2_combined = torch.cat([output2, q2_align, self.submul(output2, q2_align)], -1) 170 | 171 | # batch_size * seq_len * (2 * hidden_size) 172 | q1_compose, _ = self.lstm2(q1_combined) 173 | q2_compose, _ = self.lstm2(q2_combined) 174 | 175 | # 进行Aggregate操作,也就是进行pooling 176 | # input: batch_size * seq_len * (2 * hidden_size) 177 | # output: batch_size * (4 * hidden_size) 178 | q1_rep = self.apply_pooling(q1_compose) 179 | q2_rep = self.apply_pooling(q2_compose) 180 | 181 | # Concate合并到一起,用来进行计算相似度 182 | x = torch.cat([q1_rep, q2_rep], -1) 183 | ``` 184 | 185 | ##### atttention的计算 186 | 187 | 实现思路: 188 | 189 | 1. 先获取attention_weight 190 | 2. 在使用attention_weight和encoder_output进行相乘 191 | 192 | ```python 193 | def soft_attention_align(self, x1, x2, mask1, mask2): 194 | ''' 195 | x1: batch_size * seq_len_1 * hidden_size 196 | x2: batch_size * seq_len_2 * hidden_size 197 | ''' 198 | # attention: batch_size * seq_len_1 * seq_len_2 199 | attention_weight = torch.matmul(x1, x2.transpose(1, 2)) 200 | #mask1 : batch_size,seq_len1 201 | mask1 = mask1.float().masked_fill_(mask1, float('-inf')) 202 | #mask2 : batch_size,seq_len2 203 | mask2 = mask2.float().masked_fill_(mask2, float('-inf')) 204 | 205 | # weight: batch_size * seq_len_1 * seq_len_2 206 | weight1 = F.softmax(attention_weight + mask2.unsqueeze(1), dim=-1) 207 | #batch_size*seq_len_1*hidden_size 208 | x1_align = torch.matmul(weight1, x2) 209 | 210 | #同理,需要对attention_weight进行permute操作 211 | weight2 = F.softmax(attention_weight.transpose(1, 2) + mask1.unsqueeze(1), dim=-1) 212 | x2_align = torch.matmul(weight2, x1) 213 | ``` 214 | 215 | ##### Pooling实现 216 | 217 | 池化的过程有一个`窗口`的概念在其中,所以max 或者是average指的是窗口中的值取最大值还是取平均估值。整个过程可以理解为拿着窗口在源数据上取值 218 | 219 | 窗口有窗口大小(kernel_size,窗口多大)和步长(stride,每次移动多少)两个概念 220 | 221 | - ```python 222 | >>> input = torch.tensor([[[1,2,3,4,5,6,7]]]) 223 | >>> F.avg_pool1d(input, kernel_size=3, stride=2) 224 | tensor([[[ 2., 4., 6.]]]) #[1,2,3] [3,4,5] [5,6,7]的平均估值 225 | ``` 226 | 227 | ![](../images/2.4/pooling.png) 228 | 229 | ```python 230 | def apply_pooling(self, x): 231 | # input: batch_size * seq_len * (2 * hidden_size) 232 | #进行平均池化 233 | p1 = F.avg_pool1d(x.transpose(1, 2), x.size(1)).squeeze(-1) 234 | #进行最大池化 235 | p2 = F.max_pool1d(x.transpose(1, 2), x.size(1)).squeeze(-1) 236 | # output: batch_size * (4 * hidden_size) 237 | return torch.cat([p1, p2], 1) 238 | ``` 239 | 240 | ##### 3.2.2 相似度计算部分 241 | 242 | 相似度的计算我们可以使用一个传统的距离计算公式,或者是exp的方法来实现,但是其效果不一定好,所以这里我们使用一个深层的神经网络来实现,使用pytorch中的Sequential对象来实现非常简单 243 | 244 | ```python 245 | self.fc = nn.Sequential( 246 | nn.BatchNorm1d(self.hidden_size * 8), 247 | nn.Linear(self.hidden_size * 8, self.linear_size), 248 | nn.ELU(inplace=True), 249 | nn.BatchNorm1d(self.linear_size), 250 | nn.Dropout(self.dropout), 251 | 252 | nn.Linear(self.linear_size, self.linear_size), 253 | nn.ELU(inplace=True), 254 | nn.BatchNorm1d(self.linear_size), 255 | nn.Dropout(self.dropout), 256 | 257 | nn.Linear(self.linear_size, 2), 258 | nn.Softmax(dim=-1) 259 | ) 260 | ``` 261 | 262 | 在上述过程中,我们使用了激活函数ELU,而没有使用RELU,因为在有噪声的数据中ELU的效果往往会更好。 263 | 264 | $ELU(*x*)=max(0,x)+min(0,α∗(exp(x)−1))$,其中$\alpha​$在torch中默认值为1。 265 | 266 | 通过下图可以看出他和RELU的区别,RELU在小于0的位置全部为0,但是ELU在小于零的位置是从0到-1的。可以理解为正常的数据汇总难免出现噪声,小于0的值,而RELU会直接把他处理为0,认为其实正常值,但是ELU却会保留他,所以ELU比RELU更有鲁棒性 267 | 268 | ![](../images/2.4/elu.png) 269 | 270 | 271 | 272 | ##### 3.2.3 损失函数部分 273 | 274 | 在孪生神经网络中我们经常会使用对比损失(Contrastive Loss),作为损失函数,对比损失是`Yann LeCun`提出的用来判断数据降维之后和源数据是否相似的问题。在这里我们用它来判断两个句子的表示是否相似。 275 | 276 | 对比损失的计算公式如下: 277 | $$ 278 | L = \frac{1}{2N}\sum^N_{n=1}(yd^2 + (1-y)max(margin-d,0)^2) 279 | $$ 280 | 其中$d = ||a_n-b_n||_2​$,代表两个两本特征的欧氏距离,y表示是否匹配,y=1表示匹配,y=0表示不匹配,margin是一个阈值,比如margin=1。 281 | 282 | 上式可分为两个部分,即: 283 | 284 | 1. y = 1时,只剩下左边,$\sum yd^2$,即相似的样本,如果距离太大,则效果不好,损失变大 285 | 2. y=0的时候,只剩下右边部分,即样本不相似的时候,如果距离小的话,效果反而不好,损失变大 286 | 287 | 下图红色是相似样本的损失,蓝色是不相似样本的损失 288 | 289 | ![](../images/2.4/对比损失.png) 290 | 291 | 但是前面我们已经计算出了相似度,所以在这里我们有两个操作 292 | 293 | 1. 使用前面的相似度的结果,把整个问题转化为分类(相似,不相似)的问题,或者是转化为回归问题(相似度是多少) 294 | 2. 不是用前面相似度的计算结果部分,只用编码之后的结果,然后使用对比损失。最后在获取距离的时候使用欧氏距离来计算器相似度 295 | 296 | ##### 使用DNN+均方误差来计算得到结果 297 | 298 | ```python 299 | def train(model,optimizer,loss_func,epoch): 300 | model.tarin() 301 | for batch_idx, (q,simq,q_len,simq_len,sim) in enumerate(train_loader): 302 | optimizer.zero_grad() 303 | output = model(q.to(config.device),simq.to(config.device)) 304 | loss = loss_func(output,sim.to(config.deivce)) 305 | loss.backward() 306 | optimizer.step() 307 | if batch_idx%100==0: 308 | print("...") 309 | torch.save(model.state_dict(), './DNN/data/model_paramters.pkl') 310 | torch.save(optimizer.state_dict(),"./DNN/data/optimizer_paramters.pkl") 311 | 312 | 313 | model = SiameseNetwork().cuda() 314 | loss = torch.nn.MSELoss() 315 | optimizer = optim.Adam(model.parameters(), lr=0.001) 316 | for epoch in range(1,config.epoch+1): 317 | train(model,optimizer,loss,epoch) 318 | ``` 319 | 320 | #### 使用对比损失来计算得到结果 321 | 322 | ```python 323 | #contrastive_loss.py 324 | import torch 325 | import torch.nn 326 | class ContrastiveLoss(torch.nn.Module): 327 | """ 328 | Contrastive loss function. 329 | """ 330 | 331 | def __init__(self, margin=1.0): 332 | super(ContrastiveLoss, self).__init__() 333 | self.margin = margin 334 | 335 | def forward(self, x0, x1, y): 336 | # 欧式距离 337 | diff = x0 - x1 338 | dist_sq = torch.sum(torch.pow(diff, 2), 1) 339 | dist = torch.sqrt(dist_sq) 340 | 341 | mdist = self.margin - dist 342 | #clamp(input,min,max),和numpy中裁剪的效果相同 343 | dist = torch.clamp(mdist, min=0.0) 344 | loss = y * dist_sq + (1 - y) * torch.pow(dist, 2) 345 | loss = torch.sum(loss) / 2.0 / x0.size()[0] 346 | return loss 347 | ``` 348 | 349 | 之后只需要把原来的损失函数改为当前的损失函数即可 350 | 351 | 352 | 353 | ### 3.3 不同模型的结果对比 354 | 355 | -------------------------------------------------------------------------------- /2.4 QA机器人/README.md: -------------------------------------------------------------------------------- 1 | # 2.4 QA机器人 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## NLP Note 2 | 3 | #### 介绍 4 | 内容主要讲解神经网络和pytorch,同时在最后完成了一个类似智能客服的聊天机器人项目 5 | 6 | #### content list 7 | 8 | ``` 9 | - 神经网络和pytorch 10 | - [深度学习和神经网络](./1.1 深度学习和神经网络/README.md) 11 | - [深度学习的介绍](./1.1 深度学习和神经网络/1.1.1 深度学习的介绍.md) 12 | - [神经网络的介绍](./1.1 深度学习和神经网络/1.1.2 神经网络的介绍.md) 13 | - [pytorch](./1.2 pytorch/README.md) 14 | - [pytorch的介绍和安装](./1.2 pytorch/1.2.1 pytorch的介绍和安装.md) 15 | - [pytorch的入门使用](./1.2 pytorch/1.2.2 pytorch的入门使用.md) 16 | - [梯度下降和反向传播原理](./1.2 pytorch/1.2.3 梯度下降和反向传播原理.md) 17 | - [手动完成线性回归 ](./1.2 pytorch/1.2.4 手动完成线性回归 .md) 18 | - [调用 pytorch API完成线性回归](./1.2 pytorch/1.2.5调用 pytorch API完成线性回归.md) 19 | - [pytorch中的数据加载](./1.2 pytorch/1.2.6 pytorch中的数据加载.md) 20 | - [使用pytorch实现手写数字识别](./1.2 pytorch/1.2.7 使用pytorch实现手写数字识别.md) 21 | - [循环神经网络](./1.3 循环神经网络/README.md) 22 | - [循环神经网络基础](./1.3 循环神经网络/1.3.1 循环神经网络基础.md) 23 | - [文本情感分类](./1.3 循环神经网络/1.3.2 文本情感分类.md) 24 | - [循环神经网络](./1.3 循环神经网络/1.3.3 循环神经网络.md) 25 | - [循环神经网络实现情感分类](./1.3 循环神经网络/1.3.4 循环神经网络实现情感分类.md) 26 | - [神经网络中的序列化容器](./1.3 循环神经网络/1.3.5 神经网络中的序列化容器.md) 27 | - [神经网络实现分词](./1.3 循环神经网络/1.3.6 神经网络实现分词.md) 28 | 29 | - 项目实现 30 | 31 | - [项目装备](./2.1 项目准备/README.md) 32 | - [走进聊天机器人](./2.1 项目准备/2.1.1 走进聊天机器人.md) 33 | - [需求分析和流程介绍](./2.1 项目准备/2.1.2 需求分析和流程介绍.md) 34 | - [环境准备](./2.1 项目准备/2.1.3 环境准备.md) 35 | - [语料准备](./2.1 项目准备/2.1.4 语料准备.md) 36 | - [文本分词](./2.1 项目准备/2.1.5 文本分词.md) 37 | - [动手练习](./2.1 项目准备/2.1.6 动手练习.md) 38 | - [FastText文本分类](./2.2 fasttext文本分类/README.md) 39 | - [分类的目的和方法](./2.2 fasttext文本分类/2.2.1 分类的目的和方法.md) 40 | - [fasttext实现文本分类](./2.2 fasttext文本分类/2.2.2 fasttext实现文本分类.md) 41 | - [fasttext的原理剖析](./2.2 fasttext文本分类/2.2.3 fasttext的原理剖析.md) 42 | - [Seq2Seq模型和闲聊机器人](./2.3 Seq2Seq模型和闲聊机器人/README.md) 43 | - [闲聊机器人的介绍](./2.3 Seq2Seq模型和闲聊机器人/2.3.1 闲聊机器人的介绍.md) 44 | - [seq2seq模型的原理](./2.3 Seq2Seq模型和闲聊机器人/2.3.2 seq2seq模型的原理.md) 45 | - [seq2seq实现闲聊机器人](./2.3 Seq2Seq模型和闲聊机器人/2.3.3 seq2seq实现闲聊机器人.md) 46 | - [Attention的原理和实现](./2.3 Seq2Seq模型和闲聊机器人/2.3.4 Attention的原理和实现.md) 47 | - [BeamSearch的原理和实现](./2.3 Seq2Seq模型和闲聊机器人/2.3.5 BeamSearch的原理和实现.md) 48 | - [闲聊机器人的优化](./2.3 Seq2Seq模型和闲聊机器人/2.3.6 闲聊机器人的优化.md) 49 | - [QA机器人](./2.4 QA机器人/README.md) 50 | - [QA机器人介绍.md](./2.4 QA机器人/2.4.1 QA机器人介绍.md) 51 | - [QA机器人的召回.md](./2.4 QA机器人/2.4.2 QA机器人的召回.md) 52 | - [召回优化.md](./2.4 QA机器人/2.4.3 召回优化.md) 53 | - [QA机器人排序模型.md](./2.4 QA机器人/2.4.4 QA机器人排序模型.md) 54 | - [代码的封装和提供接口.md](./2.4 QA机器人/2.4.5 代码的封装和提供接口.md) 55 | ``` 56 | 57 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["mathjax"], 3 | "pluginsConfig": { 4 | "mathjax":{ 5 | "forceSVG": true 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /images/1.1/全连接层.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/全连接层.png -------------------------------------------------------------------------------- /images/1.1/区别.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/区别.png -------------------------------------------------------------------------------- /images/1.1/单层神经网络.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/单层神经网络.png -------------------------------------------------------------------------------- /images/1.1/多层神经网络.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/多层神经网络.png -------------------------------------------------------------------------------- /images/1.1/感知机.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/感知机.png -------------------------------------------------------------------------------- /images/1.1/数据量.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/数据量.png -------------------------------------------------------------------------------- /images/1.1/激活函数1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/激活函数1.png -------------------------------------------------------------------------------- /images/1.1/激活函数2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/激活函数2.png -------------------------------------------------------------------------------- /images/1.1/激活函数3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/激活函数3.png -------------------------------------------------------------------------------- /images/1.1/激活函数4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/激活函数4.png -------------------------------------------------------------------------------- /images/1.1/激活函数5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/激活函数5.jpg -------------------------------------------------------------------------------- /images/1.1/神经元.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/神经元.png -------------------------------------------------------------------------------- /images/1.1/神经网络例子1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/神经网络例子1.png -------------------------------------------------------------------------------- /images/1.1/神经网络例子2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/神经网络例子2.png -------------------------------------------------------------------------------- /images/1.1/神经网络例子3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/神经网络例子3.png -------------------------------------------------------------------------------- /images/1.1/神经网络例子4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.1/神经网络例子4.png -------------------------------------------------------------------------------- /images/1.2/MNIST-dataset-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/MNIST-dataset-5.png -------------------------------------------------------------------------------- /images/1.2/MNIST-dataset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/MNIST-dataset.png -------------------------------------------------------------------------------- /images/1.2/dataset数据示例.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/dataset数据示例.png -------------------------------------------------------------------------------- /images/1.2/softmax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/softmax.png -------------------------------------------------------------------------------- /images/1.2/tensor的数据类型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/tensor的数据类型.png -------------------------------------------------------------------------------- /images/1.2/torch版本.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/torch版本.png -------------------------------------------------------------------------------- /images/1.2/优化器方法.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/优化器方法.gif -------------------------------------------------------------------------------- /images/1.2/优化器方法.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/优化器方法.png -------------------------------------------------------------------------------- /images/1.2/偏导的计算.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/偏导的计算.png -------------------------------------------------------------------------------- /images/1.2/偏导的计算2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/偏导的计算2.png -------------------------------------------------------------------------------- /images/1.2/偏导的计算3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/偏导的计算3.png -------------------------------------------------------------------------------- /images/1.2/偏导的计算4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/偏导的计算4.png -------------------------------------------------------------------------------- /images/1.2/梯度1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/梯度1.png -------------------------------------------------------------------------------- /images/1.2/梯度2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/梯度2.png -------------------------------------------------------------------------------- /images/1.2/神经网络计算图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/神经网络计算图.png -------------------------------------------------------------------------------- /images/1.2/神经网络计算图2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/神经网络计算图2.png -------------------------------------------------------------------------------- /images/1.2/线性回归1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/线性回归1.png -------------------------------------------------------------------------------- /images/1.2/线性回归2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/线性回归2.png -------------------------------------------------------------------------------- /images/1.2/计算图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/计算图.png -------------------------------------------------------------------------------- /images/1.2/计算梯度.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.2/计算梯度.png -------------------------------------------------------------------------------- /images/1.3/GRU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/GRU.png -------------------------------------------------------------------------------- /images/1.3/LSTM-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/LSTM-update.png -------------------------------------------------------------------------------- /images/1.3/LSTM1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/LSTM1.jpg -------------------------------------------------------------------------------- /images/1.3/LSTM2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/LSTM2.jpg -------------------------------------------------------------------------------- /images/1.3/LSTM3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/LSTM3.png -------------------------------------------------------------------------------- /images/1.3/LSTM4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/LSTM4.png -------------------------------------------------------------------------------- /images/1.3/RNN功能.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/RNN功能.png -------------------------------------------------------------------------------- /images/1.3/RNN图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/RNN图.png -------------------------------------------------------------------------------- /images/1.3/RNN展开.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/RNN展开.png -------------------------------------------------------------------------------- /images/1.3/bidir_lstm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/bidir_lstm.png -------------------------------------------------------------------------------- /images/1.3/sigmoid导数.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/sigmoid导数.png -------------------------------------------------------------------------------- /images/1.3/word_embedding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/word_embedding.png -------------------------------------------------------------------------------- /images/1.3/基础的RNN展开图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/基础的RNN展开图.png -------------------------------------------------------------------------------- /images/1.3/情感分类-data加载1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/情感分类-data加载1.png -------------------------------------------------------------------------------- /images/1.3/情感分类-data加载错误.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/情感分类-data加载错误.png -------------------------------------------------------------------------------- /images/1.3/易王门.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/易王门.png -------------------------------------------------------------------------------- /images/1.3/替换激活函数.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/替换激活函数.png -------------------------------------------------------------------------------- /images/1.3/样本 内容.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/样本 内容.png -------------------------------------------------------------------------------- /images/1.3/样本名称.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/样本名称.png -------------------------------------------------------------------------------- /images/1.3/梯度消失.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/梯度消失.png -------------------------------------------------------------------------------- /images/1.3/输入门.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/输入门.png -------------------------------------------------------------------------------- /images/1.3/输出门.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/1.3/输出门.png -------------------------------------------------------------------------------- /images/2.1/QAbot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.1/QAbot.png -------------------------------------------------------------------------------- /images/2.1/app截图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.1/app截图.png -------------------------------------------------------------------------------- /images/2.1/chabot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.1/chabot.png -------------------------------------------------------------------------------- /images/2.1/excel中的问题.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.1/excel中的问题.png -------------------------------------------------------------------------------- /images/2.1/小蜜的交互过程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.1/小蜜的交互过程.png -------------------------------------------------------------------------------- /images/2.1/小蜜的架构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.1/小蜜的架构.png -------------------------------------------------------------------------------- /images/2.1/小蜜的检索流程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.1/小蜜的检索流程.png -------------------------------------------------------------------------------- /images/2.1/整体架构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.1/整体架构.png -------------------------------------------------------------------------------- /images/2.1/百度相似问题搜索.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.1/百度相似问题搜索.png -------------------------------------------------------------------------------- /images/2.1/词典.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.1/词典.png -------------------------------------------------------------------------------- /images/2.1/问答对.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.1/问答对.png -------------------------------------------------------------------------------- /images/2.2/Inked哈夫曼树.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.2/Inked哈夫曼树.jpg -------------------------------------------------------------------------------- /images/2.2/bow模型.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.2/bow模型.png -------------------------------------------------------------------------------- /images/2.2/fasttext原理.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.2/fasttext原理.jpg -------------------------------------------------------------------------------- /images/2.2/fasttext负采样.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.2/fasttext负采样.png -------------------------------------------------------------------------------- /images/2.2/哈夫曼树.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.2/哈夫曼树.jpg -------------------------------------------------------------------------------- /images/2.2/哈夫曼树构造过程.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.2/哈夫曼树构造过程.png -------------------------------------------------------------------------------- /images/2.2/哈夫曼编码 - 副本.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.2/哈夫曼编码 - 副本.jpg -------------------------------------------------------------------------------- /images/2.2/哈夫曼编码.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.2/哈夫曼编码.jpg -------------------------------------------------------------------------------- /images/2.2/数据准备1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.2/数据准备1.png -------------------------------------------------------------------------------- /images/2.2/数据准备2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.2/数据准备2.png -------------------------------------------------------------------------------- /images/2.2/负采样.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.2/负采样.png -------------------------------------------------------------------------------- /images/2.2/负采样梯度上升.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.2/负采样梯度上升.png -------------------------------------------------------------------------------- /images/2.3/Attention2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/Attention2.png -------------------------------------------------------------------------------- /images/2.3/Attention3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/Attention3.png -------------------------------------------------------------------------------- /images/2.3/Attention4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/Attention4.png -------------------------------------------------------------------------------- /images/2.3/Attention5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/Attention5.png -------------------------------------------------------------------------------- /images/2.3/Bahdanau.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/Bahdanau.png -------------------------------------------------------------------------------- /images/2.3/Encoder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/Encoder.png -------------------------------------------------------------------------------- /images/2.3/Luong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/Luong.png -------------------------------------------------------------------------------- /images/2.3/attention1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/attention1.png -------------------------------------------------------------------------------- /images/2.3/attention6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/attention6.png -------------------------------------------------------------------------------- /images/2.3/beamsearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/beamsearch.png -------------------------------------------------------------------------------- /images/2.3/grad_clip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/grad_clip.png -------------------------------------------------------------------------------- /images/2.3/greedy search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/greedy search.png -------------------------------------------------------------------------------- /images/2.3/scores.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/scores.png -------------------------------------------------------------------------------- /images/2.3/seq2seq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/seq2seq.png -------------------------------------------------------------------------------- /images/2.3/seq2seq2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/seq2seq2.png -------------------------------------------------------------------------------- /images/2.3/soft-hard attention.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/soft-hard attention.jpg -------------------------------------------------------------------------------- /images/2.3/teacher forcing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/teacher forcing.jpg -------------------------------------------------------------------------------- /images/2.3/小黄鸡语料.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/小黄鸡语料.png -------------------------------------------------------------------------------- /images/2.3/微博语料1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/微博语料1.png -------------------------------------------------------------------------------- /images/2.3/微博语料2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/微博语料2.png -------------------------------------------------------------------------------- /images/2.3/文本处理后.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/文本处理后.png -------------------------------------------------------------------------------- /images/2.3/文本处理后2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.3/文本处理后2.png -------------------------------------------------------------------------------- /images/2.4/AAAI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.4/AAAI.png -------------------------------------------------------------------------------- /images/2.4/bm25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.4/bm25.png -------------------------------------------------------------------------------- /images/2.4/bm25_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.4/bm25_2.png -------------------------------------------------------------------------------- /images/2.4/deepqa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.4/deepqa.png -------------------------------------------------------------------------------- /images/2.4/elu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.4/elu.png -------------------------------------------------------------------------------- /images/2.4/pooling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.4/pooling.png -------------------------------------------------------------------------------- /images/2.4/pysparnn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.4/pysparnn.png -------------------------------------------------------------------------------- /images/2.4/对比损失.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/2.4/对比损失.png -------------------------------------------------------------------------------- /images/补充/HMM案例1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/补充/HMM案例1.png -------------------------------------------------------------------------------- /images/补充/不同对比.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/补充/不同对比.png -------------------------------------------------------------------------------- /images/补充/取最大值.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/补充/取最大值.png -------------------------------------------------------------------------------- /images/补充/向前算法.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/补充/向前算法.png -------------------------------------------------------------------------------- /images/补充/条件随机场.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/补充/条件随机场.png -------------------------------------------------------------------------------- /images/补充/海藻.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/补充/海藻.png -------------------------------------------------------------------------------- /images/补充/海藻和天气.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/补充/海藻和天气.png -------------------------------------------------------------------------------- /images/补充/状态转移矩阵.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/补充/状态转移矩阵.png -------------------------------------------------------------------------------- /images/补充/维特比.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/补充/维特比.png -------------------------------------------------------------------------------- /images/补充/自动机.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/补充/自动机.png -------------------------------------------------------------------------------- /images/补充/马尔可夫链-天气: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/补充/马尔可夫链-天气 -------------------------------------------------------------------------------- /images/补充/马尔可夫链天气.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpringMagnolia/NLPnote/9e61f81099b3f21bb3d8aa6d8ac74998d29596d9/images/补充/马尔可夫链天气.png -------------------------------------------------------------------------------- /summary.md: -------------------------------------------------------------------------------- 1 | * 神经网络和pytorch 2 | 3 | * [1.1 深度学习和神经网络](./1.1 深度学习和神经网络/README.md) 4 | * [深度学习的介绍](./1.1 深度学习和神经网络/1.1.1 深度学习的介绍.md) 5 | * [神经网络的介绍](./1.1 深度学习和神经网络/1.1.2 神经网络的介绍.md) 6 | * [1.2 pytorch](./1.2 pytorch/README.md) 7 | * [pytorch的介绍和安装](./1.2 pytorch/1.2.1 pytorch的介绍和安装.md) 8 | * [pytorch的入门使用](./1.2 pytorch/1.2.2 pytorch的入门使用.md) 9 | * [梯度下降和反向传播原理](./1.2 pytorch/1.2.3 梯度下降和反向传播原理.md) 10 | * [手动完成线性回归 ](./1.2 pytorch/1.2.4 手动完成线性回归 .md) 11 | * [调用 pytorch API完成线性回归](./1.2 pytorch/1.2.5调用 pytorch API完成线性回归.md) 12 | * [pytorch中的数据加载](./1.2 pytorch/1.2.6 pytorch中的数据加载.md) 13 | * [使用pytorch实现手写数字识别](./1.2 pytorch/1.2.7 使用pytorch实现手写数字识别.md) 14 | * [1.3 循环神经网络](./1.3 循环神经网络/README.md) 15 | * [循环神经网络基础](./1.3 循环神经网络/1.3.1 循环神经网络基础.md) 16 | * [文本情感分类](./1.3 循环神经网络/1.3.2 文本情感分类.md) 17 | * [循环神经网络](./1.3 循环神经网络/1.3.3 循环神经网络.md) 18 | * [循环神经网络实现情感分类](./1.3 循环神经网络/1.3.4 循环神经网络实现情感分类.md) 19 | * [神经网络中的序列化容器](./1.3 循环神经网络/1.3.5 神经网络中的序列化容器.md) 20 | * [神经网络实现分词](./1.3 循环神经网络/1.3.6 神经网络实现分词.md) 21 | 22 | * 项目实现 23 | 24 | * [2.1 项目装备](./2.1 项目准备/README.md) 25 | * [走进聊天机器人](./2.1 项目准备/2.1.1 走进聊天机器人.md) 26 | * [需求分析和流程介绍](./2.1 项目准备/2.1.2 需求分析和流程介绍.md) 27 | * [环境准备](./2.1 项目准备/2.1.3 环境准备.md) 28 | * [语料准备](./2.1 项目准备/2.1.4 语料准备.md) 29 | * [文本分词](./2.1 项目准备/2.1.5 文本分词.md) 30 | * [动手练习](./2.1 项目准备/2.1.6 动手练习.md) 31 | * [2.2 FastText文本分类](./2.2 fasttext文本分类/README.md) 32 | * [分类的目的和方法](./2.2 fasttext文本分类/2.2.1 分类的目的和方法.md) 33 | * [fasttext实现文本分类](./2.2 fasttext文本分类/2.2.2 fasttext实现文本分类.md) 34 | * [fasttext的原理剖析](./2.2 fasttext文本分类/2.2.3 fasttext的原理剖析.md) 35 | * [2.3 Seq2Seq模型和闲聊机器人](./2.3 Seq2Seq模型和闲聊机器人/README.md) 36 | * [闲聊机器人的介绍](./2.3 Seq2Seq模型和闲聊机器人/2.3.1 闲聊机器人的介绍.md) 37 | * [seq2seq模型的原理](./2.3 Seq2Seq模型和闲聊机器人/2.3.2 seq2seq模型的原理.md) 38 | * [seq2seq实现闲聊机器人](./2.3 Seq2Seq模型和闲聊机器人/2.3.3 seq2seq实现闲聊机器人.md) 39 | * [Attention的原理和实现](./2.3 Seq2Seq模型和闲聊机器人/2.3.4 Attention的原理和实现.md) 40 | * [BeamSearch的原理和实现](./2.3 Seq2Seq模型和闲聊机器人/2.3.5 BeamSearch的原理和实现.md) 41 | * [闲聊机器人的优化](./2.3 Seq2Seq模型和闲聊机器人/2.3.6 闲聊机器人的优化.md) 42 | * [2.4 QA机器人](./2.4 QA机器人/README.md) 43 | * [QA机器人介绍.md](./2.4 QA机器人/2.4.1 QA机器人介绍.md) 44 | * [QA机器人的召回.md](./2.4 QA机器人/2.4.2 QA机器人的召回.md) 45 | * [召回优化.md](./2.4 QA机器人/2.4.3 召回优化.md) 46 | * [QA机器人排序模型.md](./2.4 QA机器人/2.4.4 QA机器人排序模型.md) 47 | * [代码的封装和提供接口.md](./2.4 QA机器人/2.4.5 代码的封装和提供接口.md) 48 | 49 | * 补充 50 | 51 | * [HMM](./补充/HMM.md) 52 | * [MEMM和CRF](./补充/MEMM和CRF.md) 53 | * [最大匹配法](./补充/最大匹配法.md) 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /补充/HMM.md: -------------------------------------------------------------------------------- 1 | # 隐马尔可夫 2 | 3 | ## 目标 4 | 5 | 1. 知道什么是自动机 6 | 2. 知道什么是马尔可夫链 7 | 3. 知道隐马尔可夫是什么 8 | 4. 知道隐马尔可夫的原理是什么 9 | 5. 知道如何使用隐马尔可夫完成分词,词性标注等任务 10 | 6. 知道MEMM是什么 11 | 7. 知道CRF是什么 12 | 13 | 14 | 15 | ## 1. 自动机 16 | 17 | 自动机:(又称为 有限自动机,有限状态自动机,FSA)是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。 18 | 19 | 例如: 20 | 21 | 我们常用的正则表达式就是一种用来描述字符串出现字符的自动机。 22 | 23 | 假如我们有正则表达式:`baa+!`,表示的是ba后面有1个或这多个a,最后是一个感叹号。 24 | 25 | 我们可以把上述的自动机用图来展示,如下: 26 | 27 | ![](../images/补充/自动机.png) 28 | 29 | 30 | 31 | 自动机从初始状态q0开始,反复进行下面的过程:找到第一个字母b,如果找到b那么进入到下一个状态,再去寻找下一个状态需要的字母,指导进行接收状态q4。 32 | 33 | 我们可以使用状态转移表来自动机: 34 | 35 | ![](../images/补充/状态转移矩阵.png) 36 | 37 | 上述的状态机我们也称为**确定的自动状态机DFSA**(例如红绿灯),如果位于q3的循环在q2上,那么在q2状态下,看到a,他是不清楚要向那个地方进行转移的。所以把这种状态机成为**非确定的自动状态机 NFSA**,(比如天气)。 38 | 39 | ## 2. 马尔可夫链和马尔可夫假设 40 | 41 | 马尔可链是自动状态机的扩展版,是一种带权的自动状态机。权重在马尔可夫链中就是连接弧的概率。离开一个节点的所有的概率和为1。 42 | 43 | 用马尔可夫链描述天气的变化,如果使用图模型来描述的话,可以有下面的示例: 44 | 45 | ![](../images/补充/马尔可夫链天气.png) 46 | 47 | 如果今天下雨,那么明天的天气会怎么样呢? 48 | 49 | 明天下雪的概率:0.02 50 | 51 | 明天下雨的概率:0.8 52 | 53 | 明天晴天的概率:0.18 54 | 55 | 上述的过程包含了概率上的一个重要假设:在一个**一阶马尔可夫链**中,一个特定状态的概率只和他的前一个状态是有关的: 56 | 57 | **马尔可夫假设**: 58 | $$ 59 | P(q_i|q_{i-1}\cdots q_1) = P(q_i|q_{i-1}) 60 | $$ 61 | 如果是把马尔可夫应用于NLP的文本序列,那么他表示的就是**二元N-gram模型** 62 | 63 | 64 | 65 | ## 3. 隐马尔可夫模型 66 | 67 | 当我们要计算我们能够观察到的事件序列的概率的时候,马尔可夫链是很有用的。但是在很多情况下,我们感兴趣的概率是没有办法直接计算的。例如在**词性标注**的问题中,我们能够看到句子中的词,能够计算这个句子组合的概率。但是我们的目标是或者这个句子对应的词性的序列。这些词性序列是隐藏的,不能够直接被观察到,我们需要去推断隐藏的状态,这个时候我们就需要使用**隐马尔科夫模型(HMM)**。 68 | 69 | 隐马尔可夫模型可以使用以下内容来进行描述: 70 | 71 | > $Q = q_1,q_2,\cdots q_N$ 状态N的集合 72 | > 73 | > $A = a_{11},a_{12},\cdots,a_{nn}​$ 转移概率矩阵A。每一个转移概率$a_{ij}​$表示从状态i转移到状态j的概率,同时从某一个状态出发的转移概率和为1 74 | > 75 | > $O = O_1,O_2 \cdots O_n$ 观察到的序列T 76 | > 77 | > $B = b_i(O_i)​$ 观察似然度,也叫做发射概率,表示从状态i得到观察$O_i​$的概率 78 | > 79 | > q_0,q_F 表示初始状态和终结状态 80 | 81 | 82 | 83 | 隐马尔可夫模型中,除了马尔可夫假设之外,还有另外一个假设,即输出独立性假设,即: 84 | 85 | 一个输出观察$O_i$的概率只和产生该观察的状态$q_i​$有关 86 | $$ 87 | P(O_i|q_1,q_2\cdots q_T ,O_1,O_2 \cdots O_T) = P(O_i|q_i) 88 | $$ 89 | 90 | 91 | 在类似词性标注的问题中,我们需要做的事情,在含有n个单词的观察序列的所有可能的隐藏序列中找到一个概率最大的隐藏序列,写成公式如下(其中帽子符号$\hat{}$表示对正确序列的估计): 92 | $$ 93 | \hat{t}_n = \mathop{argmax}_{t_n}P(t_n|w_n) 94 | $$ 95 | 96 | 97 | 根据前面的两个概率假设,上述公式也可以写为: 98 | $$ 99 | \begin{align} 100 | \hat{t}_n &= \mathop{argmax}_{t_n}P(t_n|w_n) \\ 101 | &= \mathop{argmax}_{t_n}\frac{P(t_n,w_n)}{P(w_n)} \\ 102 | &= \mathop{argmax}_{t_n}P(t_n,w_n) \\ 103 | &= \mathop{argmax}_{t_n}\prod_{i=1}^{n} P(w_i|t_i)P(t_i|t_{i-1}) \\ 104 | &= \mathop{argmax}_{t_n}P(w_i|t_i)P(t_i|t_{i-1}) 105 | \end{align} 106 | $$ 107 | 上述的公式中包含两个概率: 108 | 109 | 1. 标记的 **转移概率**:$P(t_i|t_{i-1})​$ 110 | 111 | 2. 单词的**似然度(likelihood)**:又称为发射概率,即在状态$t_i​$的情况下发现观测值为$w_i​$的概率。 112 | 113 | > 似然度:likelihood的中文翻译,表示可能性、概率的意思 114 | 115 | 转移概率的计算方法:通过极大似然估计(MLE),通过现有的语料,直接计算即可: 116 | 117 | 即:状态从$t_{i-1}到t_i$的总数 除以$t_{i-1}$的总数 118 | $$ 119 | P(t_i|t_{i-1}) = \frac{C(t_{i-1},t_i)}{C(t_{i-1})} 120 | $$ 121 | 122 | > **极大似然估计**:是一种概率在统计学中的应用,是一种参数估计方法,说的是说的是已知某个随机样本满足某种概率分布,但是其中具体的参数不清楚,参数估计就是通过若干次试验,使用实验得出的概率作为样本的概率。 123 | 124 | 似然度概率的计算方法同理: 125 | $$ 126 | P(w_i|t_i) = \frac{C(t_i,w_i)}{C(t_i)} 127 | $$ 128 | 即数据集中所有的$w_i为t_i$的样本数量 除以 该状态$t_i$的总数 129 | 130 | ### 3.1 马尔科夫模型中的三个问题: 131 | 132 | 1. 似然度问题,即对于给定的观察序列,计算其可能的概率 133 | 2. 解码问题:即对于给定的观察序列,找到概率最大的隐藏序列 134 | 3. 学习问题:给定样本,学习HMM中的参数A(转移概率)和B(观察似然度) 135 | 136 | 其中问题三:学习问题前面已经讲解,通过语料进行统计,通过极大似然估计就可以计算。 137 | 138 | ### 3.2 似然度问题: 139 | 140 | 传说海藻的能够预测天气,假如海藻有下面四种状态,天气有三种状态,那么现在我们知道一列海藻的状态`[Damp,Dryish,Dry,Soggy]`,那么我们想知道对应这四天的天气是什么样子的,需要如何计算? 141 | 142 | ![](../images/补充/海藻和天气.png) 143 | 144 | 要完成上述的问题,我们需要历史的数据,假设我们有如下的历史数据: 145 | 146 | 1. 第一天分别为[sun,cloud,Rain]的概率分别是[0.3,0.3,0.4] 147 | 148 | 2. 状态转移概率和发射概率如下 149 | 150 | ![](../images/补充/海藻.png) 151 | 152 | 153 | 154 | 最简单的方式,我们可以计算满足要求`[Damp,Dryish,Dry,Soggy]`的所有的天气状态的概率,然后进行乘积,得到结果,那么我们需要计算$N^M​$次,其中M表示观测值的数量,N表示状态的数量 155 | 156 | 我们可以使用**向前算法**来代替上述呈指数级增长的概率计算方法。向前算法是一种动态规划的方法。其核心思想就是,在计算观察序列的概率的时候,通过一个中间值来存储其中间值,同时对于**中间状态的概率,使用 之前状态 乘 转移概率的求和计算得到** 157 | 158 | > 动态规划:把多阶段决策过程的最优化问题转化为一系列单阶段的问题 159 | 160 | 其计算过程如下: 161 | 162 | ![](../images/补充/向前算法.png) 163 | 164 | 165 | 166 | 其计算过程的伪代码如下: 167 | 168 | >1. 初始化 169 | > 170 | > $\alpha_1(j) = \alpha_{0j}b_{j}(o_1)​$ 171 | > 172 | >2. 递归 173 | > 174 | > $\alpha_t(j) = \sum_{i=1}^N \alpha_{t-1}(i) a_{ij} b_{j}(o_{t})​$ 175 | > 176 | >3. 结束 177 | > 178 | > $P(O|\lambda) = \sum_{i=1}^N \alpha_{T}(i)\alpha_{iF}​$ 179 | > 180 | >$其中\alpha_t表示中间概率,\alpha_{ij}表示转移概率,b_j(o_t)表示发射概率​$ 181 | 182 | 183 | 184 | ### 3.3 解码问题 185 | 186 | 根据观察序列确定隐藏序列的过程称之为decoding(解码),decoder(加码器)的任务就是发现最优隐藏序列 187 | 188 | ![](../images/补充/维特比.png) 189 | 190 | 其实现过程如下: 191 | 192 | 1. 遍历所有的状态,根据初始状态的概率计算*观察序列对应的发射概率,得到第一次概率结果 193 | 2. 遍历从第二次到最后的时间步 194 | 3. 遍历所有的状态 195 | 4. 计算:前一次的概率结果\*转移概率\*发射概率,选择概率最大的隐藏状态作为当前的隐藏状态 196 | 197 | 198 | 199 | ## 案例 200 | 201 | 下面我们使用语料,使用HMM设计一个模型进行分词 202 | 203 | 语料github地址: 204 | 205 | 语料内容如下 206 | 207 | ![](../images/补充/HMM案例1.png) 208 | 209 | 其中词性标注的代码含义如下: 210 | 211 | ```txt 212 | n 普通名词 213 | nt 时间名词 214 | nd 方位名词 215 | nl 处所名词 216 | nh 人名 217 | nhf 姓 218 | nhs 名 219 | ns 地名 220 | nn 族名 221 | ni 机构名 222 | nz 其他专名 223 | v 动词 224 | vd 趋向动词 225 | vl 联系动词 226 | vu 能愿动词 227 | a 形容词 228 | f 区别词 229 | m 数词   230 | q 量词 231 | d 副词 232 | r 代词 233 | p 介词 234 | c 连词 235 | u 助词 236 | e 叹词 237 | o 拟声词 238 | i 习用语 239 | j 缩略语 240 | h 前接成分 241 | k 后接成分 242 | g 语素字 243 | x 非语素字 244 | w 标点符号 245 | ws 非汉字字符串 246 | wu 其他未知的符号 247 | ``` 248 | 249 | 该语料可以用来训练分词模型,也可以用来训练词性标注等模型。 250 | 251 | ### 如何对句子进行分词 252 | 253 | 思考: 254 | 255 | > 如果使用上述的语料进行分词,我们应该如何准备我们的数据 256 | 257 | 通常在分词过程中,每个字会对应一个符号,然后我们根据预测的符号对句子进行划分。 258 | 259 | 例如:我们有下列四种符号表示所有单字的状态 260 | 261 | ```txt 262 | B 表示 begin 词语的开始 263 | M 表示 middle 词语的中间 264 | E 表示 end 词语的结束 265 | S 表示 single 单个字成词 266 | ``` 267 | 268 | 那么,会有下列情况 269 | 270 | ```pyhton 271 | 我/S爱/S北/B京/E天/B安/M门/E 272 | ``` 273 | 274 | 此时,我们把所有的E和S分开,就能够实现对句子的分词了 275 | 276 | 277 | 278 | ### 统计概率 279 | 280 | 根据前面的知识,我们知道,为了计算解码过程中,每个时间步上的概率最大值,需要统计四个概率,分别是 281 | 282 | 1. 初始状态概率 283 | 2. 状态转移概率 284 | 3. 发射概率 285 | 4. 结束状态概率 286 | 287 | 根据极大似然估计的思想,我们通过统计语料,可以得到上述概率 288 | 289 | 我们的思路如下: 290 | 291 | 1. 定义保存概率的容器 292 | 2. 对每个句子进行处理 293 | 3. 对每个句子中的字进行统计 294 | 4. 保存概率后续使用 295 | 296 | 我们可以定义一个对象, 来进行概率的统计 297 | 298 | ```python 299 | class ProbilityMartix: 300 | def __init__(self): 301 | self.state_list = ["B","M","E","S"] #初始的四种状态 302 | self.state_num = len(self.state_list) 303 | 304 | #初始概率向量 {B:200,S:400} 305 | self.PiVector = {i:0 for i in self.state_list} 306 | #总的句子数,或者是总得初始向量 307 | self.PiVector_size = 0 308 | 309 | #转移概率矩阵,从一个状态到另一个状态的数量 {B:{E:100,S:200...}} 310 | self.TransProbMatrix = {i:{j:0 for j in self.state_list} for i in self.state_list } 311 | #每个状态的总数,上面的/下面的 = 从一个状态到另一个状态的概率 {S:200,E:300} 312 | self.TransProbMatrix_size = {i:0 for i in self.state_list} 313 | 314 | #发射概率矩阵,从状态到词的数量,【后续求当前这个词到位某个状态的数量/ 状态的数量= 某个词为某个状态的概率】 315 | self.EmitProbMartix = {i:{} for i in self.state_list} 316 | #每个状态数量 {"S":100} 317 | self.EmitProbMartix_word_size = {} 318 | 319 | self.EndProbMartix = {i:0 for i in self.state_list} 320 | self.EndProbMartix_size = 0 321 | 322 | 323 | ``` 324 | 325 | 之后,对每个句子进行处理和统计 326 | 327 | ```python 328 | def sentence2states(self,sentence): 329 | ''' 330 | :param sentence:['明日', '将', '与', '河北', '队', '作', '赛', '津', '队', '在', '实力', '上', '稍胜一筹', '可望', '取胜'] 331 | :return: ["BE","S","S"....] 332 | ''' 333 | state_output = [] 334 | for word in sentence: 335 | word = word.strip() 336 | if len(word)<1: 337 | continue 338 | current_state = "" 339 | if len(word) ==1: 340 | current_state += "S" 341 | elif len(word)>1: 342 | M_num = len(word)-2 343 | current_state += "B" 344 | current_state += "M"*M_num 345 | current_state += "E" 346 | state_output.append(current_state) 347 | return state_output 348 | 349 | def start_count_by_sentence(self,sentence): 350 | states = self.sentence2states(sentence) 351 | 352 | #把词和状态链接到一起,方便后面统计 353 | joined_sentence = "".join(sentence) #明日将与河北' 354 | joined_states = "".join(states) #"BESSBE" 355 | 356 | #统计初始数量 357 | self.PiVector[joined_states[0]] +=1 358 | #统计初始总数 359 | self.PiVector_size+=1 360 | 361 | for i in range(len(joined_states)-1): 362 | #统计转移状态的数量 363 | self.TransProbMatrix[joined_states[i]][joined_states[i+1]] +=1 364 | #统计状态的数量 365 | self.TransProbMatrix_size[joined_states[i]] +=1 366 | 367 | for i in range(len(joined_states)): 368 | #统计发射词的数量 369 | if joined_sentence[i] in self.EmitProbMartix[joined_states[i]]: 370 | self.EmitProbMartix[joined_states[i]][joined_sentence[i]] +=1 371 | else: 372 | self.EmitProbMartix[joined_states[i]][joined_sentence[i]]=1 373 | 374 | #统计不同词的总数,应该是统计所有的状态 375 | if joined_states[i] in self.EmitProbMartix_word_size: 376 | self.EmitProbMartix_word_size[joined_states[i]] += 1 377 | else: 378 | self.EmitProbMartix_word_size[joined_states[i]] = 1 379 | #统计结束的概率 380 | last_state = joined_states[-1] 381 | self.EndProbMartix[last_state] += 1 382 | self.EndProbMartix_size += 1 383 | 384 | ``` 385 | 386 | 之后进行计算和保存 387 | 388 | ```python 389 | def get_probility(self): 390 | ''' 391 | 开始计算概率 392 | :return: 393 | ''' 394 | self.PiVector_prob = deepcopy(self.PiVector) 395 | self.TransProbMatrix_prob = deepcopy(self.TransProbMatrix) 396 | self.EmitProbMartix_prob = deepcopy(self.EmitProbMartix) 397 | 398 | for key in self.PiVector_prob: 399 | self.PiVector_prob[key] = np.log((self.PiVector_prob[key]/self.PiVector_size)) 400 | for start in self.TransProbMatrix_prob: 401 | for end in self.TransProbMatrix_prob[start]: 402 | #避免算出来为0 403 | self.TransProbMatrix_prob[start][end] = 1 if self.TransProbMatrix_prob[start][end]==0 else self.TransProbMatrix_prob[start][end] 404 | 405 | self.TransProbMatrix_prob[start][end] = np.log((self.TransProbMatrix_prob[start][end]/self.TransProbMatrix_size[start])) 406 | 407 | #后续再使用的时候,没有出现的词让其概率低 408 | for key in self.EmitProbMartix_prob: 409 | for word in self.EmitProbMartix_prob[key]: 410 | self.EmitProbMartix_prob[key][word] = np.log((self.EmitProbMartix_prob[key][word]/self.EmitProbMartix_word_size[key])) 411 | 412 | #统计结束概率 413 | for key in self.EndProbMartix_prob: 414 | self.EndProbMartix_prob[key] = np.log(self.EndProbMartix_prob[key]/self.EndProbMartix_size) 415 | 416 | def save_probility(self): 417 | temp = { 418 | "EmitProbMartix_prob" : self.EmitProbMartix_prob, 419 | "PiVector_prob":self.PiVector_prob, 420 | "TransProbMatrix_prob":self.TransProbMatrix_prob, 421 | "EndProbMatrix_prob": self.EndProbMartix_prob 422 | } 423 | with open("./probility.pkl","wb") as f: 424 | pickle.dump(temp,f) 425 | 426 | def run(self): 427 | file_path = r"corpusZH-master/all.txt" 428 | for sentence in prepar_sentences(file_path): 429 | self.start_count_by_sentence(sentence) 430 | 431 | self.get_probility() 432 | self.save_probility() 433 | ``` 434 | 435 | ### 使用viterbi算法进行解码 436 | 437 | 使用viterbi算法实现分词的部分代码实现如下: 438 | 439 | ```python 440 | def start_calcute(self,sentence): 441 | ''' 442 | 通过viterbi算法计算结果 443 | :param sentence: "小明硕士毕业于中国科学院计算所" 444 | :return: "S...E" 445 | ''' 446 | zero = -3.14e+100 447 | zero_log = np.log(-3.14e+100) 448 | init_state = self.prob_dict["PiVector_prob"] 449 | trans_prob = self.prob_dict["TransProbMatrix_prob"] 450 | emit_prob = self.prob_dict["EmitProbMartix_prob"] 451 | end_prob = self.prob_dict["EndProbMatrix_prob"] 452 | 453 | V = [{}] #其中的字典保存 每个时间步上的每个状态对应的概率 454 | path = {} 455 | 456 | #初始概率 457 | for y in self.state_list: 458 | V[0][y] = init_state[y] + emit_prob[y].get(sentence[0],zero_log) 459 | path[y] = [y] 460 | 461 | #从第二次到最后一个时间步 462 | for t in range(1,len(sentence)): 463 | V.append({}) 464 | newpath = {} 465 | for y in self.state_list: #遍历所有的当前状态 466 | temp_state_prob_list = [] 467 | for y0 in self.state_list: #遍历所有的前一次状态 468 | cur_prob = V[t-1][y0]+trans_prob[y0][y]+emit_prob[y].get(sentence[t],zero_log) 469 | temp_state_prob_list.append([cur_prob,y0]) 470 | #取最大值,作为当前时间步的概率 471 | prob,state = sorted(temp_state_prob_list,key=lambda x:x[0],reverse=True)[0] 472 | #保存当前时间步,当前状态的概率 473 | V[t][y] = prob 474 | #保存当前的状态到newpath中 475 | newpath[y] = path[state] + [y] 476 | #让path为新建的newpath 477 | path = newpath 478 | 479 | #输出的最后一个结果只会是S(表示单个字)或者E(表示结束符) 480 | (prob, state) = max([(V[len(sentence)][y]+end_prob[y], y) for y in ["S","E"]]) 481 | return (prob, path[state]) 482 | ``` 483 | 484 | 485 | 486 | -------------------------------------------------------------------------------- /补充/MEMM和CRF.md: -------------------------------------------------------------------------------- 1 | ## 最大熵马尔科夫和CRF 2 | 3 | 4 | 5 | 最大熵模型(MaxEnt):指的是多元逻辑回归 6 | 7 | 由于等概率的分布具有最大熵,所以最大熵的模型通过词性标注问题来描述就是: 8 | 9 | 1. 在没有任何假设的情况下,认为每种词性的概率都是相同的,假设有10中词性,那么每个词性的概率都是1/10 10 | 2. 如果语料表明,所有的词语出现的词性只有10个中的四个,那么此时,调整所有词的词性为$A:1/4 ,B:1/4,C:1.4,D:1/4,E:0....$ 11 | 3. 当我们继续增加语料,发现A和B的概率很高,10次中有8次,某个词的词性不是A就是B,那么此时调整词性概率为:$A:4/10,B:4/10,C:1/10,D:1/10$ 12 | 4. 重复上述过程 13 | 14 | 寻找一个熵最大的模型,就是要使用多元逻辑回归,训练他的权重w,让训练数据能够似然度最大化 15 | 16 | > 训练数据能够似然度最大化:训练数据是总体的一个抽样,让训练数据尽可能能够代表总体,从而可以让模型可以有更好的表现力 17 | 18 | **最大熵马尔科夫模型(MEMM)**是马尔科夫模型的变化版本。在马尔科夫模型中,我们使用贝叶斯理论来计算最有可能的观测序列,即: 19 | $$ 20 | \hat{t}_n = \mathop{argmax}_{t_n}P(t_n|w_n) = \mathop{argmax}_{t_n}P(w_i|t_i)P(t_i|t_{i-1}) 21 | $$ 22 | 但是在MEMM中,他直接去计算了后验概率P(t|w),直接对每个观测值的状态进行分类,在MEMM中,把概率进行了拆解: 23 | $$ 24 | \hat{T} = \mathop{argmax}_T P(T|W) = \mathop{argmax}\prod_i P(tag_i|word_i,tag_{i-1}) 25 | $$ 26 | 即:使用前一个状态tag和当前的词word,计算当前tag。 27 | 28 | 和隐马尔可夫模型不同的是,在上述的公式中,对于计算当前tag的分类过程中,输入不仅可以是$word_i和tag_{i-1}$,还可以包含其他的特征,比如:词语的第一个字母是否为大写,词语的后缀类型,前缀类型的等等。 29 | 30 | 所以MEMM的表现力会比HMM要更好。 31 | 32 | 33 | 34 | ## 条件随机场 35 | 36 | **条件随机场(conditional random field,CRF)**是有输入x和输出y组成的一种无向图模型,可以看成是最大熵马尔可夫模型的推广。 37 | 38 | 下图是我们的常用于词性标注的线性链 条件随机场的图结构。其中x是观测序列,Y是标记序列 39 | 40 | ![](../../../../NLP%E8%AF%BE%E4%BB%B6%E7%BC%96%E5%86%99/markdown/doc/images/%E8%A1%A5%E5%85%85/%E6%9D%A1%E4%BB%B6%E9%9A%8F%E6%9C%BA%E5%9C%BA.png) 41 | 42 | 下图是HMM,MEMM,CRF的对比 43 | 44 | ![](../../../../NLP%E8%AF%BE%E4%BB%B6%E7%BC%96%E5%86%99/markdown/doc/images/%E8%A1%A5%E5%85%85/%E4%B8%8D%E5%90%8C%E5%AF%B9%E6%AF%94.png) 45 | 46 | 47 | 48 | 当观测序列为 $x=x_1,x_2...$ 时,状态序列为 $y=y_1,y_2....$的概率可写为: 49 | $$ 50 | P(Y=y|x)=\frac{1}{Z(x)}\exp\biggl(\sum_k\lambda_k\sum_it_k(y_{i-1},y_i,x,i)+\sum_l\mu_l\sum_is_l(y_i,x,i)\biggr) \\ 51 | Z(x)=\sum_y\exp\biggl(\sum_k\lambda_k\sum_it_k(y_{i-1},y_i,x,i)+\sum_l\mu_l\sum_is_l(y_i,x,i)\biggr) 52 | $$ 53 | 其中$Z(x)$是归一化因子,类似softmax中的分母,计算的是所有可能的y的和 54 | 55 | 后面的部分由**特征函数**组成: 56 | 57 | **转移特征:** $t_k(y_{i-1},y_i,x,i)$ 是定义在边上的特征函数(transition),依赖于当前位置 i 和前一位置 i-1 ;对应的权值为 $\lambda_k$ 。 58 | 59 | **状态特征:** $s_l(y_i,x,i)$ 是定义在节点上的特征函数(state),依赖于当前位置 i ;对应的权值为 $\mu_l$ 。 60 | 61 | 一般来说,特征函数的取值为 1 或 0 ,当满足规定好的特征条件时取值为 1 ,否则为 0 。 62 | 63 | 对于`北\B京\E欢\B迎\E你\E`特征函数可以如下: 64 | 65 | ``` 66 | func1 = if (output = B and feature="北") return 1 else return 0 67 | func2 = if (output = M and feature="北") return 1 else return 0 68 | func3 = if (output = E and feature="北") return 1 else return 0 69 | func4 = if (output = B and feature="京") return 1 else return 0 70 | ``` 71 | 72 | 每个特征函数的权值 类似于发射概率,是统计后的概率。 73 | 74 | -------------------------------------------------------------------------------- /补充/最大匹配法.md: -------------------------------------------------------------------------------- 1 | # 最大匹配法 2 | 3 | ## 目标 4 | 5 | 1. 知道正向最大匹配法 6 | 2. 知道逆向最大匹配法 7 | 3. 知道双向最大匹配法 8 | 9 | 10 | 11 | ## 起源 12 | 13 | 最大匹配法是最简单的分词方法,他完全使用词典进行分词,如果词典好,则分词的效果好 14 | 15 | 16 | 17 | ## 正向最大匹配法 18 | 19 | 正向,即从左往右进行匹配 20 | 21 | ```python 22 | #Maximum Match Method 最大匹配法 23 | 24 | class MM: 25 | def __init__(self): 26 | self.window_size = 4 27 | 28 | def cut(self,text): 29 | result = [] 30 | index = 0 31 | text_lenght = len(text) 32 | #研究生命的起源 33 | dic = ["研究","研究生","生命"] 34 | while text_lenght >index: 35 | #range(3,0,-1) 36 | for size in range(min(self.window_size+index,text_lenght),index,-1): 37 | piece = text[index:size] 38 | print("size:", size,piece) 39 | if piece in dic: 40 | index = size-1 41 | break 42 | index = index+1 #第一次结束index = 3 43 | result.append(piece) 44 | print(result) 45 | return result 46 | 47 | ``` 48 | 49 | 50 | 51 | ## 逆向最大匹配法 52 | 53 | 逆向即从右往左进行匹配 54 | 55 | ```python 56 | #RMM:Reverse Maxmium Match method 逆向最大匹配 57 | 58 | class RMM: 59 | def __init__(self): 60 | self.window_size = 3 61 | 62 | def cut(self,text): 63 | result = [] 64 | index = len(text) 65 | #研究生命的起源 66 | dic = ["研究","研究生","生命"] 67 | while index>0: 68 | for size in range(max((index-self.window_size),0),index): 69 | piece = text[size:index] 70 | print("size:", size,piece) 71 | if piece in dic: 72 | index = size+1 73 | print("index:", index) 74 | break 75 | print("index:",index) 76 | index = index - 1 77 | result.append(piece) 78 | result.reverse() 79 | print(result) 80 | return result 81 | ``` 82 | 83 | 84 | 85 | ## 双向最大匹配法 86 | 87 | 同时根据正向和逆向的结果,进行匹配 88 | 89 | ```python 90 | class MCut(): 91 | def __init__(self): 92 | self.mm = MM() 93 | self.rmm = RMM() 94 | 95 | def cut(self,sentence): 96 | """ 97 | 1. 词语数量不相同,选择分词后词语数量少的 98 | 2. 如果词语数量相同,返回单字数量少的 99 | """ 100 | mm_ret = self.mm.cut(sentence) 101 | rmm_ret = self.rmm.cut(sentence) 102 | if len(mm_ret)==len(rmm_ret): 103 | mm_ret_signle_len = len([i for i in mm_ret if len(i)==1]) 104 | rmm_ret_signle_len = len([i for i in rmm_ret if len(i)==1]) 105 | return mm_ret if rmm_ret_signle_len>mm_ret_signle_len else rmm_ret 106 | else: 107 | return mm_ret if len(mm_ret)