自然语言处理学习与实战(基础篇)


由于学习过程需要记录的内容远远超过预期,这导致这篇记录接近两万字,已经开始变得臃肿,所以我们后续会在新的文章中继续记录学习过程,并将这篇文章设定为“基础篇”,主体是通过几种向量工具来讨论文档的主题建模。


学习目录

0. 内容规范

  1. 记录数学公式,对于一些数学公式转换成的代码,尽量链接到本文外部;(本文可能出现较多的数学公式记录与简单推导)
  2. 对于学习文本、学习计划的更新,应该在相应位置处给出简要标记;
  3. 对于项目实践,本文记录其简单的文字描述,若有必要则通过外部链接给出代码;(但项目实践可能由多个文件构成,此种情况下源码不予展示)

1. 学习文本

已有:

  • 《自然语言处理实战 利用Python理解、分析和生成文本》
  • 《自然语言处理入门》

计划购入:

  • 《Python 深度学习》

2. 编程语言

  • Python作为基础框架
  • 神经网络部分代码采用Keras集成实现
  • 注意代码接口定义,尽量使用类集成,绝对采用函数实现分块

3. 学习痕迹

2020/11/17
切分算法与前缀树
  • 切分算法(完全切分、正向最长匹配、逆向最长匹配、双向最长匹配)
  • 字典树/前缀树
项目实践 - 中文分词系统:
  • [使用前缀树结构与双向最长匹配算法实现的朴素词典分词系统(词典基于PKU语料库的训练集,使用Python清洗降重,以JSON格式存储在TXT文件中,共有59311个词语)]

2020/11/18
正则表达式初步
  • re模块的简单使用(通过re.match()、re.search()、re.group()查找文本中的简单模式)
项目实践 - 聊天机器人:
  • [使用简单正则表达式硬编码匹配少量模式实现的聊天机器人系统(引入随机性;问答逻辑简单完整)]

2020/11/20
线性模型初步
项目实践 - 线性模型搭建(NumPy):

线性模型

y = a x + b y = ax+b y=ax+b

损失函数

l ( a , b ) = 1 2 m ∑ i = 1 m ( a x i + b − y l a b e l ) 2 l(a,b) = \frac{1}{2m}\sum_{i = 1}^{m} (ax_i + b - y_{label})^2 l(a,b)=2m1i=1m(axi+bylabel)2

梯度函数及其简单推导

∂ l ∂ a \frac{\partial l }{\partial a} al

= 1 2 m ∑ i = 1 m [ 2 ( a x i + b − y l a b e l ) x i ] = \frac{1}{2m}\sum_{i = 1}^{m}[2(ax_i+b-y_{label})x_i] =2m1i=1m[2(axi+bylabel)xi]

= 1 m ∑ i = 1 m [ ( a x i + b − y l a b e l ) x i ] =\frac{1}{m}\sum_{i = 1}^{m}[(ax_i+b-y_{label})x_i] =m1i=1m[(axi+bylabel)xi]

∂ l ∂ b \frac{\partial l }{\partial b} bl

= 1 2 m ∑ i = 1 m 2 ( a x i + b − y l a b e l ) = \frac{1}{2m}\sum_{i = 1}^{m}2(ax_i+b-y_{label}) =2m1i=1m2(axi+bylabel)

= 1 m ∑ i = 1 m ( a x i + b − y l a b e l ) =\frac{1}{m}\sum_{i = 1}^{m}(ax_i+b-y_{label}) =m1i=1m(axi+bylabel)

梯度下降迭代公式:

a : = a − α ∂ l ∂ a a := a - \alpha \frac{\partial l }{\partial a} a:=aαal

b : = b − α ∂ l ∂ b b := b - \alpha\frac{\partial l }{\partial b} b:=bαbl


计划更新安排(2020/11/22):
  • 脱离NLP的背景,开始独立学习以下部分:
    1. 感知机与反向传播 √
    2. 卷积神经网络CNN
    3. 循环神经网络RNN
    4. 长短期记忆网络LSTM

以上内容的学习基于《自然语言处理实战 利用Python理解、分析和生成文本》第5、7、8、9章内容,学习时关注背后的数学知识以及项目的代码构建,对于NLP部分的知识,采用 [非必要时不引入] 的原则,在下一个学习阶段将此部分神经网络的知识进一步与NLP的文本处理部分统一。


