文本预处理的主要环节
文本处理基本方法
文本张量表示方法
文本语料的数据分析
文本特征处理
数据增强的方法
文本预处理方法
分词
词性标注
命名实体识别
文本张量的表示方法
one—hot 编码(不能计算不同单词之间的相似度)
Word2vd
Embedding
文本语料的数据分析
标签数量分布
句子长度分布
词频统计与关键词词云
文本特征处理
添加n—gram特征
文本长度规范
数据增强方法
回译数据增强法
文本预处理的方法
jieba分词
jieba.lcut()将生成器对象以列表的形式返回
在jieba.lcut(文本)的默认情况下,是精确模式分词
import jieba
content = “工信处女干事每月经过下属科室都要亲口交代24口交换机等技术型器件”
print(jieba.cut(content,cut_all=False))
print(jieba.lcut(content,cut_all=False))
#更多的详情见上方代码图片
流行的中英文分词工具hanlp
- 环境安装,Anaconda Prompt pip install hanlp
然后切换pytorch环境再安装一下(我用的是pytorch里的python编译器) - hanlp处理的都是列表形式,如果文本不是列表形式的话,用list给解析一下
- 本小节所使用的部分代码,由于篇幅较长,所以在图片选择的时候,没有选择全部 在敲代码的时候一定要看清括号的嵌套。
词性标注
文本张量的表示
在这里插入图片描述
one-hot编码实现
word2vec的实现
总的来说,CBOW就是用周围的词语来预测中间的词语,在整段语料上,选取窗口大小的语料,计算,并筛选最合适的,然后依次向后进行,直到完成所有语料的遍历。
skipgram则是使用中间的词语来预测前后词语的训练方式。
word2vec代码实现
以上数据加载是在Linux系统下的命令行实现的,接下来讲如何使用Python进行训练。
代码
import fasttext
model = fasttext.train_unsupervised('路径') #设置模型超参数
model.get_nearest_neighbors('某个词')# 查询最近的词向量
model.get_word_vector("the")#获取某个词向量
model.save_model("文件名.bin")#模型保存
model = fasttext.load_model("文件名.bin")#模型重加载
# 模型超参数的设定
model = fasttext.train_unsupervised('路径',"cbow", dim = 300, epoch = 1, lr = 0.1, thread = 8)#无监督训练的方式,dim词嵌入向量维度,epoch总体的数据集循环几轮,初始学习率lr,线程thread
# 模型效果检验
model.get_nearest_neighbors('cat')
#模型保存与重加载
model.save_model("文件名.bin")
model = fasttext.load_model("文件名.bin")
词嵌入
文本张量表示的小结
HMM模型与CRF模型
RNN模型
概念
实例
RNN模型的分类
N-N
N-1
1-N
N-M(seq2seq架构)
RNN小结
传统RNN模型
在代码中体现
import torch
import torch.nn as nn
# 实例化RNN对象
#第一个参数:input_size(输入张量x的维度)
#第二个参数:hidden_size(隐藏层的维度,隐藏层神经元的数量)
#第三个参数:num_layers(隐藏层的层数)
rnn = nn.RNN(input_size, hidden_size, num_layers)
#设定输入的张量x
# seq_len:输入序列的长度。batch_size:批次的样本数。input_size:输入张量x的维度
input = torch.randon(seq_len, batch_size, input_size)
#设定初始化的h0
#第一个参数:num_layers*num_directions(层数*网络方向数)
#batch_size:批次的样本数
# hidden_size:隐藏层的维度,隐藏层神经元的数量
h0 = torch.randn(num_layers, batch_size, hidden_size)
#输入张量放入RNN中,得到输出结果
output, hn = rnn(input1, h0)
print(output)
print(output.shape)
print(hn)
print(hn.shape)
LSTM模型
遗忘门部分结构图
两个输入: 当前时间步的输入xt 与 上一个时间步隐含状态h(t-1)拼接 得到[xt,h(t-1)]经过全连接层与sigmoid函数得到f
在RNN中,tanh的作用是将流经网络的值压缩为[-1,1]之间
而在LSTM中,sigmoid函数的作用是将流经网络中的值压缩在[0,1]之间
输入门部分结构图
将拼接后的值复制两份,然后一个进入sigmoid函数,一个进入tanh函数
将刚刚得到遗忘门门值与上一个时间步得到的C(t-1)相乘,再加上输入门门值与当前时间步得到的未更新C(t)相乘的结果
输出门部分结构图
计算输入门的门值,将拼接后的放入sigmoid函数里。再跟进入tanh函数的细胞状态做乘法,最后将二者进行乘积
Bi-LSTM
Bi-LSTM的第一个h0是将正向的LSTM的h0与反向LSTM最后一个hn拼接得到的
在Pytorch中使用LSTM
# 定义LSTM的参数含义: (input_size, hidden_size, num_layers)
# 定义输入张量的参数含义:(sequence_length, batch_size, input_size)
# 定义隐藏层初始张量和细胞初始状态张量的参数含义:
# (num_layers * num_directions, batch_size, hidden_size)
import torch.nn as nn
import torch
lstm = nn.LSTM(5, 6, 2)
input = torch.randn(1, 3, 5)
h0 = torch.randn(2, 3, 6)
c0 = torch.randn(2, 3, 6)
output, (hn, cn) = rnn(input, (h0, c0))
# 实例化LSTM对象
# 第一个参数:input_size(输入张量x的维度)
# 第二个参数:hidden_size(隐藏层的维度,隐藏层的神经元的数量)
# 第三个参数: num_layers(隐藏层的层数)
lstm = nn.LSTM(5,6,2)
# 初始化输入张量x
# 第一个参数:sequence_length(输入序列的长度)
# 第二个参数:batch_size(批次的样本数量)
# 第三个参数:input_size(输入张量x的维度)
input1 = torch.randn(1,3,5)
# 初始化隐藏层张量h0和细胞状态c0
# 第一个参数: num_layers * num_directions(隐藏层的层数*方向数)
# 第二个参数: batch_size(批次的样本数量)
# 第三个参数: hidden_size(隐藏层的维度,隐藏层的神经元的数量)
h0 = torch.randn(2, 3, 6)
c0 = torch.randn(2, 3, 6)
# 将input1, h0, c0,输入lstm中,得到输出张量结果
output, (hn, cn) = rnn(input, (h0, c0))
print(output)
print(output.shape)
print(hn)
print(hn.shape)
print(cn)
print(cn.shape)
LSTM优劣对比
GRU模型
总体来说,两个门四个计算公式。两个输入分别是上一个时间步的隐藏层的输出h(t-1)与当前时间步的输入xt,做一个拼接然后复制两份,一份进入sigmoid函数作为更新门zt,另一部分也进入sigmoid函数作为重置门rt
GRU代码
#跟前面不能说一模一样,只能说一模一样
import torch
import torch.nn as nn
rnn = nn.GRU(5, 6, 2)
input = torch.randn(1,3,5)
h0 = torch.randn(2,3,6)
output,hn = rnn(input,h0)
# 实例化GRU对象
# 第一个参数:input_size(输入张量x的维度)
# 第二个参数:hidden_size(隐藏层的维度,隐藏层的神经元的数量)
# 第三个参数: num_layers(隐藏层的层数)
gru = nn.GRU(5,6,2)
# 初始化输入张量x
# 第一个参数:sequence_length(输入序列的长度)
# 第二个参数:batch_size(批次的样本数量)
# 第三个参数:input_size(输入张量x的维度)
input1 = torch.randn(1,3,5)
# 初始化隐藏层张量h0
# 第一个参数: num_layers * num_directions(隐藏层的层数*方向数)
# 第二个参数: batch_size(批次的样本数量)
# 第三个参数: hidden_size(隐藏层的维度,隐藏层的神经元的数量)
h0 = torch.randn(2, 3, 6)
# 将input1, h0,输入GRU中,得到输出张量结果
output, hn = gru(input1, h0)
print(output)
print(output.shape)
print(hn)
print(hn.shape)
注意力机制
注意力机制概念
注意力机制的代码实现
import torch
import torch.nn as nn
import torch.nn.functional as F
class Attn(nn.Module):
def __init__(self, query_size, key_size, value_size1, value_size2, output_size):
# query_size代表的是Q的最后一个维度,key_size代表的是K的最后一个维度
# V的尺寸表示(1,value_size1,value_size2)
# output_size代表输出的最后一个维度的大小
super(Attn, self).__init__()
self.query_size = query_size
self.ket_size = key_size
self.value_size1 = value_size1
self.value_size2 = value_size2
self,output_size = output_size
# 初始化注意力机制实现中的第一步的线性层
self.attn = nn.Linear(self.query_size + self.key_size, self.value_size1)
# 初始化注意力机制实现中第三步的线性层
self.attn_combine = nn.Linear(self.query_size + self.value_size2, self.output_size)
def forward(self, Q, K, V):
# 注意我们假定Q,K,V都是三维张量
# 第一步,将Q,K进行纵轴的拼接,然后做一次线性变换,最后使用softmax进行处理得到注意力向量
attn_weights = F.softmax(self.attn(torch.cat((Q[0],K[0]),1)),dim = 1)
#将注意力矩阵和V进行一次bmm运算
attn_applied = torch.bmm(attn_weights.unsqueeze(0), V)
#再次去Q[0]进行降维,再次和上面的运算结果进行一次拼接
output = torch.cat((Q[0], attn_applied[0]),1)
#第三步就是将上面的输出进行一次线性变换,然后再扩展维度成3维张量
output = self.attn_combine(output).unsqueeze(0)
return output, attn_weights
query_size = 32
key_size = 32
value_size1 = 32
value_size2 = 64
output_size = 64
attn = Attn(query_size, value_size1, value_size2, output_size)
Q = torch.randn(1,1,32)
K = torch.randn(1,1,32)
V = torch.randn(1,32,64)
output = attn(Q, K, V)
print(output[0])
print(output[0].size())
print(output[1])
print(output[1].size())
注意力机制小结
Transformer的优势
总体架构图
== 左边为源文本,右边Outputs为目标文本。==
输入部分的实现(包括代码)
Embedding层
import torch
import torch.nn as nn
import math
from torch.autograd import Variable
'''
# 定义Embeddings类来实现文本嵌入层,这里s说明代表两个一模一样的嵌入层,他们共享参数,
# 该类继承nn.Moudule,这样就有标准层的一些功能,这里我们也可以理解为一种模式,我们自己实现所有层
class Embeddings(nn.Module):
def __init__(self, d_model, vocal):
# 类的初始化函数,有两个参数,d_model:指词嵌入的维度,vocab:指词表的大小
#接着就是用super()方式来指明继承nn.Moudle的初始化函数,我们自己实现所有的层都会这样去定义
super(Embeddings,self).__init__()
#之后就是调用nn中的预定义层Embedding,获得一个词嵌入对象self.lut
self.lut = nn.Embedding(vocab, d_model)
# 最后就是将d_model传入类中
self.d_model = d_model
def forward(self, x):
# 可以将其理解为该层的前向传播逻辑,所有层中都会有此函数
# 当传给该类的实例化对象参数时,自动调用该类函数
# 参数x:因为Embedding层是首层,所以代表输入给的墨香的文本通过词汇映射后的张量
# 将x传给self.lut并与根号下self.d_model相乘作为结果返回
return self.lut(x) * math.sqrt(self.d_model)
'''
# 构建Embedding类来实现文本嵌入层
class Embeddings(nn.Module):
def __init__(self, d_model, vocab):
# d_model:词嵌入的维度
# vocab:词表的大小
super(Embeddings, self).__init__()
# 定义Embedding层
self.lut = nn.Embedding(vocab, d_model)
# 参数传入类中
self.d_model = d_model
def forward(self, x):
# x:代表输入进模型文本通过词汇映射后的数字张量
return self.lut(x) * math.sqrt(self.d_model)
d_model = 512
vocab = 1000
x = Variable(torch.LongTensor([[100, 2, 421, 508],[491, 998, 1, 221]]))
emb = Embeddings(d_model, vocab)
embr = emb(x)
print("embr:", embr)
print(embr.shape)
位置编码器
# 定义位置编码器,我们同样把它看作一个层,因此会继承nn.Moudle
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout, max_len = 5000):
'''位置编码器类的初始化函数,共有三个参数,分别是
d_model:词嵌入维度,
dropout:置0比率,
max_len:每个句子的最大长度'''
super(PositionalEncoding, self).__init__()
# 实例化nn中预定义的Dropout层,并将dropout传入其中,获得对象self.dropout
self.dropout = nn.Dropout(p = dropout)
# 初始化一个位置编码矩阵,他是一个0阵,矩阵的大小是max_len * d_model
pe = torch.zeros(max_len, d_model)
# 初始化一个绝对位置矩阵,在我们这里,词汇的绝对位置就是用它的索引去表示
# 所以我们首先使用arange方法获得一个连续自然数向量,然后再使用unsqueeze方法拓展向量维度
# 又因为参数传的是1,代表矩阵拓展的位置,会使向量变成一个max_len * 1的矩阵
position = torch.arange(0,max_len).unsqueeze(1)
# 绝对位置矩阵初始化之后,接下来就是考虑如何将这些位置信息加入到位置编码矩阵中,
# 最简单的思路就是先将max_len x 1的绝对位置矩阵,变换成max_len x d_model形状,然后覆盖原来的初始位置编码矩阵即可,
# 要做这种矩阵变换,就需要一个1xd_model形状的变换矩阵div_term,我们对这个变换矩阵的要求除了形状外,
# 还希望它能够将自然数的绝对位置编码缩放成足够小的数字,有助于在之后的梯度下降过程中更快的收敛,这样我们就可以开始初始
# 首先使用arange获得一个自然数矩阵,但是会发现,我们这里并没有按照预计的一样初始化一个1 * d_model的矩阵,
# 而是有了一个跳跃,只初始化了一半即1xd_model/2的矩阵。为什么是一半呢,其实这里并不是真正意义上的初始化了一半的矩阵,
# 我们可以把它看作是初始化了两次,而每次初始化的变换矩阵会做不同的处理,第一次初始化的变换矩阵分布在正弦波上,第二次初始化
# 并把这两个矩阵分别填充在位置编码矩阵的偶数和奇数位置上,组成最终的位置编码矩阵
div_term = torch.exp(torch.arange(0, d_model, 2) * (math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
# 这样我们就得到了位置编码矩阵pe,pe现在还只是一个二维矩阵,要想和embedding的输出(一个#就必须拓展一个维度,所以这里使用unsqueeze拓展维度.
pe = pe.unsqueeze(0)
#最后把pe位置编码矩阵注册成模型的buffer,什么是buffer呢,
#我们把它认为是对模型效果有帮助的,但是却不是模型结构中超参数或者参邀,不需要随着优化步骤
#注册之后我们就可以在模型保存后重加载时和模型结构与参数一同被加载.
self.register_buffer('pe', pe)
def forward(self, ×):
"“forward函数的参数是x,表示文本序列的词嵌入表示"""
#在相加之前我们对pe做一些适配工作,将这个三维张量的第二维也就是句子最大长度的那一维将切
#因为我们默认max_len为5000一般来讲实在太大了,很难有一条句子包含5000个词汇,所以要进行
#最后使用Variable进行封装,使其与x的样式相同,但是它是不需要进行梯度求解的,因此把request
X = × + Variable(self.pe[:,:x.size(1)],requires_grad=False)
#最后使用self.dropout对象进行'丢弃'操作,并返回结果.
return self.dropout(×)
nn.Dropout演示
m = nn.Dropout(p = 0.2)
input1 = torch.randn(4, 5)
output = m(input1)
# print(output)
x = torch.tensor([1, 2, 3, 4])
y = torch.unsqueeze(x, 0)
print(y)
z = torch.unsqueeze(x, 1)
print(z)
torch.unsqueeze演示
x = torch.tensor([1, 2, 3, 4])
y = torch.unsqueeze(x, 0)
print(y)
#输出结果为 tensor([[ 1, 2, 3, 4]]),这是x在第0个维度上扩展
z = torch.unsqueeze(x, 1)
print(z)
#输出结果为tensor([[ 1],
# [2],
# [3],
# [4]])这是x在列的维度上扩展
构建位置编码器代码
构建位置编码器的类
class PositionalEncoding(nn.Moudle):
def __init__(self, d_model, dropout, max_len = 5000):
# d_model: 代表词嵌入的维度
# dropout: 代表Dropout层的置零比率
# max_len: 代表句子的最大长度
super(PositionalEncoding,self).__init__()
# 实例化Dropout层
self.dropout = nn.Dropout(p = dropout)
# 初始化一个位置编码矩阵,大小是max_len * d_model
pe = torch.zeros(max_len, d_model)
# 初始化一个位置编码矩阵, 大小是max_len * d_model
pe = torch.zeros(max_len, d_model)
#初始化一个绝对位置矩阵
position = torch.arange(0,max_len).unsqueeze(1)
#定义一个变换矩阵div_term,跳跃式的初始化
div_term = torch.exp(torch.arange(0, d_model,2) * -(math.log(10000.0) / d_model))
#将前面定义的变换矩阵进行奇数,偶数的分别赋值
pe[:,0::2] = torch.sin(position * div_term)
pe[:,1::2] = torch.cos(position * div_term)
# 将二维张量扩充成三维张量
pe = pe.unsqueeze(0)
# 将位置编码矩阵注册成模型的buffer,这个buffer不是模型中的参数,不跟随优化器同步更新
# 注册成buffer后我们就可以在模型保存后重新加载的时候,将这个位置编码器和模型参数一同加载进来
self.register_buffer('pe',pe)
def forward(self, x):
# x:代表文本序列的词嵌入表示
# 首先明确pe的编码太长了,将第二个维度,也就是max_len对应的那个维度缩小成x的句子长度同等的长度
x = x + Variable(self.pe[:,:x.size(1)], requires_grad=False)
return self.dropout(x)
d_model = 512
dropout = 0.1
max_len = 60
x = embr
pe = PositionalEncoding(d_model, dropout, max_len)
pe_result = pe(x)
print(pe_result)
print(pe_result.shape)