数据预处理(TF20-Keras-Preprocessing)
我们自己的普通数据集(常用)
主要使用tensorflow.keras.preprocessing这个库中的(image, text,sequence)这三个模块。
text: 可以用来 (统计词频,分字,word_2_id, id_2_word等操作。)
sequence 可以(给句子做结构化操作(填充0,裁剪长度))
from tensorflow.keras.preprocessing.text import Tokenizer # 主干,句子编码
from tensorflow.keras.preprocessing.sequence import pad_sequences # 辅助,填充,剪枝
q1 = '欢 迎 你 你 你'
q2 = '我 很 好'
q_list = [q1,q2] # 需要特别注意,因为此API对英文友好,所以,我们必须把句子用 空格 隔开输入
token = Tokenizer(
num_words=2, # num_words代表设置过滤num_words-1个词频, 例如num_words=2,
# 那么过滤掉2-1=1个词频, 所以一会你会看到下面词频为1的都被过滤掉了
) # 这里面参数很多,还有标点符号过滤器等
token.fit_on_texts(q_list) # 把原始句子集合,放进去拟合一下(封装成一个类)
print(token.document_count) # 2 # 句子个数
print(token.word_index) # {'你': 1, '欢': 2, '迎': 3, '我': 4, '很': 5, '好': 6} # word_2_id
print(token.index_word) # {1: '你', 2: '欢', 3: '迎', 4: '我', 5: '很', 6: '好'} # id_2_word
print(token.word_counts) # OrderedDict([('欢', 1), ('迎', 1), ('你', 3), ('我', 1), ('很', 1), ('好', 1)]) # 统计词频
seq = token.texts_to_sequences(q_list) # 先把所有的输入,变成一一变成编码化
print(seq) # [[1, 1, 1], []] # 会不会好奇?数据怎么没了?因为我们上面设置了过滤词频为1的都过滤了
pad_seq = pad_sequences(
seq, # 输入编码化后的 句子
maxlen=2, # 统一句子最大长度
padding='pre', # 不足的补0, 从前面补0, (也可以用 post,代表后面)
truncating='pre' # 多余的长度裁剪,从前面裁剪
)
print(pad_seq) # 打印一下我们填充后的句子形状。
# [
# [1 1], # 如你所愿,最大长度为2,[1,1,1] 已经裁剪成了 [1,1]
# [0 0], # 如你所愿,之前[] ,已经都填满了0
# ]
虽然我们用不到 image这个模块数据增强模块,但是我把了解的API也写出来。
train_datagen = keras.preprocessing.image.ImageDataGenerator( # 数据增强生成器(定义)
rescale=1. / 255, # 数据归一化
rotation_range = 40, # -40-40 随机角度 (数据增强)
width_shift_range = 0.2, # 宽度位移(0-20%随机选个比例去平移) (数据增强)
height_shift_range = 0.2, # 高度位移(同上) (数据增强)
shear_range=0.2, # 图片剪切(0.2) (数据增强)
zoom_range=0.2, # 图片缩放(0.2) (数据增强)
horizontal_flip=True, # 图片随机水平反转 (数据增强)
fill_mode='nearest', # 图片填充像素(放大后失帧)用附近像素值来填充 (数据增强)
)
# train_generator = train_datagen.flow_from_dataframe() # 如果你用Pandas,你可以选这个
train_generator = train_datagen.flow_from_directory( # 从文件中读取(Kaggle)
train_dir, # 图片目录
target_size = (height, width), # 图片读取进来后缩放大小
batch_size = batch_size, # 就是批次
seed=6, # 随机化种子
shuffle=True, # 样本随机打散训练,增强模型泛化能力
class_mode='categorical', # label格式,是否需要one_hot, 是
)
...
...
train_num = train_generator.samples # 打印样本形状
history = model.fit_generator( # 注意我们上面是用的数据生成器,所以这要用 fit_generator
train_generator,
steps_per_epoch=train_num//batch_size, # 每个epoch多少 step(因为数据增强API是生成器方式,所以需要自己手动计算一下)
epochs=epochs,
validation_data=valid_generator, # 如果你有验证集,你也可以用这个。否则可以不用
validation_steps=valid_num//batch_size # 同上
)
Seq2Seq
思想
语言不同,那么我们可以搭建桥梁。
即使我们表面上不相同。 但是我们映射到这个桥梁上的结果是几乎类似的。
样本句子长度统一
为什么每个句子的长度需要统一?
因为,每个句子for循环操作会很耗算力, 而转化为矩阵/向量化操作,会节约太多算力。
因为矩阵运算严格要求样本的形状,所以每个句子的长度需要一致
如何做到句子长度统一?
填0, 对应TF操作就是padding, 不过TF20 的keras预处理包中已经有 成品的数据统一化操作。
并且还具有 word_2_id,词向量编码操作。
组成
- 编码器 (输入每条样本句子的每个单词, 编码器的最后一个RNN单元,浓缩了整个句子的信息)
- 中间向量 (作为中间特征桥梁, 用来保存,输入进来的整个句子)
- 解码器 (中间向量作为解码器第一个RNN单元的输入,而每个单元的输出y,作为下一个单元的输入)
其中解码器部分的输出y会用 softmax 对 词库(词典)求多分类概率。
然后求损失(MSE或者CrossEntropy)
注意了: softmax求出的概率该如何选择,这是个问题:
假如: 每个单元的输出y的概率都取最大值, 那么可能一步错,步步错。 太极端了(贪心搜索)
接下来,聊一聊一周 集束搜索的算法 BeamSearch
BeamSearch
由于贪心搜索(只取概率的一个最大值,的结果不尽人意。所以 BeamSearch来啦)
BeamSearch的主要思想:
只取一个太冒险了,所以: BeamSearch 取每个经过softmax输出概率集合的 Top-N个
Top-N: 的 N 代表你保留几个概率 (举一反三理解: 贪心算法就是 Top-1)
假如我们取Top-3个
那么你一个RNN节点的预测y将会保留3个概率值, 并将这3个概率值作为 下一个节点的输入。
具体流程看:下图 (可能有点丑)
然后,我们会选择出: 3 个 "红线" 最优路径。
最终: 我们通过单独的语言模型,来从这 3 个 "红线" 较优路径中,选出一个 最优路径。
Attention(注意力机制)
前情回顾
Seq2Seq 的 Encoder部分虽然用的是 高效的 LSTM,并且也很好的解决了,记忆的问题。
但是他不能很好的解决每个单词的权重分配问题。
虽然: Encoder的所有单元都会通过LSTM的记忆传递, 输入进“中间桥梁向量”。
但是: 还是有"偏心"成分, 最后一个LSTM单元信息一定是最浓的。 (新鲜的,热乎的)
所以: 你第1个LSTM单元的信息,或者说前面的LSTM单元的信息,这些记忆到最后可能会被稀释。
为了解决上面的问题, Attention就出来帮忙了~~~
Attentioin原理
我觉得墨迹半天不如自己画一张图~~~ (只会mspaint画图)
上图中计算权重那里"通过一个函数,可以是求相似度", 我简写了。 其实有两种常用的方式:
Bahdanau注意力:
weight = FC层( tanh ( FC层(Encoder的每个输出y) + FC层(Decoder的一个H) ) )
luong注意力:
weight = Encoder的每个输出y @ W随机权重矩阵 @ Decoder的一个H # @是TF20的矩阵乘法操作符
无论使用上面哪种: 都要套一层 Softmax
weight = softmax(weight, axis=1)
注意力向量C = sum( weight * Encoder的每个输出y , axis=1) # 加权求和,最终得到一个向量
Decoder的下一个输入 = concat( 注意力向量C, 上一个预测y4 )
Transformer
第一印象挑明: 他是一种无RNN的一种特殊的 Seq2Seq 模型。
RNN-LSTM-GRU虽然这些NN的主要特色就是"时间序列"。(缺点:慢,记忆弥散)
但是我们上面说了,要想取得好的效果。那么需要加Attention。
于是有人想到了,既然Attention效果这么好,为什么不直接用Attention呢?
Attention效果虽好,关联性强,但是它不能保证时间序列模式。
于是后来出现了 Transformer。(既能保证记忆注意力,又能保证时间序列)。具体如下!
Transformer整体结构组成
Self-Attention
self-attention原理就是各种链式矩阵乘法(并行计算,可用GPU加速)
self-attention计算过程如下:(假设输入句子切分单词为:矩阵X = ["早","上","好"])
矩阵X @ 权重矩阵Q(Q1,Q2,Q3)=> Q矩阵(Q1,Q2,Q3)
矩阵X @ 权重矩阵K(Q1,Q2,Q3)=> K矩阵(Q1,Q2,Q3)
矩阵X @ 权重矩阵V(Q1,Q2,Q3)=> V矩阵(Q1,Q2,Q3)
α = softmax( (Q矩阵 @ K矩阵) / q^0.5 )
self_attention = α @ V矩阵
Multi-Head Self-Attention
Multi-Head Attention 对 Self-Attention 对了如下扩展:
self-attention: 一组 Q矩阵,K矩阵,V矩阵
Multi-Head Self-Attention: 多组 Q矩阵,K矩阵,V矩阵
扩张为多头注意力的过程:
Q @ W ====> [Q1, Q2, Q3]
K @ W ====> [K1, K2, K3]
V @ W ====> [V1, V2, V3]
可理解为,多个卷积核的意思能提取不同特征的意思。
Position Encoder
上述的self-attention有个问题, 我们没有用到RNN等序列NN,那么矩阵相乘的过程中。
单词的计算顺序可能是不同的。
那么如何保证让他们位置有条不紊?
可以使用位置编码,融入到Embedding,形成带有时间序列性质的模型。
可自行查找计算位置编码的博文。
传送门
至于Transformer,现在官方已经有TF20和Pytorch的库了。
传送门如下。
https://github.com/huggingface/transformers
Transformer延申的各种模型,像Bert等也有可调用的API
https://huggingface.co/transformers/