2020/11/22
感知机
  • 感知机:
    f ( X ) = { 1 , ∑ i = 0 n x i w i + b i > 阈 值 0 , 其 它 f(X)=\begin{cases}1,\sum\limits_{i=0}^{n}x_iw_i+b_i>阈值\\0,其它\end{cases} f(X)=1,i=0nxiwi+bi>0

其中,偏置量 b i b_i bi可以表示为 1 × w i 1 ×w_i 1×wi,作为输入特征为1的一组向量与偏置量的权重向量的积,这样处理的话,输入特征的维数变为了 n + 1 n+1 n+1,但是便于对偏置量的权重进行训练;另一种方法是对偏执向量单独训练。

需要注意的是,权重的初始值选取应该是0附近的一个值(通常选取正态分布中趋近与0的随机值),因为当权重都为0时,输入特征的前n个维度会被彻底抹除。

项目实践 - 感知机模型搭建(NumPy)- 学习逻辑与门:
  • 让感知机模型学会二变量的与门关系(引入的迭代公式为 w : = w + α ( l a b e l − y ) w:=w+\alpha(label-y) w:=w+α(labely), b : = b + α ( l a b e l − y ) b:=b+\alpha(label-y) b:=b+α(labely),迭代次数很小,但关键是 α \alpha α的设置;阈值为0.5),具体代码可以参考文章用简单的感知机模型学习两变量的逻辑与关系-Python实现
    • 模型修正:上述文章中的模型并没有收敛,进行的修改是增添一组[1,1]的训练样本,并适当调小 α \alpha α,大约在500次迭代左右收敛到
      w = [ 0.14025113 , 0.14025113 ] w = [0.14025113,0.14025113] w=[0.14025113,0.14025113]
      b = 0.23025112910662812 b = 0.23025112910662812 b=0.23025112910662812
  • 经过此次修改,模型的关键在于在基础的四个训练样本上将一个特殊的样本重复(经过测试,重复一次即可收敛),步长 α \alpha α的选取重要性降低,所需迭代次数在测试中体现出训练或门逻辑的迭代次数要小于训练与门所需的迭代次数
  • 另外需要注意的是,单个感知机可学习的样本必须是线性可分的,经过简单测试就可以发现单个感知机无法学习异或门,因为异或逻辑是非线性的。(在二变量输入构成的二维平面上,样本点无法被一条直线彻底切分)

2020/11/23
三大距离
  • 三大距离的数学公式

1.欧几里得距离:

d = ∑ i = 1 n ( x i − y i ) 2 d=\sqrt{\sum_{i=1}^{n}(x_i-y_i)^2} d=i=1n(xiyi)2

假设: r x = ∑ i = 1 n x i 2 r_x=\sqrt{\sum_{i=1}^nx_i^2} rx=i=1nxi2 , r y = ∑ i = 1 n y i 2 r_y=\sqrt{\sum_{i=1}^ny_i^2} ry=i=1nyi2 ;

则有规范化的欧几里得距离:

d = ∑ i = 1 n ( x i r x − y i r y ) 2 d=\sqrt{\sum_{i=1}^n(\dfrac{x_i}{r_x}-\dfrac{y_i}{r_y})^2} d=i=1n(rxxiryyi)2

欧几里得距离表示由向量定义的空间中两个点之间的直线距离;又被称为L2范数或者RSS距离(差值平方和的平方根)。

2.余弦距离:

定义余弦相似度(即向量之间夹角的余弦):

s = ∑ i = 1 n x i y i ∑ i = 1 n x i 2 ∑ i = 1 n y i 2 s=\dfrac{\sum_{i=1}^nx_iy_i}{\sqrt{\sum_{i=1}^nx_i^2}\sqrt{\sum_{i=1}^ny_i^2}} s=i=1nxi2 i=1nyi2 i=1nxiyi

则余弦距离的计算表示为
d = 1 − s d=1-s d=1s

优点:

  1. 结果是有界的(0到2),因为余弦相似度是有界的(-1到1);
  2. 当欧几里得距离相近时,比较余弦距离往往可以获得其它信息;

缺点:余弦距离并不是真正的距离度量,余弦距离不受三角不等式的限制;例如:

d x , y = 0.3 , d y , z = 0.5 d_{x,y}=0.3,d_{y,z}=0.5 dx,y=0.3,dy,z=0.5

按照三角不等式,有

d x , z < = 0.8 d_{x,z}<=0.8 dx,z<=0.8

但实际上 d x , z d_{x,z} dx,z可能远远大于0.8。

3.曼哈顿距离:

d = ∑ i = 1 n ∣ x i − y i ∣ d=\sum_{i=1}^n|x_i-y_i| d=i=1nxiyi

曼哈顿距离计算所有维度的绝对距离之和,也被称为L1范数。

需要注意的是,归一化向量之间的曼哈顿距离无法保持在一个统一的范围,其曼哈顿距离的最大值会随着维数的增长而增加。

项目实践 - 三大距离计算平台(无任何优化):
  • 使用Python搭建程序计算欧几里得距离、余弦距离以及曼哈顿距离
2020/11/24
前馈神经网络BP

前馈神经网络BP初步数学概念以及简单应用

  • 激活函数 - sigmoid函数:

sigmoid函数是一个非线性连续可微的激活函数,非线性的特点使得神经网络可以对非线性关系进行建模,而连续可微则保证了误差可以平滑的反向传播到多层神经元,以加速训练进程。

sigmoid函数的表达式:

S ( x ) = 1 1 + e − x S(x)=\dfrac{1}{1+e^{-x}} S(x)=1+ex1

sigmoid函数求导:

d S d x = S ( 1 − S ) \dfrac{dS}{dx}=S(1-S) dxdS=S(1S)

  • 平方误差MSE

M S E = [ y − f ( x ) ] 2 2 MSE=\dfrac{[y-f(x)]^2}{2} MSE=2[yf(x)]2

  • 输出层的权重更新:

先假设 i i i属于隐藏层, j j j属于输出层,我们来更新 w i j w_{ij} wij

y j = S ( w i j y i ) y_j=S(w_{ij}y_i) yj=S(wijyi)

w i j : = w i j − α d M S E d w i j w_{ij}:=w_{ij}-\alpha\dfrac{dMSE}{dw_{ij}} wij:=wijαdwijdMSE

w i j : = w i j − α ( y j − f ( x ) j ) y j ( 1 − y j ) y i w_{ij}:=w_{ij}-\alpha (y_j-f(x)_j)y_j(1-y_j)y_i wij:=wijα(yjf(x)j)yj(1yj)yi

公式补充 - 激活函数:

y j = s i g m o i d ( ∑ i ∈ I w i j y i ) y_j=sigmoid(\sum_{i \in I} w_{ij}y_i) yj=sigmoid(iIwijyi)

由于对于输出层,我们已经知道第 j j j个节点的真实值 f ( x ) j f(x)_j f(x)j,所以隐藏层到输出层的权值更新比较简单。

  • 隐藏层的权重更新:

对于隐藏层,我们无法直接求得 y j − f ( x ) j y_j-f(x)_j yjf(x)j,而是需要下一层 l l l节点反向传播回来的误差,所以该项被替换为 ∑ l ∈ L w j l δ l \sum_{l\in L}w_{jl}\delta{_l} lLwjlδl,其中 δ l \delta{_l} δl为下一层第 l l l个节点的误差项;我们的公式更新为

w i j : = w i j − α ( ∑ l ∈ L w j l δ l ) y j ( 1 − y j ) y i w_{ij}:=w_{ij}-\alpha (\sum_{l\in L}w_{jl}\delta{_l})y_j(1-y_j)y_i wij:=wijα(lLwjlδl)yj(1yj)yi

项目实践 - BP神经网络的搭建(Keras)- 学习逻辑异或门:
  • 用Keras库搭建一个简单的神经网络解决异或问题(熟悉用Keras搭建神经网络的方法以及一些关键参数的实际作用与使用方法;自己定义新的接口;用H5格式的文件保存模型结构以及权重)
    具体代码参考简单的神经网络模型 - 学习与保存异或逻辑关系 - Keras实现
    使用Keras库实现自己的神经网络模型时特点是无须对其背后的数学原理进行具体操作。

2020/11/25
前馈神经网络BP

再论BP神经网络的数学原理
(1)
激活函数: s ( x ) = 1 1 + e − x s(x)=\dfrac{1}{1+e^{-x}} s(x)=1+ex1

(2)
隐藏层输出: y j = s ( ∑ i = 1 n w i j y i + b i j ) y_j=s(\sum_{i=1}^{n}w_{ij}y_i+b_{ij}) yj=s(i=1nwijyi+bij)

(3)
输出层输出: y k = s ( ∑ j = 1 n w j k y j + b j k ) y_k=s(\sum_{j=1}^nw_{jk}y_j+b_{jk}) yk=s(j=1nwjkyj+bjk)

(4)
损失函数: L = 1 2 ∑ k = 1 n ( y l a b e l − y k ) 2 L=\dfrac{1}{2}\sum_{k=1}^{n}(y_{label}-y_k)^2 L=21k=1n(ylabelyk)2

(5)
输出层梯度函数:

∂ L ∂ w j k = ∑ k = 1 n [ ( y l a b e l − y k ) − ∂ y k ∂ w j k ] = − ∑ k = 1 n [ ( y l a b e l − y k ) y k ( 1 − y k ) ∑ j = 1 n w j k ] \dfrac{\partial L}{\partial w_{jk}}=\sum_{k=1}^{n}[(y_{label}-y_k)\dfrac{-\partial y_k}{\partial w_{jk}}]\\=-\sum_{k=1}^{n}[(y_{label}-y_k)y_k(1-y_k)\sum_{j=1}^{n}w_{jk}] wjkL=k=1n[(ylabelyk)wjkyk]=k=1n[(ylabelyk)yk(1yk)j=1nwjk]

(6)
隐藏层梯度函数:
∂ L ∂ w i j = ∂ L ∂ y j ∂ y j ∂ w i j \dfrac{\partial L}{\partial w_{ij}}=\dfrac{\partial L}{\partial y_j}\dfrac{\partial y_j}{\partial w_{ij}} wijL=yjLwijyj

∂ L ∂ y j = ∑ k = 1 n [ ( y l a b l e − y k ) − ∂ y k ∂ y j ] = − ∑ k = 1 n [ ( y l a b l e − y k ) y k ( 1 − y k ) ∑ j = 1 n w j k ] \dfrac{\partial L}{\partial y_j}=\sum_{k=1}^n[(y_{lable}-y_k)\dfrac{-\partial y_k}{\partial y_j}]\\=-\sum_{k=1}^n[(y_{lable}-y_k)y_k(1-y_k)\sum_{j=1}^{n}w_{jk}] yjL=k=1n[(ylableyk)yjyk]=k=1n[(ylableyk)yk(1yk)j=1nwjk]

∂ y j ∂ w i j = y j ( 1 − y j ) ∑ i = 1 n y i \dfrac{\partial y_j}{\partial w_{ij}}=y_j(1-y_j)\sum_{i=1}^{n}y_{i} wijyj=yj(1yj)i=1nyi

注意对特定的节点 i、j、k上述公式中的累加符号可去。


卷积神经网络CNN

卷积神经网络CNN初步

基础概念构建:

  • 卷积核:
    卷积核由一组权重和一个激活函数构成;卷积核的权重是固定的,当卷积核覆盖到某片数据时,对应位置的数据与权重相乘,最后对这些运算求和并传入激活函数;卷积核的激活函数通常选择线性修正单元ReLU函数。
    一批数据上可以有多个卷积核,每个卷积核对数据进行处理后会得到一组新数据,卷积核的这种操作也可以被称为滤波(卷积核实际上就是某种滤波器)。
    卷积运算也可以多次进行,通过不同的卷积核实现对原始数据不同“知识”(例如边缘、形状、颜色、含义)进行学习。
    卷积核的作用是让网络学会检测和提取信息,以便让后面的层能更好的工作。

  • 线性修正单元ReLU: y = m a x { s u m ( w i j x i j ) , 0 } y=max\{sum(w_{ij}x_{ij}),0\} y=max{sum(wijxij),0}

  • 步长:
    步长设置小于卷积核宽度,这种特性使得卷积核每次起作用时都会有有相邻的部分被处理,使得相邻像素(或者相邻词条)之间有模糊效果;由于卷积核的大小通常不是很大,所以步长的设置通常为1。

  • 填充:
    即使是以1为步长,在边缘上进行的卷积运算仍然可能使得输出数据的规模小于原始数据的规模。这种性质使得边缘数据的处理次数小于内部数据,被称为边缘数据被欠采样;当原始数据规模足够大时,这种规模的降低可以被忽略,但当原始数据不够大时,欠采样可能回对模型产生较大影响。为了避免欠采样,进行卷积运算前常常需要对原始数据进行填充操作。

卷积神经网络的学习:

  • 卷积核也会以随机小量作为初始值,其更新也依赖于激活函数对误差函数值得反向传播。
  • 卷积核得梯度公式: ∂ E ∂ w a b = ∑ i = 0 m ∑ j = 0 n ( ∂ E ∂ x i j ∂ x i j ∂ w a b ) \dfrac{\partial E}{\partial w_{ab}}=\sum_{i=0}^{m}\sum_{j=0}^{n}(\dfrac{\partial E}{\partial x_{ij}}\dfrac{\partial x_{ij}}{\partial w_{ab}}) wabE=i=0mj=0n(xijEwabxij)
    也就是说,对于一个卷积核得权重,梯度是前向传播中卷积得每个位置上梯度得和。

NLP与CV中得不同卷积核:

在语言处理中,句子本身是一维的,其主要信息展现在水平方向上,但是词本身的向量是“向下”的,所以,NLP中的卷积核是假性一维卷积,也就是说,卷积的第二个维度等于单个词向量的维度(即卷积核的垂直长度与原始数据的垂直长度相同),卷积核只需从左到右在一个维度滑动滤波即可。


计划更新安排(2020/11/26)

在实现了BP神经网络后,尝试对CNN进行实践;在实践过程中发现需要对词向量的理解(更具体的说,是从训练文本到词向量的转化工程),且由于考虑到之后的RNN以及LSTM网络的实现也需要关于语言向量化的基本知识,所以决定对学习计划做出修改:

  • 返回基础文本处理部分,在学习过程中补充NLP领域的基础知识

    • 第2章:分词 √
    • 第3章:向量化√
    • 第4章:语义分析 - 主题向量
    • 第6章:词向量推理
  • 理解从词到向量的过程后,继续学习神经网络

    • CNN
    • RNN
    • LSTM

总结:关于神经网络基础知识(学习率、权重、激活函数、全连接、损失函数、梯度函数等)以及Keras基础应用,在BP神经网络的学习以及搭建中已经完成。

2020/11/30补充:在简单了解了神经网络的内容之后,学习重点继续放在NLP上,回到第二章开始学习,此学习过程可能会持续两周左右。


2020/11/30
  • Word2vec训练算法 - 概念性了解

此算法为无监督学习算法,目的是通过预测句子中目标词附近的词来理解词义的模糊特性;之所以将其作为无监督学习,是因为训练数据的标签就是待预测的相邻词,而这些“标签”都来自训练数据本身。

更具体一点,词向量可以看作一个权重列表,而文档中某个词周围的词会感染这个词的某个权重,将其意义融入到词向量中。词向量具有“面向向量的推理”过程,我们可以使用词向量进行数学运算,再将得到的数学结果转化为词。

关于此算法的具体实现仍然需要下面将提到的某些知识。

  • 分词

分词将自然语言文本这种非结构化的数据切分为多个信息块,使得文本所包含的某部分信息变成了离散化的元素,这些元素的频率可以直接用于文档的向量表示。通过分词,我们可以构建每个词的数值向量表示 - 独热向量。

独热向量

特点:

  • 保留原始文档的所有信息;
  • 可以从独热向量中完全还原原始文本;
  • 需要很大的空间;
  • 对独热向量简单相加即可得到词频向量;
项目实践:模拟独热向量的产生过程:

完全保留文档信息的关键是在对文本进行分词时,降重后的分词结果作为独热向量的列、未降重的分词结果作为独热向量的行,且这个分词过程要求保留原文档的语序。

完整代码:独热向量的产生过程

项目简单评价:

  • 要想产生能完全还原原始文本的独热向量,需要在分词时做出限制;
  • 独热向量的列的增长取决于文档中相同词汇的规模;
  • 独热向量简单相加即可产生词频向量;

独热向量简单描述:一个i行j列的矩阵,每一行代表一个词,且第i行代表文本中出现的第i个词;每一行所代表的词通过其行向量表示,具体为该稀疏的行向量中值1所在列对应的词即为该行所对应的词。

  • 用点积运算度量两个“词袋”间的距离

我们可以使用一个向量表示一个句子或者一篇文档,需要做的只是创建一个向量,向量中每个位置对应一个将可能出现的词,对于我们要转化的句子或者文档,当其中出现某个词时,我们只需要在向量对应的位置上加1即可(此向量使用np.zeros初始化即可)。

这样的向量虽然丢失了原来文本的很多信息,且表示出来以后比较稀疏(蕴含的思想类似于独热编码),但我们可以通过简单的点积运算来度量这样的两个向量的“距离”,进而度量两个向量表示的文本的相似度。

关于一个正则表达式实例的简单讨论与常用的英文分词工具示例
  • 正则表达式

    • 模式实例解析
r'[-\s.,;!?]+'

[ ]表示一个字符集;

r用来定义原始字符串(即不可变的字符串);

连字符 - 只有放在[ ]中第一个位置的 - 才表示符号 - ,其它位置出现的 - 均被解释为字符区间;

\s是正则表达式中的一个预定义字符集,包含了6个空白字符;

+表示此模式匹配字符集中的一个或者多个字符;

上面展示的这个模式用于查找文档中所有常见的标点符号;

re.split()

此函数根据正则表达式提供的模式(例如上面展示的常见标点符号)进行匹配,匹配到的模式被前后切分,类似str.split,只不过具有更强的分隔符指定方法。

  • 另一种使用方法:对模式进行预编译
pattern = r'[-\s,.;?!]'
pattern = pattern.compile(pattern)
pattern.split(document)

当定义了很多模式的时候,通过调用编译好的模型使用正则表达式效率要高于通过re使用模式。

  • 英文分词的“jieba库”
from nltk.tokenize import TreebankWordTokenizer

tokenizer = TreebankWordTokenizer()
tokenizer.tokenize(sentence)

此分词器的局限性是其假设已对文本进行了句切分,这样对于未经过句切分的文档,除了最后一个句子之外,其余的句子被切分的最后一个词总是带着一个句号。

  • jieba库分词示例:
import jieba

jieba.lcut(sentence)
n-gram - n元语法简单讨论
  • n-gram - n元语法

n-gram是一系列含有n个元素的序列,用于在分词时保留一部分上下文信息;以2-gram为例,最简单的2-gram分词结果就是将一个句子所有相邻的两个词提取为一个序列;例如“北京理工大学”的分词结果为[“北京”,“理工”,“大学”],使用2-gram分词结果可以表示为[(“北京”,“理工”), (“理工”,“大学”)]。

n的增长会给词条数带来指数规模的增长。

使用nltk库可以进行n-gram分词,需要的只是一个分词好的列表:

from nltk.util import ngrams

list(ngrams(myList,2))

上述函数ngrams()将分好的列表myList转换为一个2-gram模型,其中每个2-gram使用一个元组表示;之所以要进行类型转换,是因为此函数返回的实际上是一个生成器。

生成器generator的行为特点是一次只生成一个元素,而不是一次将所有序列返回,这样的行为特点使其在迭代大型数据时可以很有效的节省内存。

  • n-gram的过滤

在实际情况中,大部分的n-gram都不会出现,所以出现次数过少的n-gram会被过滤掉;同时一些过于常见的n-gram可能是由于语言的语法产生的,这类n-gram在不同的文本中意义几乎相同,所以也会被过滤掉——也就是说,出现频率过高(在语料库中超过25%的文档中都出现的词条)或者过低(出现该词条的文档数不多于3)的n-gram都会被过滤掉。

  • 进一步讨论停用词

上述出现频率过高的词条被称为停用词,但停用词的加入可能会导致文本中上下属关系信息的丢失,解决这种问题的一个方式增加n-gram中的n以保留这种从属关系。所以,停用词的加入需要慎重。

当数据规模不够大时,我们也可能会担心模型对这些反复出现的词条过拟合,但是这种情况有更好的解决方法,禁止停用词可以保证模型使用最小的n-gram来进行足够精确的工作。

不同的模型框架对停用词的选择也有较大差别,并且往往是随时更新的,直接引入这些框架提供的停用词表可能会导致工作无法被复现。

针对英文的词汇表归一化的几种方法讨论
  • 简单讨论大小写归一化

大小写归一化带来的好处是将词汇表规模减小一半,这样能带来存储和处理效率的提升;但是大小写归一化的缺点则是专有名词的信息损失。

好的选择是只对句首的词进行大小写归一化,同时在归一化之前先进行专有名词识别;这样既能带来效率的提升,又能最小程度的减少信息的丢失。

  • 词干还原

词干换原是一种降维方法,能在可接受信息损失限度内最大程度的压缩词汇表规模。具体来说,词干还原指的是将具有相似意义的词归并到一个公共词干下,例如消除复数、所有格甚至将动词形式换原为名词等(不同的激进程度)。

  • 词性归并

不论是词干还原还是大小写归一化,这些方法都没有考虑词本身的含义;词性归并是一种使用同义词表、词性标签(POS)等的方法,这种方法尽量确保具有相似意义的词才会被归并到同一个词条。

  • 总结:

上述三种方法的目的都是降低词汇表规模,同时不可避免的导致文本信息的丢失、增加文本的歧义性。与n-gram类似,这些方法都需要我们在信息量与复杂度之间做出抉择,因为二者是同时向相同的方向变化的。

当这些方法用在搜索领域时,也有这样的博弈:提高文字的召回率(对一系列搜索关键字返回更多目标文章)的同时,会大大降低返回结果的正确率与精确率。

事实上,这些技术在大部分场景下都弊大于利。

关于这些技术的实际应用,由于大部分归一化方法都涉及许多语法规则,所以通常使用其他人维护的词汇表以及这些词汇表上复杂规则形成的算法;自己实现真正可以应用的归一化框架并没有什么意义(成形的框架中形成的规则往往经过了专家的分析以及长时间的迭代)。


综上所述,我们可以使用的词条包括:

  • 未经处理的单个词
  • n-gram词条
  • 词干
  • 词元

两种情感分析的方法简介
  • 基于规则的算法:

将文本分解成词干、词元或者n-gram词条,通过制定好的规则迭加文档中关键词在字典上的情感得分。此类算法的基础是一个包含一系列关键词以及相应关键词情感得分的字典,以及一系列处理关键词的规则。

此类算法的代表是VADER算法。

  • 利用机器学习方法

机器学习的方法都需要一系列标注语句或者文档来训练机器学习模型,这一过程可以自动产生规则。

例如,使用朴素贝叶斯模型:朴素贝叶斯模型的目的是从一系列集合中寻找对目标变量有预测作用的关键词。当目标变量是要预测的情感时,模型的任务就变成了自动寻找能预测情感的关键词以及这些关键词背后隐藏的规则。并且朴素贝叶斯模型内部的系数会将词条自动映射为情感得分。


2020/12/1
统计词频的Python技巧:
from collections import Counter

myList = [1,1,1,2,2,5,4,5,4,5,4,6,9,9,9,9]

print(Counter(myList))

返回的结果是一个对列表中出现的元素个数进行计数的字典:

Counter({9: 4, 1: 3, 5: 3, 4: 3, 2: 2, 6: 1})

这个Counter对象实现的字典具有一定的有序性(经过了内部优化),即将元素按照出现次数进行排序,但是将其转换为Python内置的dict类时,这种有序性将会消失。不管哪种字典,蕴含在词序中的信息都会被忽略

这样的一个Counter对象可以被称为袋(bag),对文本进行词频的统计形成的向量我们可以称其为词袋;词袋装有文本的一部分信息:词项频率TF。词项频率一半要通过文档词项总数进行归一化。词袋就是一个词频向量,其它工具例如n-gram袋、TF-IDF向量(DF指的是文档频率,即某个词出现的文档在总的文档书中的占比,而IDF则是逆文档频率,指的是文档频率的倒数)与词袋一样,都是对词在文档中重要度的一种数学度量表示方法。

另一个Counter对象的技巧是most_common方法:

myBag = Counter(myList)
print(myBag.most_common())

结果类似下面的形式:


[(‘自然语言’, 2), (‘领域’, 2), (‘与’, 2), (‘的’, 2), (’。’, 2), (‘处理’, 1), (‘是’, 1), (‘计算机科学’, 1), (‘人工智能’, 1), (‘中’, 1), (‘一个’, 1), (‘重要’, 1), (‘方向’, 1), (‘它’, 1), (‘研究’, 1), (‘能’, 1), (‘实现’, 1), (‘人’, 1), (‘计算机’, 1), (‘之间’, 1), (‘用’, 1), (‘进行’, 1), (‘有效’, 1), (‘通信’, 1), (‘各种’, 1), (‘理论’, 1), (‘和’, 1), (‘方法’, 1)]


这是一个以二元组为元素的列表,二元组的第一个元素是词,第二个元素是对应的词频,我们可以给这个方法传递一个参数n:int,用来访问出现频率最高的前n个元素。


计划更新安排(2020/12/1) - 初步完成

由于后续神经网络实现可能都会使用集成化工具,且集成化工具的优化确实比自己实现好很多,这样的过程可能导致对神经网络模型背后的数学原理的忽略,所以在《前馈神经网络的数学表述》中,将尝试自行推导BP神经网络的关键算法背后的数学公式。

目前为止,已经多次讨论了BP神经网络的数学原理,其关键在于如何将反向传播矩阵化。为了不让这部分内容影响学习进展:

  • 在后期抽时间找到《Python神经网络编程》一书,详细阅读BP神经网络背后算法的矩阵化过程。
  • 后续学习继续集中在《自然语言处理实战 利用Python理解、分析和生成文本》一书的第3、4章内容中。

2020/12/5
文档的向量化

多篇文档构成了一个文档集,文档集中出现的词的数量作为向量空间的维数,构成了一个K维向量,我们可以使用这个K维向量空间中的一个向量表示一篇文档,对于这篇文档来说,这个向量就是其词频向量。

对于两个向量表示的文档,向量的长度实际上反映了文档的长度,但我们关心两篇文章主题的相似度时,并不想过多考虑文档的长度(其对文章主题的影响很小),所以我们更多的使用余弦相似度来考察文档主题的联系。

项目实践:计算三篇文档的词频向量,并计算余弦相似度

项目代码:NLP实践:计算三篇文章的余弦相似度

项目评价:

  • 加入了简单的停用词系统,包括标点符号以及“的”(因为我们不考虑词条之间的从属关系)。
  • 使用JSON文件存储与提取信息,简化了代码的重复性工作。

错误分析:

  • 此项目对文档词的频数进行归一化时,实际上用的是所有文档的词条数,而不是对应文档的词条数,这样计算出来的向量实际上并非是TF向量。
总结

在将文档向量化的过程中,我们围绕的中心是如何将文档数值化并考量其意义;我们首先建立了独热编码,但发现这种表示过于臃肿,并且无法直观的展现更多意义,独热编码本质上只是一种文档的再现方式,没有任何对信息的提取。

之后我们使用了TF向量用以计算文档的意义或者说主题,并在TF向量的基础上对一些在所有文本中都大量出现的词汇进行了处理,建立了新的TF-IDF向量方法,这个工具的工作效果使得我们很接近文档的主题,但我们发现TF-IDF向量只是一种统计学的简单应用,并没有真正涉及到文本或者词汇的意义——直观的说,这种工具在同义词上的表现没有展现出其对主题或者意义的任何考量。

所以,我们在TF-IDF的基础上进一步提出了主题向量模型。

纵观上面的过程,我们获得了越来越多的信息,但我们使用的工具的维度越来越低,这是一个降维/压缩的过程,这样的过程也必然导致我们得到这些工具的方法越来越复杂。

2020/12/7
齐普夫定律

齐普夫定律:在给定的自然语言语料库中,任何一个词的频率与它在频率表中的排名成反比。

  • 数学描述:

Y 为 某 个 词 条 在 所 以 词 条 频 率 中 的 排 名 , X 为 此 词 条 的 频 率 : Y为某个词条在所以词条频率中的排名,X为此词条的频率: YX:

Y = 1 X Y = \dfrac{1}{X} Y=X1

l o g Y = − l o g X logY=-logX logY=logX

  • 解释:

如果把语料库中的词按照出现次数降序排列,则对于一个足够大的语料库,出现次数排名第一的词的出现次数为出现次数排名第二的词的两倍、排名第三的词的三倍……

也就是说,齐普夫定律使得词的频数按照指数方式增长。

IDF向量:词项逆文档频率

当我们使用词频TF向量来表示一篇文档的主题时,存在这样一个问题:某些词在所有文档中都出现过很多次,这样的词对文档含义的实际贡献很少,但是在TF向量中它占有很大的权重,这会导致我们对文档主题的判断被这样的词带入某个误区。

所以,我们引入IDF词项逆文档频率来解决这个问题:

i d f = n ( 总 文 档 ) n ( 该 词 出 现 的 文 档 ) idf=\dfrac{n(总文档)}{n(该词出现的文档)} idf=n()n()

通过IDF向量的修正,我们将文档主题的表示引向一些在某篇文档中出现次数足够多,且很少在其它文档中出现的词条。

TF-IDF向量的计算公式

在实际使用TF-IDF向量时,我们通常使用对数对向量进行尺度缩放,例如下面的计算模型:

t f ( t , d ) = t 在 d 中 出 现 的 次 数 d 的 长 度 tf(t,d)=\dfrac{t在d中出现的次数}{d的长度} tf(t,d)=dtd

i d f ( t , D ) = l o g 文 档 数 包 含 t 的 文 档 数 idf(t,D)=log\frac{文档数}{包含t的文档数} idf(t,D)=logt

t f i d f ( t , d , D ) = t f ( t , d ) × i d f ( t , D ) tfidf(t,d,D)=tf(t,d) × idf(t,D) tfidf(t,d,D)=tf(t,d)×idf(t,D)

项目实践:使用未修正的TF-IDF向量计算文章的余弦相似度:
  • 项目代码:TF-IDF向量的计算 - Python实现
  • 项目评价:
    • 余弦相似度的结果比较符合对文章主题的把握;
    • 由于文档数量比较小,所以在计算IDF向量的时候并没有进行对数缩放;
    • 在实践的过程中可以看到,即使文档数比较小,得到的TF-IDF向量仍旧是比较稀疏的;
    • 在实践的过程中可以发现,加入停用词往往能使系统工作更加稳定,例如在汉语中一些常用的从属关系词条“的”、“与”等,往往占有较大比重,这些词在语料较少的情况下,IDF向量对其调整的结果并不是很明显,所以可以使用加入停用词的方法对其进行特定的修正。
    • 此系统稍加改进也可以用来获得一篇文章的主题,即返回TF-IDF得分最高的几项(返回几个关键词条)来展示文档的主题。

词项频率-逆文档频率向量(TF-IDF向量)的缺陷

我们使用TF-IDF向量的目的是考量文档的主题,但TF-IDF向量有一个关键的缺陷:如果两段文字讨论同一个内容,但在描述内容时使用了不同的词,那么在TF-IDF向量空间中,这两篇文档的接近程度可能不是很高——即使它们讨论的是同一个内容。

简单来说:**具有不同表述形式的同义词所产生的TF-IDF向量在向量空间中彼此并不接近,**同时,TF-IDF向量的稀疏性也许也是一个潜在的问题(或许不是)。

我们需要新的工具——主题向量;词或者文档都可以计算出主题向量,主题向量的维度可以很高,也可以变得紧凑,使用主题向量这个工具时,我们能得到比TF-IDF向量更多的信息——真正关于意义的信息。

关于TF-IDF向量空间:我们可以很容易的发现,这个空间的每一个维度实际上代表着一个将会出现的词。


主题建模 - 主题矩阵

我们可以自己手动搭建一个主题模型,并用向量表示:

X 是 一 个 m 行 n 列 的 主 题 模 型 矩 阵 X是一个m行n列的主题模型矩阵 Xmn

对于这样一个主题模型矩阵,它的行代表某个我们希望进行建模的意义,该行的元素值则是所有词条对这个主题的贡献权重(可正可负),对于一个文本,我们可以使用这个矩阵来得到文本在我们建模的意义下的得分。

当我们手动建立这样的主题模型时,问题在于如何得到每个意义下每个词条的意义权重。正是这些权重赋予文本以词意,而我们的语义分析算法的目的是更高效、更精确、更稳健的找出这些意义权重。

对于这类使用主题-权重的矩阵,我们可以对其进行转置:每一行代表一个词,而该行向量在不同主题下的权重变成了其在不同主题下的维度,可以很好的反映这个词的主题——这个行向量就可以作为这个词的主题向量。对于上述模型,我们也可以很容易的得到一个n维向量,这个向量空间的维度变成了主题,这样我们也可以得到一个文档的主题向量。

压缩/降维

上述过程存在一个明显的降维过程:我们的行向量的维度与TF-IDF向量的维度相同,但我们通过模型中的意义权重将稀疏的TF-IDF向量进行了压缩,或者说降维——结果就是我们定义出来的主题构成的维度,所以主题向量的维度要远远小于TF-IDF向量的维度。

上述分析展示了这个模型具有很好的价值,唯一的问题在于,当我们人工构建这样的模型时,对权重的赋予具有主观性,更大的问题在于人工无法处理足够多的词汇,也就是仅靠人力很难为大量的词在某个主题下赋予合适的权重,所以,要想让这个模型能够正常工作,我们需要一种不依赖尝试或者个人主观的算法来实现这个模型。


主题矩阵与TF-IDF向量的联系

有了主题矩阵,我们将其TF-IDF向量矩阵相乘,这个过程能使得TF-IDF向量降维——通常来说,我们定义的主题(主题矩阵的行数)是要远远小于文档词条数(主题矩阵代的列数或者TF-IDF向量的大小)的。

我们只需得到主题矩阵,然后将TF-IDF向量与之相乘,就能得到我们想要的主题矩阵。

语言学家认为:

可以通过词的上下文来理解它。

LSA简介/引入

LSA算法,其数学计算方法与所谓主成分分析(PCA)是一样的,只不过前者用于NLP领域,而后者用于图像处理领域。二者的目的都是一样的——降维;LSA通过TF-IDF向量代表的词汇表中的词数来获得主题向量。

在过去,LSA又被称为LSI(隐形语义索引),但由于这种算法 本身并不产生索引,所以现在更倾向于称其为隐性语义分析。与LSA相似的算法还包括:

  • 线性判别分析LSA
  • 隐性狄利克雷分布LDiA

LDiA与LSA都可以将文档分解到任意多个主题中,而LDA则只能将文档分解到单个主题。


LDA - 线性判别分析

LDA分类器是一种有监督算法,但所需训练样本数较少;其实现步骤如下:

(1)计算某个类中所有TF-IDF向量的平均位置,我们将其称为质心
(2)计算不在该类中的所有TF-IDF向量的质心
(3)计算上述两个质心之间的向量差

训练LDA模型的目的是找到两个类的质心之间的向量,使用LDA模型进行预测只需要判断新的TF-IDF向量是否更接近类内而不是类外。

LDA说明:

LDA本质上是一个降维过程,我们希望将任意维度的向量降维至一维,这样的过程最简单的实现方式就是与某个标准向量进行点积,而我们需要找到的就是这样一个标准向量;我们可以通过两组数据“训练”出这样一个标准向量,通过数学推导,我们发现这个标准向量的最优解是两组数据质心构成的向量;我们的数据与这样一个标准向量进行点积,得到的降维结果是最优的(两组之间的距离最大、组内数据不重合)。利用这个标准向量以及从“训练数据”中得到的降维后的质心,我们就可以使用LDA进行预测。

在这里插入图片描述

2020/12/8
项目实践:实现简化版LDA分类器:
  • 将两组三维向量投影到一维直线上并使用模型进行预测。

降维过程
为了方便可视化,我们随机生成具有明显类别的两组三维向量:在这里插入图片描述
上图中有两组三维向量,我们计算了这两组向量的质心,即图中的两个红色标记的向量点;利用这两个质心所构成的向量,我们就可以将三维数据与其进行点积,从而实现三维向量到一维向量的投影:
在这里插入图片描述
通过简单比较即可发现,即使两组数据有较大的重合边界,选择质心向量进行点积的结果要远远好于单纯的坐标轴投影。

2020/12/9
隐性语义分析LSA与其基础奇异值分解(SVD)

SVD是常用的降维技术,可以将一个矩阵分解为三个更简单的方阵,其中一个是对角矩阵,这个过程类似于矩阵的因数分解。

经过SVD分解后的三个矩阵相乘将得到原始矩阵,但我们需要对这三个矩阵进行一些截断处理,减少需要处理的维数,经过截断的矩阵相乘之后得到另一个矩阵,这个矩阵表示就包含了忽略噪声后的文档的本质——隐性语义,并且维数也大大降低了。

更具体的来说,LSA使用SVD查找导致数据中最大方差的词的维度,然后将TF-IDF向量旋转到这些方向,这时每个维度就变成了词频的组合,这些维度就是我们之前寻找的、构成主题的词的加权组合。同时,我们可以去掉对不同文档向量贡献很小的维度,这些维度通常是所有文档都大致相同的主题,这样的主题无法为区分文档提供帮助,所以我们可以删除这些噪声。这样的过程被称为截断的奇异值分解。需要注意的是,利用截断的奇异值分解进行降维,或者说利用LSA进行降维、去噪的效果要比通过停用词去除噪声对主题的干扰的效果要好得多。

总而言之,**LSA将更多的意义压缩到更少的维度,我们只需要保留高方差的维度,就可以通过这些维度代表的语义得到我们我们寻找的文档主题,也就是我们前面寻找的词条的加权组合。**谋者意义上,利用LSA降维的结果是最优的。

另外,LSA算法并不知道主题的实际含义,而是通过数据集中轴的方差寻找隐含的主题,并且会对这些主题对文档差异的贡献度进行排序。

SVD将相关度高(经常一起出现在同一篇文档中)的词项组合在一起,同时如果这些组合在一组文档中出现的差异很大,我们就认为这些组合就是主题,这种组合是词条的线性组合。

SVD是LSA的核心。

SVD所得三个矩阵的NLP解析
  • 数学表述:

W m × n = U m × p S p × p V p × n T W_{m×n}=U_{m×p}S_{p×p}V_{p×n}^T Wm×n=Um×pSp×pVp×nT

其中,m可以表示词汇表中的词条数量;n可以表示语料库中的文档数量;p可以表示语料库中的主题数量。


  • 左奇异向量 U U U

U U U是词项-主题矩阵,给出词所具有的上下文信息。

U U U基于词在同一文档中的共现关系给出词与主题之间的联系,在进行截断(删除某些列代表的主题)前, U U U是一个方阵

U U U是我们最重要的一个矩阵,因为 U U U矩阵每个位置的值就是每个词对每个主题的重要程度,在 U U U矩阵中,每一列主题向量对应语料库中的一个词(这也是 U U U矩阵为方阵的原因——其行与列都代表了词汇表中的词条)。这意味着我们将 U U U矩阵乘以一个TF-IDF向量,就能得到之前寻找的文档在某个主题模型下的得分。

U U U矩阵就是我们将词频映射到主题时所需要的因子矩阵。


  • 奇异值向量 S S S

S S S是一个对角方阵,其除了对角线之外所有元素都为0。这些对角线的元素被称为奇异值,而在我们的模型中,这些值的大小代表了在新的主题向量空间下,每个维度所代表的信息量。由于尚未进行截断降维,所以此时的主题数仍然等于词汇表中的词项数。


  • 右奇异向量 V T V^T VT

V T V^T VT的每一列都是一个右奇异向量,度量每篇文档在新的主题模型下对主题的使用频率


主题简约/截断

经过SVD的TF-IDF向量被转换为了主题权重向量,但并没有减少维数。但是SVD过程得到的 U U U矩阵的行和列的排列已经使得最重要的主题都被放在了右边,所以我们并不需要 S S S矩阵,而是可以将其作为一个信息的补充,衡量我们筛选的主题对不同文档之间差异性的影响。

项目实践:LSA/PCA的可视化:
  • 把握关键:LSA/PCA技术类似于LDA,但可以将向量在任意维度下的方差最大化。

  • 代码链接:《PCA/LSA可视化实例-Python实现》

  • 结果展示:在这里插入图片描述
    在这里插入图片描述
    经过PCA/LSA(主成分分析/隐性语义分析)算法的处理,第一章图中的三类数据集被成功的投影到了二维平面,并且这个二维平面的维度综合考虑了所有数据,给出了最能反映三组数据的两个特征维度。

上述过程并不涉及实际上的语义分析,因为我们的数据点只是为了可视化方便而给出的随机点,但这个例子绝对有助于我们理解LSA过程——至少降维这一点是十分明显的。我们需要时刻记住的是,主题就是能将几组数据进行最好区分的维度


skleran.PCA模型类提供的API

实例化:

sklearn.decomposition.PCA(n_components=None,\
						  copy=True, \
						  whiten=False)
  • n_components:PCA算法所要保留的特征/主题/维度个数;缺省时保留所有维度/特征/主题,可以使用:int进行制定,也可以使用’mle’自动选取所需的特征。
  • copy:缺省时为True,此时算法将保留原始数据,指定为Falas时,算法将在数据本地进行。
  • whiten:缺省时为Falsa,用于制定数据白化(使得每个特征方差相同)。

属性:

  • components_:返回具有最大方差的特征;
  • explained_variance_ratio_:返回保留的特征的方差百分比;
  • n_components_:返回保留的成分个数;

方法:

  • fit(train_data):训练;
  • fit_transfrom(data):训练并返回data降维后的结果;
  • inverse_transfrom():将降维后的数据转换成原始数据;
  • transfrom():训练好模型之后,对于新的输入数据,可以使用此方法进行降维;
项目实践:将PCA/LSA方法实际应用到NLP中:主题提取

项目描述:

  • 以十篇文档作为语料库;
  • 首先计算这些文档的TF-IDF矩阵;
  • 利用PCA对生成的TF-IDF 矩阵计算其原维度的主题向量;
  • 尝试在这些主题向量上寻找三到五个能反映文章主题的词条;

先在一个小的数据上测试是否能从PCA得到的主题向量中换原词条;如果不能则重构之前的文档相似度代码,如果可以就继续此项目的实践。总而言之,我们实现了从TF-IDF向量到主题向量的降维过程,这篇文章也已经突破两万字,剩余内容将被放在另一篇文章里进行。

经过初步测试,似乎使用LDiA可以进行关键词抓取的实现(本质上,TF-IDF也可以进行关键词抓取)。下一步的学习与实践将在下一篇文章中进行。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值