输入是什么(现在有什么) 输出是什么(想要什么) 要解决什么问题(要怎么做呢)
输入:m个向量,一个向量组A,由一组向量构成一个序列,m>1 输出:n个向量,另外一个向量组B,n>1 且输出的向量之间是有顺序的 B中的每个bi向量,都是 A中所有向量 + B中bi之前的向量 综合计算的结果 比如树枝的生长,每成长一步,就是建立在之前成长的基础上的 比如事物的运动,每状态改变,就是建立在之前状态的基础上的 当然了, 无尽的变化在不同的范围内有固定的规律, seq2seq的任务就是提取这些变化的共性, 不同的状态,相似的轮回... 通常情况下说的 seq2seq, 向量组A 之间的向量有依赖关系 向量组B 之间的向量有依赖关系 这个依赖关系,在代入程序中计算时, 通常转化为前后依赖关系,转换为一一映射, 实际上,依赖关系不止前后依赖这一种(关系通常是个网), 但只要 在时间上 有前后依赖关系,那么就属于序列问题
|
单纯使用RNN的seq2seq处理的序列长度大约在20
加上注意力后,能处理更长的序列
|
序列到序列是生成的,根据已有序列的规律,生成一个数据... 然后循环这个过程,直到结束 |
|
|
|
|
|
应用场景 降维:用少量特征来 代表 原来的多特征 翻译:一段文本转另外一段文本 语音转文本, 序列信号, 时序数据
|
|
前后依赖关系 序列 为 前后存在依赖关系的数据 当然了,人看数据都是线性的, 不管原来是个什么结构,往数组里一扔,就开始各种处理 有些数据虽然在数组中对应着索引的位置,但实际上没有前后依赖关系 是否有前后依赖关系,计算机并不知道,由业务含义决定 全连接没有前后依赖关系,它只是提取特征 这是由全连接的计算方式决定的, 全连接是 所有参数与数据相乘再相加, 不管原来特征什么顺序,最后一相加,结果都一样
|
|
卷积计算没有前后依赖关系 全连接计算没有依赖关系,那卷积呢? 对于单通道,一个kernel矩阵在特征shape上进行局部全连接计算, 多通道时,各个通道与各自的kernel参数矩阵分别计算后, 然后相加,即先将多个通道的信息融合到一起,再多维度分层 (卷积后通常紧跟一个批归一化,不然通道数太多,相加后就大于1了) 这个过程是有顺序的,即原特征图的左上角对应新特征shape的左上角 但这个顺序没有前后依赖关系,是可以并行计算的, 注意,这里说的是卷积计算没有前后依赖关系, 并不是说卷积计算的结果之间没有顺序 卷积不像RNN那样,当前单元依赖于上一个单元的输出 虽然一次卷积计算涉及了所有通道上的所有特征图, 但损失函数会逼迫大部分参数归于0 最后只留下特定区域的参数 卷积开始先将通道数提升,最后再抛弃或者说将大部分通道上的参数归于0 这是个暴力计算的过程,提前根本不知道,也不管 那些通道有用 换个方式再描述下: 一张白纸滴上一点墨水,大片白色中有个区域是黑色的, 卷积就可以把墨水和白色部分 分开,图像分割就是做这个的, 主体就是分层,一个特征图分散成N个特征图, 通过一层又一层计算以及损失函数的逼迫, 黑色到一张特征图,白色到另外一个特征图上了
总体趋势
- 特征图在收缩
- 通道维数在增加
- 即将特征从形状上转化到特征的维度上
------------------------------------------------------------------------------------------ |
|
区域特征倒底是什么特征? 一张照片一朵花, 花在图像上左上,右下,中间都是花的图片, 它不会因为位置不同而变成动物图片, 它跟顺序关系不大, 就算花朵旁边还出现一个小草, 不会因为小草在花的左边还是右边而产生严重的业务差异, 这个图像只有因为某个区域是花,就叫花的图片 这就是区域特征 序列特征是什么特征? 就是调整一下顺序会得到不同的业务含义 我欠银行一百万,银行欠我一百万,这两句话的业务含义相差可太远了 区域特征中交换两个 主体 位置 业务含义几乎是不变的 有些问题可能界限并不明显, 比如情感识别,只要话语中出现负面语汇,不管是在开头,中间,还是结尾, 它都是负面情绪了,用卷积是可以做的 当然了,文本分类通常是序列问题... 这也说明,序列类算法未必就不能处理区域问题 后来出现的注意力机制,即可以提取区域特征,又可以提取序列特征,就说明了这一点 |
|
|
|
总体流程 先建立一组组/一对对映射关系,让计算机去学习 然后问上半对,计算机回答你 “曾经学习过的” 下半对 你无法描述你认知之外的事物,计算机也无法回答它没学过的知识 都到这了,那什么是知识?用数学怎么描述? 知识 就是 一对对映射关系 煤是黑的 墙是白的 水可以喝 米饭能吃 不与外界其他事物建立映射关系的事物,不可被描述,不可被认知 描述一个事物,总是用其他事物来描述,总是在找他们的共性/联系 序列到序列,就是知识的一种描述方式,是一对对映射关系...
|
|
字到字 芝麻开花,节节高 start芝麻开花,节节高end start -->芝 芝-->麻 麻-->开 开-->花 花-->, ,-->节 节-->节 节-->高 高-->end 词到词 芝麻开花,节节高 start芝麻开花,节节高end start-->芝麻 芝麻-->开花 开花-->, ,-->节节高 节节高-->end 多个单词到词,同时抹去标点符号 芝麻开花,节节高 start芝麻开花,节节高end start-->芝麻开花 芝麻开花-->节节高 节节高-->end
|
|
映射关系的叠加 文本向量化后,字或词 变成一个个向量, 词到词的映射转化为 向量到向量之间的映射 现阶段,AI中能用到的向量之间的关系,简单且粗糙, 就是个距离远近的关系, 两个的向量之间的远近用“一个数值”来表示 用 距离 表示 相似/相近程度 语言,描述的事物包罗万象, 结果组成它们之们的单词的关系,只是 “单个数字”就表示了 但实际上已经可以解决很多粗糙的问题了, 当然了,过于精细的还不行 话题收回来, 我们想要序列中相近的单词,转化为向量后,其距离也能近一些 怎么做? 简单粗糙的做法,就是前面的做法, 两个单词靠近,直接建立一个映射关系就行了 多层面/多角度收集信息 感觉这样不够,可以把周围的单词也考虑进去, 比如, 以开花为 中心词 芝麻-->节节高 也可以是一个映射关系 还不够? 把词性也用上,主谓宾丁壮补 就是序列中的隐藏序列 语法结构的信息,也融入 还不够?那就再细化 同义词,用上 成语/专有名词,单独训练一下 再不够?不仅佃化,还要深入 特殊场景 加权处理 融入规则 仍不够?精准化 规则匹配,建立特殊单词到特定单词的规则 哪怕这个单词从开始到结尾总共出现了一次,但你加了规则, 一经出现,必定触发特定操作 七分规则 ,三分概率,这系统即智能又稳定 --------------------------------------------------------------------------------- |
|
|
|
|
|
常规做法 预测, 就是将一个序列的向量A输入模型,模型输出一个新向量B 新向量B,不会与任何已有单词向量 完全相等 这里还需要计算 新向量B 与 所有已有向量之间的距离, 与之最近的那个向量 定为 模型的输出 没错, 模型直接的输出结果并不是我们期望的标签,这里需要一步转化操作 不仅如此, 按神经网络的常规操作, 不用计算就知道 模型输出 离哪个单词向量最近
单词索引编码后,化为一个个从0开始的整数,
设定不重复的单词个数为n,
单词的索引编号,
恰恰是 长度为n的one hot向量V 中唯一的1所在的索引编号
将单词进行one hot编码,模型的输出直接就是单词集合的长度
这样就免去计算就知道是哪个单词了
但实际上没人这么做,因为单词个数太多了,计算机算不动
|
|
|
|
|
|
|
|
|
输入是一个序列,输出是另外一个序列 NLP有关序列的问题:主要有 编码器,解码器 两大模块 编码器是一个序列,解码器是一个类别,典型的文本分类问题 编码器是一个序列,解码器是一个序列,典型的seq to seq问题 这二类问题,解码器中的每个部分,受 编码器所有部分影响 即编码器计算后得到一个整体的特征,供解码器使用 这就是一个序列到另外一个序列 编码器,解码器是从 编程/代码实现 的角度看 序列到序列 编码器对应前一个序列 解码器对应后一个序列
|
|
编码器-模板类参数 数据X[sql_len,batch_size,hidden_size] 一个序列中所有的单词一次性全输入进去了 因此,可以使用双向这个参数,正一条RNN链,反再来一条 层数为2,可以更好地提取特征 编码器-对象参数: 数据X,mask标记(记录哪个地方是PAD) 解码器-模板类参数
数据X[1,batch_size,hidden_size]
一次只输入一个单词,这个单词最终要映射到字典的维度,就是标签的维度
不管中间增加多少技巧,解码器的终极目的,就是 输入单词 映射到 字典的某个单词上
输入单词 -- 字典中某个单词
因此,输出的维度必不可少,且要清楚要与标签维度对应
是否双向,
由于解码器是根据当前单词预测下一个单词,
与编码器一次可逐步计算一个序列中所有单词是不同的,
因此,解码器只有单向,一个方向
层数,
层数为2比1能更好地提取特征,但编码器可100%确定一个序列中所有的单词都是正确的,
而解码器的单词是预测出来的,某个单词预测错了,再深度提取特征也没用,
因此解码器的层数为1
解码器-对象参数:
单个单词,上个单元的输出(隐藏层)
该单词相对整个编码序列的上下文对象
|
为了批次计算,深度学习都是批次,对数据进行补0对齐 如果是短句,那么大部分是0,好处是可以批次计算了,缺点如下 - 计算量大了,因为多了很多不属于原来的数据 - 增加了噪声,原来的数据中根本就没有“补充”的数据,AI学了之后,反之形成不好的影响 为什么要批次计算,单个样本不行吗? - 行,但损失会剧烈波动,效率也没有批次计算高 - 现在的损失都是批次中所有样本损失的均值,平均一下,看起来平滑多了 - 两者本质上没啥区别,单样本也是行的,但看着舒服一些,也不影响效果也是可以的吧?! - 当然可以 于是就有了下面的方法,即能批次运算,模型学习时也无噪声 编码器模型
class EncoderRNN(nn.Module):
"""编码模型
- 从批次数据中pack_pad出真实数据
- 将真实数据输入模型
- 将模型输出进行pad_pack得到批次数据
输入数据
- 要求输入的数据格式为[seq_len,batch_size]
- 通常为单词索引列表
"""
def __init__(self, hidden_size, embedding, n_layers=1, dropout=0):
super(EncoderRNN, self).__init__()
self.n_layers = n_layers
self.hidden_size = hidden_size
self.embedding = embedding
# Initialize GRU; the input_size and hidden_size params are both set to 'hidden_size'
# because our input size is a word embedding with number of features == hidden_size
self.gru = nn.GRU(hidden_size, hidden_size, n_layers,
dropout=(0 if n_layers == 1 else dropout), bidirectional=True)
def forward(self, input_seq, input_lengths, hidden=None):
# Convert word indexes to embeddings
embedded = self.embedding(input_seq)
# Pack padded batch of sequences for RNN module
packed = nn.utils.rnn.pack_padded_sequence(embedded, input_lengths)
# Forward pass through GRU
outputs, hidden = self.gru(packed, hidden)
# Unpack padding
outputs, _ = nn.utils.rnn.pad_packed_sequence(outputs)
# Sum bidirectional GRU outputs
outputs = outputs[:, :, :self.hidden_size] + outputs[:, :, self.hidden_size:]
# Return output and final hidden state
return outputs, hidden
总体上来说,就是一个GRU,很简洁 - GRU接受一个句子,输出out,hn - out包含序列上每一步的输出,有整个句子的信息,是个序列 - 即一个GRU就是一个编码器 同时增加了两个处理技巧 - GRU模型计算的数据是无补的, - 之前数据加工时,记录了每个句子的长度, - 在入模之前根据这个长度,取出了真实的数据,没有补的数据 - 处理之后,又恢复补0对齐,恢复了原来的形状 处理之前,有单词的位置有信息,无单词的位置补的0 处理之后,有单词的位置有信息,无单词的位置还是0 seq单词的位置是一对一的 双向GRU,官方说法是在起点也包含了未来的信息 GRU算法如果是双链,处理方法是拼接,前半部分为正向,后半部分为反向 在seq2seq中为了保持形状的一致,将二者进行了相加 |
|
注意力机制来由
seq2seq中编码器传给解码器的hn,隐藏状态,状态不是很好
解决办法是,求解码器中每个单词,就是每个单词+该单词相对编码器重要的信息
- 对于解码器中不同的单词,它们相对于编码器的重要程度是不一样的
- 反过来说也是如此,编码器中的信息相对于解码器中不同的单词有不同的侧重点
怎么做?
注意力权重的使用
解码器的一个单词D[1,hidden_size]
编码器信息E[seq_len,batch_size,hidden_size]
D[1,hidden_size] -- D[seq_len,hidden_size] 复制seq_len份,是编码器的单词个数
E[seq_len,batch_size,hidden_size] -- E[batch_size,seq_len,hidden_size]
D[seq_len,hidden_size] -- D[1,seq_len,hidden_size]
torch.sum(D * E, dim=2)
- D[1,seq_len,hidden_size] * E[batch_size,seq_len,hidden_size]
- 注意,这是按位相乘再相加
- 得到 A[batch_size,seq_len],这一步就是注意力,也叫注意力权重
它是的shape是[batch_size,seq_len],即批次中每个单词相对另外一个句子都有一个概率值
- 另外一个句子中的每个单词与该单词都有一个重要性数值,即概率值
- 其和为1
- 第2维的个数为另外一个句子的单词个数
- 第1维是批次数
注意力权重并不是最终的目的,目的是找到解码器单词相对编码器信息的关键信息
所以还要使用注意力权重乘到编码器信息上,这次使用的是矩阵相乘
A[batch_size,seq_len] -- A[batch_size,1,seq_len]
A[batch_size,1,seq_len]@E[batch_size,seq_len,hidden_size] = C[batch_size,1,hidden_size]
C[batch_size,1,hidden_size] -- C[batch_size,hidden_size]
最终上下文向量的维度就是hidden_size
代码
# Luong attention layer
class Attn(nn.Module):
def __init__(self, method, hidden_size):
"""
描述
- 计算一句话中各个单词对输入单词的重要程度;
输入参数
- hidden:输出单词
- hidden的shape皆为[1, batch_size, hidden_size]
- encoder_outputs.shape=[seq_len, batch_size, hidden_size]
返回结果
- 一个单词hidden与encoder_outputs seq_len个单词的得分(做了softmax总和为1)
- 返回结果shape=[batch_size,1,seq_len]
- sum(dim=2)=1,编码一句话中各个单词对输入单词的重要程度
"""
super(Attn, self).__init__()
self.method = method
if self.method not in ['dot', 'general', 'concat']:
raise ValueError(self.method, "is not an appropriate attention method.")
self.hidden_size = hidden_size
if self.method == 'general':
# 矩阵相乘就是一系列向量内积
self.attn = nn.Linear(self.hidden_size, hidden_size)
elif self.method == 'concat':
self.attn = nn.Linear(self.hidden_size * 2, hidden_size)
self.v = nn.Parameter(torch.FloatTensor(hidden_size))
def dot_score(self, hidden, encoder_output):
# hidden.shape=[1,64,500],encoder_output.shape=[10,64,500]
# 1个单词,64个批次,500为hidden的大小,这是按批次 对一个单词 转换后的矩阵
# MAX_LENGTH = 10,句子最大长度为10,最后一个字符为EOS
# 单词维度 位乘(按位置相乘),再相加,sum之后单词这个维度消失
# [sel_len,batch_size]
# [1,64,500][10,64,500]=> [10,64,500][10,64,500]=>[10,64]=[sel_len,batch_size]
# 位乘再相加,实际就是向量点乘,通常的点乘指两个向量之间的位乘再相加
# 一个单词长度为hidden_size的向量,这样的单词有seq_len*batch_size个
# 将它们按[sel_len,batch_size,hidden_size]的方式存放
# 真实计算的时候,仍然是两个单词(hidden_size维度)之间的点乘
# 每两个单词之间向量点乘之后,得到一个数字,这个数字近似代表了两个单词之间的相似程度
# 这个单词与[sel_len,batch_size]个单词进行了点乘,就得到了[sel_len,batch_size]个结果
# 这就是序列到序列,注意力提取特征的关键
# 矩阵乘法是向量内积按一定格式/规律计算的过程,前一个矩阵的首与后一个矩阵的尾维度相等,首尾同
# 而向量内积的使用,除了矩阵乘法的计算方法外,
# 还可以按矩阵shape一致的方式计算,首与首同,尾与尾同,即同shape计算
# 这里不影响,这里计算的是一个单词相对一个句子的注意力
return torch.sum(hidden * encoder_output, dim=2)
def general_score(self, hidden, encoder_output):
# 相当于用ht向量与编码层的每个向量进行向量内积运算
# 得到编码层每个单词输出的得分,或叫做百分比
energy = self.attn(encoder_output)
# 编码层的输出与得分点乘再相加
return torch.sum(hidden * energy, dim=2)
def concat_score(self, hidden, encoder_output):
energy = self.attn(torch.cat((hidden.expand(encoder_output.size(0), -1, -1), encoder_output), 2)).tanh()
return torch.sum(self.v * energy, dim=2)
def forward(self, hidden, encoder_outputs):
"""
描述
- 计算一句话中各个单词对输入单词的重要程度;
输入参数
- hidden:输出单词
- hidden的shape皆为[1, batch_size, hidden_size]
- encoder_outputs.shape=[seq_len, batch_size, hidden_size]
返回结果
- 一个单词hidden与encoder_outputs seq_len个单词的得分(做了softmax总和为1)
- 返回结果shape=[batch_size,1,seq_len]
- sum(dim=2)=1,编码一句话中各个单词对输入单词的重要程度
"""
# Calculate the attention weights (energies) based on the given method
if self.method == 'general':
attn_energies = self.general_score(hidden, encoder_outputs)
elif self.method == 'concat':
attn_energies = self.concat_score(hidden, encoder_outputs)
elif self.method == 'dot':
attn_energies = self.dot_score(hidden, encoder_outputs)
# Transpose max_length and batch_size dimensions
# [sel_len,batch_size] -- [batch_size,sel_len]
attn_energies = attn_energies.t()
# 按seq_len维度转为概率,[batch_size,seq_len]
# [batch_size,seq_len] -- [batch_size, 1, seq_len] 添加这个1是为了后面进行bmm
# 因为计算的是一个单词相对一个句子的注意力,这个1也可以理解为1个单词,即这个维度的业务含义是seq_len
# [batch_size, 1, seq_len]中的seq_len的业务含义则是重要百分比,一个单词相对句子的重要百分比
return F.softmax(attn_energies, dim=1).unsqueeze(1)
|
|
单个单词gru映射 拼接 注意力 decoder是一个单词一个单词计算的,对于每个单词来说 - 使用gru将单词的维度映射到hidden_size,记为out,hn,gru不限制序列长度,一个单词也能算 - 使用out计算编码序列整个句子的注意力,即一个单词相对一个句子的注意力, - 得到的上下文与out拼接成一个2倍hidden_size的向量,tanh激活函数处理将值域转换到[-1,1]之间 - 再使用线性变换将维度从2*hidden_size变换到hidden_size - 最后一层全连接是分类,将数据的维度从hidden_size转换到字典的维度
class LuongAttnDecoderRNN(nn.Module):
def __init__(self, attn_model, embedding, hidden_size, output_size, n_layers=1, dropout=0.1):
"""
输入参数
- 一个批次某个位置上的单词,单词维度是索引,格式为[1,batch_size]
- 隐藏层,编码器最后一次的输出,或者上一个解码器的输出
- 解码层
输出参数
- 单词向量
- 被预测的单词,经过了注意力计算,shape为[batch_size,dict_len]
- 这里用[dict_len]长度的向量表示一个单词,并且使用softmax转概率,其和为1
- 如此,就可以使用交叉熵逼迫其与one-hot意义的单词标签接近
- gru输出的隐藏层
批次计算每个单词的概率,最终将单词维度映射到字典,确定它是哪一个单词,然后解码输出一个单词
"""
super(LuongAttnDecoderRNN, self).__init__()
# Keep for reference
self.attn_model = attn_model # dot
self.hidden_size = hidden_size # 500
self.output_size = output_size # voc.num_words 7826
self.n_layers = n_layers # 2
self.dropout = dropout
# Define layers
self.embedding = embedding
self.embedding_dropout = nn.Dropout(dropout)
self.gru = nn.GRU(hidden_size, hidden_size, n_layers, dropout=(0 if n_layers == 1 else dropout))
self.concat = nn.Linear(hidden_size * 2, hidden_size)
self.out = nn.Linear(hidden_size, output_size) # 500 --> voc.num_words
self.attn = Attn(attn_model, hidden_size)
def forward(self, input_step, last_hidden, encoder_outputs):
"""根据前一个单词预测后一个单词
Args:
input_step (_type_): 某个位置一个批次的单词索引,shape=[1, 64]
last_hidden (_type_): 隐藏层,shape=[2, 64, 500]
encoder_outputs (_type_): [10,64,500]
Returns:
output: 单词向量,[64, 7826],转了概率,方便后续与标签求损失
hidden: 隐藏层,中间结果,[2, 64, 500]
"""
# Note: we run this one step (word) at a time
# Get embedding of current input word
embedded = self.embedding(input_step) # [1, 64, 500]
embedded = self.embedding_dropout(embedded)
# Forward through unidirectional GRU
# 每次只输入一个单词,
# 因此rnn_output, hidden的shape皆为[1, batch_size, hidden_size]
rnn_output, hidden = self.gru(embedded, last_hidden)
# Calculate attention weights from the current GRU output
# [batch_size,seq_len] -- [batch_size, 1, seq_len]
# 1个单词与一个句子求注意力
attn_weights = self.attn(rnn_output, encoder_outputs)
# Multiply attention weights to encoder outputs to get new "weighted sum" context vector
# encoder_outputs.shape=[seq_len,batch_size,hidden_size]
# encoder_outputs.transpose(0, 1).shape = [batch_size,seq_len,hidden_size]
# attn_weights.shape = [batch_size, 1, seq_len]
# bmm [1, seq_len]@[seq_len,hidden_size] = [batch_size,1,hidden_size]
# context.shape = [batch_size,1,hidden_size]
# decoder中每个单词对应一个encoder的上下文向量
# 加权平均,[1, seq_len] sum(dim=1)=1,注意力的得分之和为1,将这个得分乘到原来解码器上
context = attn_weights.bmm(encoder_outputs.transpose(0, 1))
# Concatenate weighted context vector and GRU output using Luong eq. 5
# rnn_output.shape = [1, batch_size, hidden_size]
# rnn_output.squeeze(0).shape = [batch_size, hidden_size]
# decoder中每个单词的输出
rnn_output = rnn_output.squeeze(0)
# context.shape = [batch_size,hidden_size]
# decoder中每个单词对应encoder中的上下文向量
context = context.squeeze(1)
# 将从encoder中得到的上下文向量拼接到decoder中每个单词的输出维度上
# concat_input.shape=[batch_size,hidden_size*2]
concat_input = torch.cat((rnn_output, context), dim=1)
# 全连接,线性变换,再tanh,[batch_size,hidden_size*2]-- [batch_size,hidden_size]
concat_output = torch.tanh(self.concat(concat_input))
# Predict next word using Luong eq. 6
# [batch_size,hidden_size] -- [batch_size,output_size]
# output_size为标签的个数
output = self.out(concat_output)
# 这里转了概率,单词维度之和为1
output = F.softmax(output, dim=1) # 转概率,不改变维度,[64, 7826]
# Return output and final hidden state
# hidden:普通的gru,一个单词通过gru得到的输出,[2, 64, 500]
# output:经过gru+attention,然后转标签概率
return output, hidden
|
|
编码器与解码器的训练 序列是成对出现的(编码器序列1,解码器序列2) 编码器对序列1提取特征得到一个状态0 解码器: 输入序列2的START标记+状态0 输出为 序列的第1个单词+状态1 输入序列2的第1个单词+状态1 输出为序列的第2个单词+状态2 每次输入一个单词+前面的状态, 输出一个单词+一个状态 输入序列2的第n个单词+状态n 输出END标记+状态n+1 |
|
编码器与解码器的预测
输入序列1,预期的是,得到序列2
编码器对序列1提取特征得到一个状态0
解码器:
输入序列2的START标记+状态1
输出 单词1+状态1
输入单词1+状态1
输出一个单词2+状态2
每次输入上个单元输出的单词+前面的状态,
输出一个单词+一个状态
输入序列2的第n个单词+状态n
输出END标记+状态n+1
解码器 根据前面的输出,推断下一个输出大概率是什么
这是一个续补的过程,能依据的只有前面的序列信息
然后要给出缺失的部分
|
|
|
以单词为例,文本向量化后,一个单词对应一个向量 学习的理想效果是一个固定的向量 至 另外一个固定的向量 但这只是理想罢了 实际上模型预测 出来 一个新的向量, 并且这个新向量不与任何已有的单词匹配相等 通过梯度下降法 不断调用模型的参数,可以让模型的输出不断地向已有的单词向量靠近 总体思路 回归最初,模型输出一个新的向量, 要解决的问题是 这个新向量 与 已有单词向量相比,离哪个最近 这个问题,直接逆向决定了,单词如何编码,损失函数如何设计 这里先说一个结果, 序列到序列的预测,是要模型输出一个单词的概率
|
def maskNLLLoss(inp, target, mask):
"""
功能描述
- 计算模型预测单词与target标签单词之间距离
输入参数
- inp: 某个位置一个批次的单词,inp.shape=[64, 7826] = [batch_size,dict_len]
- target: 对应答句相应位置单词的索引
- mask: 该位置是单词还是pad
输出参数
- 批次平均损失
- 批次中单词个数
--------------
按位置计算,一次计算某个位置上一个批次的单词;
不同的句子长度不一致,所有句子在批次处理时长度皆为MAX_LENGTH
mask记录对应位置是否为单词,是单词为True,PAD为False
比如某句子长度为8,那么这句话9这个位置上就是PAD,
9这个位置对应的mask为False
"""
# 某个位置一个批次有多少个单词
nTotal = mask.sum() # 表示一个批次的某个位置上有多少个单词,1表示有单词,0表示无单词
print(f"target.shape={target.shape}") # target.shape=torch.Size([64])
index = target.view(-1, 1) # [64, 1]
# 取与 标签对应索引位置 上的模型输出的 数据,其他的都不要了
# 比如某个单词在原dict_len=7826这个字典的索引为100,那么其他位置上的值都不是该单词
# output的最后一维与dict_len=7826这个字典索引对应
# 所以,若标签这个位置应该被预测为索引100对应的单词时,
# 那么output索引100对应的数值应该概率最大
# 针对每个单词,先获取标签单词在字典中的索引下标,从one-hot的角度看,只有该位置为1,其他位置皆为0
# 同样取出模型输出的单词维度对应索引下标上的数据,该数据将与1通过损失函数进行校正
# 让模型输出的单词维度相同索引下标的数据不断接近1,让损失慢慢减少
# inp[batch_size,dict_len]单词维度dict_len做了softmax,其和为1
# 当某个索引位置上的数据接近1时,其他位置将趋于0
y = torch.gather(inp, 1, index) # [64,1]
y = y.squeeze(1) # [64]
# 交叉熵计算,计算一个批次的两个分布之间的距离,模型预测的单词与标签单词的距离
# -(lable*log(y) + (1-lable)*log(1-y))
# lable = 1 , -log(y)
# crossEntropy = -torch.log(torch.gather(inp, 1, index).squeeze(1))
# 分布的维度就是批次的维度,因此可以做交叉熵
crossEntropy = -torch.log(y) # [64]
# 所有单词位置上的损失的均值
# 取有单词的位置上的数据,不计算PAD
loss = crossEntropy.masked_select(mask).mean()
loss = loss.to(device)
return loss, nTotal.item()
|
# Initialize variables
loss = 0
print_losses = []
n_totals = 0
for t in range(max_target_len): # 解码序列有多少个单词,就循环多少次
# decoder_input每次从target_variable取出一个单词
# decoder_input.shape=[1, 64]
# decoder_hidden.shape=[2, 64, 500]
# decoder_output=[64,dict_len]
decoder_output, decoder_hidden = decoder(
decoder_input, decoder_hidden, encoder_outputs
)
# Teacher forcing: next input is current target
# 取当前序列需要输入的单词
# decoder_input.shape=[1,64]
# target_variable.shape=[10,64]
# target_variable是答句的word2index列表,还没有embedding
# 一次输入某个序列位置上一个批次的单词
# [64] -- [1,64]
# 从第二次循环开始,输入的是单词[seq_len,batch_size],其中seq_len=1
decoder_input = target_variable[t].view(1, -1)
# Calculate and accumulate loss
# mask[t]表示t位置是单词还是PAD标记,为单词时为True
# target_variable[t]是t位置一个批次单词的索引
mask_loss, nTotal = maskNLLLoss(decoder_output, target_variable[t], mask[t])
loss += mask_loss
# Perform backpropatation
loss.backward()
|
|
|
|
|
模糊还是精准
闲聊,模糊匹配:人工智能 精准,不允许出错:规则匹配
规则
正则表达式 能够快速从大量字符串中匹配符合格式的子串 一对一或一对多的映射 固定的格式,固定的关系
卷积过程详细讲解