日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)


Encoder编码器-Decoder解码器框架 + Attention注意力机制

Pytorch:Transformer(Encoder编码器-Decoder解码器、多头注意力机制、多头自注意力机制、掩码张量、前馈全连接层、规范化层、子层连接结构、pyitcast) part1

Pytorch:Transformer(Encoder编码器-Decoder解码器、多头注意力机制、多头自注意力机制、掩码张量、前馈全连接层、规范化层、子层连接结构、pyitcast) part2

Pytorch:使用Transformer构建语言模型

Pytorch:解码器端的Attention注意力机制、seq2seq模型架构实现英译法任务

BahdanauAttention注意力机制、LuongAttention注意力机制

BahdanauAttention注意力机制:基于seq2seq的西班牙语到英语的机器翻译任务、解码器端的Attention注意力机制、seq2seq模型架构

图片的描述生成任务、使用迁移学习实现图片的描述生成过程、CNN编码器+RNN解码器(GRU)的模型架构、BahdanauAttention注意力机制、解码器端的Attention注意力机制

注意力机制、bmm运算

注意力机制 SENet、CBAM

机器翻译 MXNet(使用含注意力机制的编码器—解码器,即 Encoder编码器-Decoder解码器框架 + Attention注意力机制)

基于Seq2Seq的中文聊天机器人编程实践(Encoder编码器-Decoder解码器框架 + Attention注意力机制)

基于Transformer的文本情感分析编程实践(Encoder编码器-Decoder解码器框架 + Attention注意力机制 + Positional Encoding位置编码)

注意:这一文章“基于Transformer的文本情感分析编程实践(Encoder编码器-Decoder解码器框架 + Attention注意力机制 + Positional Encoding位置编码)”该文章实现的Transformer的Model类型模型,实际是改造过的特别版的Transformer,因为Transformer的Model类型模型中只实现了Encoder编码器,而没有对应实现的Decoder解码器,并且因为当前Transformer的Model类型模型处理的是分类任务,所以我们此处只用了Encoder编码器来提取特征,最后通过全连接层网络来拟合分类。

# coding=utf-8
import configparser
def get_config(config_file='config.ini'):parser=configparser.ConfigParser()parser.read(config_file)# get the ints, floats and strings_conf_ints = [(key, int(value)) for key, value in parser.items('ints')]_conf_floats = [(key, float(value)) for key, value in parser.items('floats')]_conf_strings = [(key, str(value)) for key, value in parser.items('strings')]return dict(_conf_ints + _conf_floats + _conf_strings)

[strings]
# Mode : train, test, serve
mode = traintrain_pos_data_path=aclImdb/train/pos
train_neg_data_path=aclImdb/train/neg
test_pos_data_path=aclImdb/test/pos
test_neg_data_path=aclImdb/test/negtrain_pos_data=train_data/train_pos_data.txt
train_neg_data=train_data/train_neg_data.txt
test_pos_data=train_data/test_pos_data.txt
test_neg_data=train_data/test_neg_data.txtall_data=all_data.txt
vocabulary_file=train_data/vocab10000.txt
working_directory=train_data/
model_dir=model_data/
npz_data=train_data/imdb.npz[ints]
vocabulary_size=10000
sentence_size = 100
embedding_size = 80
steps_per_checkpoint = 10
num_layers = 4
num_heads = 8
batch_size=64
epochs = 1
shuffle_size=20000
diff=1024[floats]
dropout_rate=0.1

# coding=utf-8
import os
import numpy as np
import getConfig
gConfig={}gConfig=getConfig.get_config(config_file='config.ini')UNK = "__UNK__"  # 标记未出现在词汇表中的字符
START_VOCABULART = [UNK]
UNK_ID = 1
# 定义字典生成函数
#生成字典的原理其实很简单,就是统计所有训练数据中的词频,然后按照词频进行排序,每个词在训练集中出现次数就是其对应的编码
#知识点:函数定义、在函数中函数的调用是不需要声明的、字典类型"""
词频词典的创建:
1、读取所有的文字
2、统计每个文字的出现次数
3、排序
4、取值保存"""
def create_vocabulary(input_file,vocabulary_size,output_file):vocabulary = {}k=int(vocabulary_size)with open(input_file,'r') as f:counter = 0for line in f:counter += 1tokens = [word for word in line.split()]for word in tokens:if word in vocabulary:vocabulary[word] += 1else:vocabulary[word] = 1vocabulary_list = START_VOCABULART + sorted(vocabulary, key=vocabulary.get, reverse=True)# 根据配置,取vocabulary_size大小的词典if len(vocabulary_list) > k:vocabulary_list = vocabulary_list[:k]#将生成的词典保存到文件中print(input_file + " 词汇表大小:", len(vocabulary_list))with open(output_file, 'w') as ff:for word in vocabulary_list:ff.write(word + "\n")#在生成字典之后,我们就需要将我们之前训练集的文字全部用字典进行替换
#知识点:list的append和extend,dict的get操作、文件的写入操作# 把对话字符串转为向量形式"""
1、遍历文件
2、找到一个字 然后在词典出来,然后做替换
3、保存文件"""
def convert_to_vector(input_file, vocabulary_file, output_file):print('文字转向量...')tmp_vocab = []with open(vocabulary_file, "r") as f:#读取字典文件的数据,生成一个dict,也就是键值对的字典tmp_vocab.extend(f.readlines())tmp_vocab = [line.strip() for line in tmp_vocab]vocab = dict([(x, y) for (y, x) in enumerate(tmp_vocab)])#将vocabulary_file中的键值对互换,因为在字典文件里是按照{123:好}这种格式存储的,我们需要换成{好:123}格式output_f = open(output_file, 'w')with open(input_file, 'r') as f:line_out=[]for line in f:line_vec = []for words in line.split():line_vec.append(vocab.get(words, UNK_ID))#获取words的对应编码,如果找不到就返回UNK_IDoutput_f.write(" ".join([str(num) for num in line_vec]) + "\n")#将input_file里的中文字符通过查字典的方式,替换成对应的key,并保存在output_file#print(line_vec)line_out.append(line_vec)output_f.close()return line_outdef prepare_custom_data(working_directory,train_pos,train_neg,test_pos,test_neg,all_data,vocabulary_size):# 生成字典的路径,encoder和decoder的字典是分开的vocab_path = os.path.join(working_directory, "vocab%d.txt" % vocabulary_size)#生成字典文件create_vocabulary(all_data,vocabulary_size,vocab_path)# 将训练数据集的中文用字典进行替换pos_train_ids_path = train_pos + (".ids%d" % vocabulary_size)neg_train_ids_path = train_neg + (".ids%d" % vocabulary_size)train_pos=convert_to_vector(train_pos, vocab_path, pos_train_ids_path)train_neg=convert_to_vector(train_neg, vocab_path, neg_train_ids_path)# 将测试数据集的中文用字典进行替换pos_test_ids_path = test_pos + (".ids%d" % vocabulary_size)neg_test_ids_path = test_neg + (".ids%d" % vocabulary_size)test_pos=convert_to_vector(test_pos, vocab_path, pos_test_ids_path)test_neg=convert_to_vector(test_neg, vocab_path, neg_test_ids_path)return train_pos,train_neg,test_pos,test_negtrain_pos,train_neg,test_pos,test_neg=prepare_custom_data(gConfig['working_directory'],gConfig['train_pos_data'],gConfig['train_neg_data'],gConfig['test_pos_data'],gConfig['test_neg_data'],gConfig['all_data'],gConfig['vocabulary_size'])y_trian=[]
y_test=[]for i in range(len(train_pos)):y_trian.append(0)
for i in range(len(train_neg)):y_trian.append(1)
for i in range(len(test_pos)):y_test.append(0)
for i in range(len(test_neg)):y_test.append(1)x_train=np.concatenate((train_pos,train_neg),axis=0)
x_test=np.concatenate((test_pos,test_neg),axis=0)
np.savez("train_data/imdb.npz",x_train,y_trian,x_test,y_test)
注意:下面实现Transformer的Model类型模型,实际是改造过的特别版的Transformer,因为Transformer的Model类型模型中只实现了Encoder编码器,而没有对应实现的Decoder解码器,并且因为当前Transformer的Model类型模型处理的是分类任务,所以我们此处只用了Encoder编码器来提取特征,最后通过全连接层网络来拟合分类。

# coding=utf-8
import tensorflow as tf
import numpy as np
import getConfig
gConfig={}
gConfig=getConfig.get_config(config_file='config.ini')def get_angles(pos, i, d_model):angle_rates = 1 / np.power(10000, (2 * (i//2)) / np.float32(d_model))return pos * angle_ratesdef positional_encoding(position, d_model):angle_rads = get_angles(np.arange(position)[:, np.newaxis],np.arange(d_model)[np.newaxis, :],d_model)sines = np.sin(angle_rads[:, 0::2])cosines = np.cos(angle_rads[:, 1::2])pos_encoding = np.concatenate([sines, cosines], axis=-1)pos_encoding = pos_encoding[np.newaxis, ...]return tf.cast(pos_encoding, dtype=tf.float32)def scaled_dot_product_attention(q, k, v,mask):matmul_qk = tf.matmul(q, k, transpose_b=True)  # (..., seq_len_q, seq_len_k)dk = tf.cast(tf.shape(k)[-1], tf.float32)scaled_attention_logits = matmul_qk / tf.math.sqrt(dk)if mask is not None:scaled_attention_logits += (mask * -1e9)attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1)  # (..., seq_len_q, seq_len_k)output = tf.matmul(attention_weights, v)  # (..., seq_len_v, depth)return output, attention_weightsclass MultiHeadAttention(tf.keras.layers.Layer):def __init__(self, d_model, num_heads):super(MultiHeadAttention, self).__init__()self.num_heads = num_headsself.d_model = d_modelassert d_model % self.num_heads == 0self.depth = d_model // self.num_headsself.wq = tf.keras.layers.Dense(d_model)self.wk = tf.keras.layers.Dense(d_model)self.wv = tf.keras.layers.Dense(d_model)self.dense = tf.keras.layers.Dense(d_model)def split_heads(self, x, batch_size):x = tf.reshape(x, (batch_size, -1, self.num_heads, self.depth))return tf.transpose(x, perm=[0, 2, 1, 3])def call(self, v, k, q,mask):batch_size = tf.shape(q)[0]q = self.wq(q)  # (batch_size, seq_len, d_model)k = self.wk(k)  # (batch_size, seq_len, d_model)v = self.wv(v)  # (batch_size, seq_len, d_model)q = self.split_heads(q, batch_size)  # (batch_size, num_heads, seq_len_q, depth)k = self.split_heads(k, batch_size)  # (batch_size, num_heads, seq_len_k, depth)v = self.split_heads(v, batch_size)  # (batch_size, num_heads, seq_len_v, depth)scaled_attention, attention_weights = scaled_dot_product_attention(q, k, v,mask)scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3])  # (batch_size, seq_len_v, num_heads, depth)concat_attention = tf.reshape(scaled_attention,(batch_size, -1, self.d_model))  # (batch_size, seq_len_v, d_model)output = self.dense(concat_attention)  # (batch_size, seq_len_v, d_model)return output, attention_weightsdef point_wise_feed_forward_network(d_model,dff):return tf.keras.Sequential([tf.keras.layers.Dense(dff,activation='relu'),tf.keras.layers.Dense(d_model)
])class EncoderLayer(tf.keras.layers.Layer):def __init__(self, d_model, diff,num_heads, rate=0.1):super(EncoderLayer, self).__init__()self.mha = MultiHeadAttention(d_model, num_heads)self.ffn = point_wise_feed_forward_network(d_model,diff)self.dropout1 = tf.keras.layers.Dropout(rate)self.dropout2 = tf.keras.layers.Dropout(rate)self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)def call(self, x, training,mask):attn_output, _ = self.mha(x, x, x,mask)  # (batch_size, input_seq_len, d_model)attn_output = self.dropout1(attn_output, training=training)out1 =self.layernorm1 (x + attn_output)  # (batch_size, input_seq_len, d_model)ffn_output = self.ffn(out1)  # (batch_size, input_seq_len, d_model)ffn_output = self.dropout2(ffn_output, training=training)out2 = self.layernorm2 (out1 + ffn_output)  # (batch_size, input_seq_len, d_model)return out2class Encoder(tf.keras.layers.Layer):def __init__(self, num_layers, d_model,dff, num_heads, input_vocab_size,rate=0.1):super(Encoder, self).__init__()self.d_model = d_modelself.num_layers = num_layersself.embedding = tf.keras.layers.Embedding(input_vocab_size, d_model)self.pos_encoding = positional_encoding(input_vocab_size, self.d_model)self.enc_layers = [EncoderLayer(d_model,dff, num_heads, rate)for _ in range(num_layers)]self.dropout = tf.keras.layers.Dropout(rate)def call(self, x, training,mask):seq_len = tf.shape(x)[1]#print(seq_len)# adding embedding and position encoding.x = self.embedding(x)  # (batch_size, input_seq_len, d_model)#print(x)x *= tf.math.sqrt(tf.cast(self.d_model, tf.float32))x += self.pos_encoding[:, :seq_len, :]x = self.dropout(x, training=training)for i in range(self.num_layers):x = self.enc_layers[i](x, training,mask)#print(x)return x  # (batch_size, input_seq_len, d_model)class Transformer(tf.keras.Model):def __init__(self, num_layers, d_model, dff,num_heads, input_vocab_size, rate=0.1):super(Transformer, self).__init__()self.encoder = Encoder(num_layers, d_model,dff, num_heads,input_vocab_size, rate)self.ffn_out=tf.keras.layers.Dense(2,activation='softmax')self.dropout1 = tf.keras.layers.Dropout(rate)def call(self, inp, training,enc_padding_mask):enc_output = self.encoder(inp, training,enc_padding_mask)  # (batch_size, inp_seq_len, d_model)out_shape=gConfig['sentence_size']*gConfig['embedding_size']enc_output=tf.reshape(enc_output,[-1,out_shape])ffn=self.dropout1(enc_output,training=training)ffn_out=self.ffn_out(ffn)return ffn_outclass CustomSchedule(tf.keras.optimizers.schedules.LearningRateSchedule):def __init__(self, d_model, warmup_steps=40):super(CustomSchedule, self).__init__()self.d_model = d_modelself.d_model = tf.cast(self.d_model, tf.float32)self.warmup_steps = warmup_stepsdef __call__(self, step):arg1 = tf.math.rsqrt(step)arg2 = step * (self.warmup_steps ** -1.5)return tf.math.rsqrt(self.d_model) * tf.math.minimum(arg1, arg2)learning_rate = CustomSchedule(gConfig['embedding_size'])
optimizer = tf.keras.optimizers.Adam(learning_rate)
#temp_learning_rate_schedule = CustomSchedule(gConfig['embedding_size'])
train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')
transformer = Transformer(gConfig['num_layers'],gConfig['embedding_size'],gConfig['diff'] ,gConfig['num_heads'],gConfig['vocabulary_size'],gConfig['dropout_rate'])
ckpt = tf.train.Checkpoint(transformer=transformer,optimizer=optimizer)def create_padding_mask(seq):seq = tf.cast(tf.math.equal(seq, 0), tf.float32)return seq[:, tf.newaxis, tf.newaxis, :]  # (batch_size, 1, 1, seq_len)def step(inp, tar,train_status=True):enc_padding_mask=create_padding_mask(inp)if train_status:with tf.GradientTape() as tape:predictions= transformer(inp,True,enc_padding_mask)tar = tf.keras.utils.to_categorical(tar, 2)loss = tf.losses.categorical_crossentropy(tar,predictions)loss= tf.reduce_mean(loss)print(loss.numpy())gradients = tape.gradient(loss, transformer.trainable_variables)optimizer.apply_gradients(zip(gradients, transformer.trainable_variables))return losselse:predictions = transformer(inp,False, enc_padding_mask)return predictionsdef evaluate(inp,tar):enc_padding_mask = create_padding_mask(inp)predictions= transformer(inp,False,enc_padding_mask)tar = tf.keras.utils.to_categorical(tar, 2)loss =tf.losses.categorical_crossentropy(tar, predictions)return train_loss(loss)

# coding=utf-8
import tensorflow as tf
import numpy as np
import getConfig
import tensorflow.keras.preprocessing.sequence as sequence
import textClassiferModel as model
import time
UNK_ID=3gConfig={}
gConfig=getConfig.get_config(config_file='config.ini')
sentence_size=gConfig['sentence_size']
embedding_size = gConfig['embedding_size']
vocab_size=gConfig['vocabulary_size']
model_dir = gConfig['model_dir']def read_npz(data_file):r = np.load(data_file)return r['arr_0'],r['arr_1'],r['arr_2'],r['arr_3']def pad_sequences(inp):out_sequences=sequence.pad_sequences(inp, maxlen=gConfig['sentence_size'],padding='post',value=0)return out_sequences
x_train, y_train, x_test, y_test =read_npz(gConfig['npz_data'])
x_train = pad_sequences(x_train)
x_test = pad_sequences(x_test)dataset_train = tf.data.Dataset.from_tensor_slices((x_train,y_train)).shuffle(gConfig['shuffle_size'])
dataset_test = tf.data.Dataset.from_tensor_slices((x_test,y_test)).shuffle(gConfig['shuffle_size'])
checkpoint_path = gConfig['model_dir']
ckpt_manager = tf.train.CheckpointManager(model.ckpt, checkpoint_path, max_to_keep=5)def create_model():ckpt = tf.io.gfile.listdir(checkpoint_path)if ckpt:print("reload pretrained model")model.ckpt.restore(tf.train.latest_checkpoint(checkpoint_path))return modelelse:return modeldef train():model=create_model()while True:model.train_loss.reset_states()model.train_accuracy.reset_states()for (batch,(inp, target)) in enumerate(dataset_train.batch(gConfig['batch_size'])):start = time.time()loss = model.step(inp,target)print ('训练集:Epoch {:} ,Batch {:} ,Loss {:.4f},Prestep {:.4f}'.format(epoch + 1, batch,loss.numpy(),(time.time()-start)))#for (batch,(inp,target)) in enumerate(dataset_test.batch(gConfig['batch_size'])):#start = time.time()# loss = model.evaluate(inp,target)# print ('测试集:Epoch {:} ,Batch {:} ,Loss {:.4f},Prestep {:.4f}'.format(epoch + 1, batch,loss.numpy(),(time.time()-start)))ckpt_save_path=ckpt_manager.save()print ('保存epoch{}模型在 {}'.format(epoch+1, ckpt_save_path))def text_to_vector(inp):vocabulary_file=gConfig['vocabulary_file']tmp_vocab = []with open(vocabulary_file, "r") as f:#读取字典文件的数据,生成一个dict,也就是键值对的字典tmp_vocab.extend(f.readlines())tmp_vocab = [line.strip() for line in tmp_vocab]vocab = dict([(x, y) for (y, x) in enumerate(tmp_vocab)])print(vocab)line_vec = []for words in inp.split():line_vec.append(vocab.get(words, UNK_ID))  #return line_vecdef predict(sentences):state=['pos','neg']model=create_model()indexes = text_to_vector(sentences)print(indexes)inp = pad_sequences([indexes])inp=tf.reshape(inp[0],(1,len(inp[0])))predictions=model.step(inp,inp,False)pred = tf.math.argmax(predictions[0])p=np.int32(pred.numpy())return state[p]if __name__ == "__main__":if gConfig['mode']=='train':train()elif gConfig['mode']=='serve':print('Sever Usage:python3 app.py')

# coding=utf-8
from flask import Flask, render_template, request, make_response
from flask import jsonify
import time
import threading"""
定义心跳检测函数
"""
def heartbeat():print (time.strftime('%Y-%m-%d %H:%M:%S - heartbeat', time.localtime(time.time())))timer = threading.Timer(60, heartbeat)timer.start()
timer = threading.Timer(60, heartbeat)
timer.start()app = Flask(__name__,static_url_path="/static")
@app.route('/message', methods=['POST'])#"""定义应答函数,用于获取输入信息并返回相应的答案"""
def reply():
#从请求中获取参数信息req_msg = request.form['msg']res_msg = execute.predict(req_msg)return jsonify( { 'text': res_msg } )@app.route("/")
def index(): return render_template("index.html")
import execute# 启动APP
if (__name__ == "__main__"): app.run(host = '0.0.0.0', port = 8808)

自定义网络层参数归一化(layer nomalization)处理的函数


textSentimentClassification完整版注释

注意:下面实现Transformer的Model类型模型,实际是改造过的特别版的Transformer,因为Transformer的Model类型模型中只实现了Encoder编码器,而没有对应实现的Decoder解码器,并且因为当前Transformer的Model类型模型处理的是分类任务,所以我们此处只用了Encoder编码器来提取特征,最后通过全连接层网络来拟合分类。
报错:ValueError: Object arrays cannot be loaded when allow_pickle=False
解决:安装 numpy的1.16.2版本,暂时高于1.16.2版本都会出问题conda install numpy==1.16.2pip install numpy==1.16.2
例子:conda 已经安装有1.18.1版本的numpy,即使执行conda install numpy==1.16.2之后,实际安装效果为1.16.2版本的numpy-base。conda list 显示如下两者共存不影响使用:numpy                     1.18.1numpy-base                1.16.2现在重新执行重新读取imdb.npz便不会报错了。#读取训练集文件train_data/imdb.npz,并返回所读取的数据
def read_npz(data_file):r = np.load(data_file)#返回值分别代表 x_train, y_train, x_test, y_testreturn r['arr_0'],r['arr_1'],r['arr_2'],r['arr_3']------------------------------------------------------------------------------------------------------------------------------报错ValueError: setting an array element with a sequence.tf.keras.utils.to_categorical函数底层的第一行代码np.array(输入, dtype='int')如果报错ValueError: setting an array element with a sequence.
解决方法一:tf.enable_eager_execution() #开启紧急执行
解决方法二:可以尝试使用tf.one_hot(input, num_classes) 来代替 tf.keras.utils.to_categorical(input, num_classes)------------------------------------------------------------------------------------------------------------------------------
报错AttributeError: 'Tensor' object has no attribute 'numpy'
解决:tf.enable_eager_execution()
拓展:获取tensor类型的变量值方法一:在会话中print( sess.run(x) )import tensorflow as tf#定义tensor常量x = tf.random_uniform((2, 3), -1, 1)#开启会话with tf.Session() as sess:print (sess.run(x))#[[ 0.46468353 -0.61598516 -0.9036629 ]# [ 0.68292856  0.65335464  0.00641084]]方法二:使用.eval()相当于将tensor类数据转为numpy,再输出import tensorflow as tf#定义tensor常量x = tf.random_uniform((2, 2), -1, 1)#开启会话with tf.Session() as sess:print (x.eval())#[[-0.8492842   0.50390124]# [-0.9012234   0.77892494]]方法三:开启紧急执行tf.enable_eager_execution()方法四:tensor类型变量.numpy()
config.ini[strings]
# 配置执行器的运行模式 Mode : train, test, serve
mode = train#配置 训练集标注为 积极的 文本数据的路径
train_pos_data_path=aclImdb/train/pos
#配置 训练集标注为 消极的 文本数据的路径
train_neg_data_path=aclImdb/train/neg
#配置 测试集标注为 积极的 文本数据的路径
test_pos_data_path=aclImdb/test/pos
#配置 测试集标注为 消极的 文本数据的路径
test_neg_data_path=aclImdb/test/neg#配置 训练集 积极的 文本数据
train_pos_data=train_data/train_pos_data.txt
#配置 训练集标注为 消极的 文本数据
train_neg_data=train_data/train_neg_data.txt
#配置 测试集标注为 积极的 文本数据
test_pos_data=train_data/test_pos_data.txt
#配置 测试集标注为 消极的 文本数据
test_neg_data=train_data/test_neg_data.txt
all_data=all_data.txt
#配置字典的路径
vocabulary_file=train_data/vocab10000.txt
#配置训练集的路径
working_directory=train_data/
#配置模型保存路径
model_dir=model_data/
#配置训练集文件
npz_data=train_data/imdb.npz[ints]
#配置字典的大小
vocabulary_size=10000
#配置句子的最大长度
sentence_size = 100
#配置Embedding的长度
embedding_size = 80
#配置保存点
steps_per_checkpoint = 10
#配置 Encoder Layer的层数
num_layers = 4
#配置多头注意力的多头的头数量
num_heads = 8
#配置 批量大小
batch_size=64
#配置完整训练周期数
epochs = 1
#配置随机打乱参数
shuffle_size=20000
diff=1024[floats]
#配置神经元失效的比例概率
dropout_rate=0.1
================================================================================================================================
data_util.py
# coding=utf-8
import os
import numpy as np
import getConfiggConfig={}
gConfig=getConfig.get_config(config_file='config.ini')UNK = "__UNK__"  #用于标记未出现在词汇表中的字符
START_VOCABULART = [UNK]
UNK_ID = 3# 定义字典生成函数
# 生成字典的原理其实很简单,就是统计所有训练数据中的词频,然后按照词频进行排序,每个词在训练集中出现次数就是其对应的编码
# 知识点:函数定义、在函数中函数的调用是不需要声明的、字典类型"""
词频词典的创建:
1、读取所有的文字
2、统计每个文字的出现次数
3、排序
4、取值保存
"""#生成字典文件到本地
def create_vocabulary(input_file, vocabulary_size, output_file):#字典的路径,encoder和decoder的字典是分开的vocabulary = {}#字典的大小10000k = int(vocabulary_size)#读取./all_data.txt数据文件with open(input_file,'r',encoding="utf8") as f:counter = 0#默认按行读取for line in f:counter += 1#split() 默认按空格字符对一行数据进行切割tokens = [word for word in line.split()]#遍历一行中的每个单词for word in tokens:#如果单词已经存在字典中,则value+=1,反之存储键值对到字典中并将value置为1if word in vocabulary:vocabulary[word] += 1else:vocabulary[word] = 1#遍历完文件每行之后,对字典按照value值从大到小对键值对进行排序,然后把排序后的数据返回为list#key=vocabulary.get 通过字典对象vocabulary.get定义排序的规则,按照对字典的value值进行排序#reverse=True 表示从大到小排序vocabulary_list = START_VOCABULART + sorted(vocabulary, key=vocabulary.get, reverse=True)# print(sorted(vocabulary, key=vocabulary.get, reverse=True)[0]) #the# print(sorted(vocabulary, key=vocabulary.get, reverse=True)[1]) #a# print(sorted(vocabulary, key=vocabulary.get, reverse=True)[-1]) #Selleca# print(vocabulary.get("the")) #568735# print(vocabulary.get("a")) #306960# print(vocabulary.get("Selleca")) #1# 根据配置,取vocabulary_size大小的词典if len(vocabulary_list) > k:#取出字典的大小10000个单词,即将出现频率最高的前10000个单词vocabulary_list = vocabulary_list[:k]#将生成的词典保存到文件中print(input_file + " 词汇表大小:", len(vocabulary_list))#将出现频率最高的前10000个单词保存到本地文件train_data/vocab10000.txtwith open(output_file, 'w' ,encoding="utf8") as ff:#一行一个单词写出到本地文件for word in vocabulary_list:ff.write(word + "\n")#在生成字典之后,我们就需要将我们之前训练集的文字全部用字典进行替换
#知识点:list的append和extend,dict的get操作、文件的写入操作
# 把对话字符串转为向量形式"""
1、遍历文件
2、找到一个字 然后在词典出来,然后做替换
3、保存文件
"""# 通过{单词:出现频率次数}的字典 把每行句子中的每个单词都替换为相应的value值(出现频率次数),最后返回该全部value值(出现频率次数)组成的列表
def convert_to_vector(input_file, vocabulary_file, output_file):print('文字转向量...')tmp_vocab = []#读取train_data/vocab10000.txt字典文件的数据,生成一个dict,也就是键值对的字典with open(vocabulary_file, "r",encoding="utf8") as f:# 一次性读取多行文件数据 临时保存到tmp_vocab列表中tmp_vocab.extend(f.readlines())#遍历每行数据,strip()表示每行数据按照默认空格进行切割tmp_vocab = [line.strip() for line in tmp_vocab]#enumerate 遍历的是(y, x) 即为{1:the}、{2:a},所以还要翻转变成{the:1}、{a:2} 存储到 字典中#tmp_vocab列表中的数据实际是出现频率最高的前10000个单词,出现频率从高到小的单词按照列表索引0值开始排列,# 因此遍历出来的单词的出现频率是从高到小的vocab = dict([(x, y) for (y, x) in enumerate(tmp_vocab)])# print(vocab.get("the")) #1# print(vocab.get("a")) #2#将积极的/消极的文本数据的每个单词替换为出现频率 再输出到文件中output_f = open(output_file, 'w', encoding="utf8")#读取积极的/消极的文本数据with open(input_file, 'r',encoding="utf8") as f:line_out=[]#默认按行读取按行遍历 积极的/消极的文本数据for line in f:line_vec = []#split() 默认按照空格字符进行切割for words in line.split():#获取单词在字典中对应value值(出现频率次数),如果单词不存在字典中的话就默认返回UNK_ID = 3line_vec.append(vocab.get(words, UNK_ID))#一行单词全都替换为对应的value值(出现频率次数)之后,重新按照空格作为每个value值之间的分割符组成一行再输出到文件中output_f.write(" ".join([str(num) for num in line_vec]) + "\n")#把每行单词都替换之后的value值(出现频率次数)的列表 作为最后的函数返回line_out.append(line_vec)output_f.close()#最后返回该全部value值(出现频率次数)组成的列表return line_outdef prepare_custom_data(working_directory,train_pos,train_neg,test_pos,test_neg,all_data,vocabulary_size):# 构建要写出数据到本地保存的文件路径train_data/vocab10000.txt# 生成字典的路径,encoder和decoder的字典是分开的vocab_path = os.path.join(working_directory, "vocab%d.txt" % vocabulary_size)#生成字典,并最终将出现频率最高的前10000个单词create_vocabulary(all_data, vocabulary_size, vocab_path)#读取 训练集 积极的 文本数据train_data/train_pos_data.txt,#将积极的文本数据的每个单词替换为出现频率 再输出到train_data/train_pos_data.txt.ids10000pos_train_ids_path = train_pos + (".ids%d" % vocabulary_size)#读取 训练集标注为 消极的 文本数据 train_data/train_neg_data.txt,#将消极的文本数据的每个单词替换为出现频率 再输出到 train_data/train_neg_data.txt.ids10000neg_train_ids_path = train_neg + (".ids%d" % vocabulary_size)#通过{单词:出现频率次数}的字典 把每行句子中的每个单词都替换为相应的value值(出现频率次数),最后返回该全部value值(出现频率次数)组成的列表train_pos=convert_to_vector(train_pos, vocab_path, pos_train_ids_path)train_neg=convert_to_vector(train_neg, vocab_path, neg_train_ids_path)#读取 测试集标注为 积极的 文本数据 train_data/test_pos_data.txt,#将积极的文本数据的每个单词替换为出现频率 再输出到train_data/test_pos_data.txt.ids10000pos_test_ids_path = test_pos + (".ids%d" % vocabulary_size)#读取 测试集标注为 消极的 文本数据 train_data/test_neg_data.txt,#将消极的文本数据的每个单词替换为出现频率 再输出到train_data/test_neg_data.txt.ids10000neg_test_ids_path = test_neg + (".ids%d" % vocabulary_size)# 通过{单词:出现频率次数}的字典 把每行句子中的每个单词都替换为相应的value值(出现频率次数),最后返回该全部value值(出现频率次数)组成的列表test_pos=convert_to_vector(test_pos, vocab_path, pos_test_ids_path)test_neg=convert_to_vector(test_neg, vocab_path, neg_test_ids_path)#返回 积极的/消极的文本数据的每个单词其对应的 value值(出现频率次数)return train_pos,train_neg,test_pos,test_neg#把 积极的/消极的文本数据的每个单词 都替换为 其对应的 value值(出现频率次数)
train_pos,train_neg,test_pos,test_neg = prepare_custom_data(gConfig['working_directory'],gConfig['train_pos_data'],gConfig['train_neg_data'],gConfig['test_pos_data'],gConfig['test_neg_data'],gConfig['all_data'],gConfig['vocabulary_size'])#训练集数据的标签集(包含积极的/消极的文本数据的标签集):积极的文本数据的标签用0表示,消极的文本数据的标签用1表示
y_trian=[]
#测试集数据的标签集(包含积极的/消极的文本数据的标签集):积极的文本数据的标签用0表示,消极的文本数据的标签用1表示
y_test=[]
#训练集数据的标签集:积极的文本数据的标签用0表示
for i in range(len(train_pos)):y_trian.append(0)
#训练集数据的标签集:消极的文本数据的标签用1表示
for i in range(len(train_neg)):y_trian.append(1)
#测试集数据的标签集:积极的文本数据的标签用0表示
for i in range(len(test_pos)):y_test.append(0)
#测试集数据的标签集:消极的文本数据的标签用1表示
for i in range(len(test_neg)):y_test.append(1)
#把积极的/消极的文本数据都构建到同一个训练集数据中
x_train=np.concatenate((train_pos, train_neg),axis=0)
#把积极的/消极的文本数据都构建到同一个测试集数据中
x_test=np.concatenate((test_pos, test_neg),axis=0)
#保存为npz格式文件
np.savez("train_data/imdb.npz",x_train,y_trian,x_test,y_test)================================================================================================================================
getConfig.py
# coding=utf-8
import configparser# configparser为 用于读取配置文件的包
def get_config(config_file='config.ini'):parser=configparser.ConfigParser()parser.read(config_file,encoding="utf8")#获取int、float、string等类型的参数,按照key-value形式保存_conf_ints = [(key, int(value)) for key, value in parser.items('ints')]_conf_floats = [(key, float(value)) for key, value in parser.items('floats')]_conf_strings = [(key, str(value)) for key, value in parser.items('strings')]#封装为一个字典对象,包含所读取的所有参数return dict(_conf_ints + _conf_floats + _conf_strings)================================================================================================================================execute.py
# coding=utf-8
import tensorflow as tf
import numpy as np
import getConfig
import tensorflow.keras.preprocessing.sequence as sequence
import textClassiferModel as model
import timeUNK_ID=3
#定义一个字典用于接收配置文件的配置参数
gConfig={}
gConfig=getConfig.get_config(config_file='config.ini')sentence_size=gConfig['sentence_size'] #句子的最大长度100
embedding_size = gConfig['embedding_size']#Embedding的长度80
vocab_size=gConfig['vocabulary_size']#字典的大小10000
model_dir = gConfig['model_dir']#模型保存路径model_data/
checkpoint_path = gConfig['model_dir']#模型保存路径model_data/#读取训练集文件train_data/imdb.npz,并返回所读取的数据
def read_npz(data_file):r = np.load(data_file)#返回值分别代表 x_train, y_train, x_test, y_testreturn r['arr_0'],r['arr_1'],r['arr_2'],r['arr_3']#定义paddding填充函数,对长度不足的语句使用0进行补全。
#把每行句子的长度要么填充到100,要么截取为100。
def pad_sequences(inp):#对输入语句按照maxlen最大长度进行以0补全,默认补0,可指定padding='post'在后面做填充。out_sequences=sequence.pad_sequences(inp, maxlen=gConfig['sentence_size'],padding='post',value=0)return out_sequences"""
报错:ValueError: Object arrays cannot be loaded when allow_pickle=False
解决:安装 numpy的1.16.2版本,暂时高于1.16.2版本都会出问题conda install numpy==1.16.2pip install numpy==1.16.2
例子:conda 已经安装有1.18.1版本的numpy,即使执行conda install numpy==1.16.2之后,实际安装效果为1.16.2版本的numpy-base。conda list 显示如下两者共存不影响使用:numpy                     1.18.1numpy-base                1.16.2现在重新执行重新读取imdb.npz便不会报错了。
"""
#读取训练集文件train_data/imdb.npz,并返回所读取的数据
# x_train:25000行句子,每行句子的单词数不尽相同。训练集数据(包含积极的/消极的文本数据),读取的数据实际为单词对应的出现频率次数值。
# y_train:25000个标签值。训练集数据的标签集(包含积极的/消极的文本数据的标签),积极的文本数据的标签用0表示,消极的文本数据的标签用1表示
# x_test:25000行句子,每行句子的单词数不尽相同。测试集数据(包含积极的/消极的文本数据),读取的数据实际为单词对应的出现频率次数值
# y_test:25000个标签值。测试集数据的标签集(包含积极的/消极的文本数据的标签),积极的文本数据的标签用0表示,消极的文本数据的标签用1表示
x_train, y_train, x_test, y_test = read_npz(gConfig['npz_data'])#定义paddding填充函数,对长度不足的语句使用0进行补全。
#把每行句子的长度要么填充到100,要么截取为100。
x_train = pad_sequences(x_train)
x_test = pad_sequences(x_test)
# print("x_train.shape:",x_train.shape) #(25000, 100)
# print("y_train.shape:",y_train.shape) #(25000,)
# print("x_test.shape:",x_test.shape) #(25000, 100)
# print("y_test.shape:",y_test.shape) #(25000,)#分别将训练数据构成Dataset对象,这样就可以使用Dataset属性/方法对数据进行操作了,然后使用shuffle对数据进行打乱
dataset_train = tf.data.Dataset.from_tensor_slices((x_train, y_train)).shuffle(gConfig['shuffle_size'])
dataset_test = tf.data.Dataset.from_tensor_slices((x_test, y_test)).shuffle(gConfig['shuffle_size'])"""
使用 tf.train.CheckpointManager 删除旧的 Checkpoint 以及自定义文件编号
在模型的训练过程中,我们往往每隔一定步数保存一个 Checkpoint 并进行编号。
不过很多时候我们会有这样的需求:在长时间的训练后,程序会保存大量的 Checkpoint,但我们只想保留最后的几个 Checkpoint。Checkpoint 默认从 1 开始编号,每次累加 1,但我们可能希望使用别的编号方式(例如使用当前 Batch 的编号作为文件编号)。这时,我们可以使用 TensorFlow 的 tf.train.CheckpointManager 来实现以上需求。
具体而言,在定义 Checkpoint 后接着定义一个 CheckpointManager:checkpoint = tf.train.Checkpoint(model=model)manager = tf.train.CheckpointManager(checkpoint, directory='./save', checkpoint_name='model.ckpt', max_to_keep=k)此处, directory 参数为文件保存的路径, checkpoint_name 为文件名前缀(不提供则默认为 ckpt ), max_to_keep 为保留的 Checkpoint 数目。
在需要保存模型的时候,我们直接使用 manager.save() 即可。如果我们希望自行指定保存的 Checkpoint 的编号,
则可以在保存时加入 checkpoint_number 参数。例如 manager.save(checkpoint_number=100) 。
以下提供一个实例,展示使用 CheckpointManager 限制仅保留最后三个 Checkpoint 文件,并使用 batch 的编号作为 Checkpoint 的文件编号。manager = tf.train.CheckpointManager(checkpoint, directory='./save', max_to_keep=3)path = manager.save(checkpoint_number=batch_index)
"""
# 使用tf.train.CheckpointManager管理Checkpoint,用于保存和读取Checkpoint文件
ckpt_manager = tf.train.CheckpointManager(model.ckpt, checkpoint_path, max_to_keep=5)#模型构建函数:用于加载已经训练好的模型,或者直接使用导入的模型
def create_model():# 模型保存路径model_data/ckpt = tf.io.gfile.listdir(checkpoint_path)if ckpt:print("reload pretrained model")"""当在其他地方需要为模型重新载入之前保存的参数时,需要再次实例化一个 Checkpoint,同时保持键名的一致。再调用 checkpoint 的 restore 方法。就像下面这样:model_to_be_restored = MyModel()                                        # 待恢复参数的同一模型ckpt = tf.train.Checkpoint(myAwesomeModel=model_to_be_restored)   # 键名保持为“myAwesomeModel”ckpt.restore(save_path_with_prefix_and_index)即可恢复模型变量。 save_path_with_prefix_and_index 是之前保存的文件的目录 + 前缀 + 编号。例如,调用 checkpoint.restore('./save/model.ckpt-1') 就可以载入前缀为 model.ckpt ,序号为 1 的文件来恢复模型。当保存了多个文件时,我们往往想载入最近的一个。可以使用 tf.train.latest_checkpoint(save_path) 这个辅助函数返回目录下最近一次 checkpoint 的文件名。例如如果 save 目录下有 model.ckpt-1.index 到 model.ckpt-10.index 的 10 个保存文件, tf.train.latest_checkpoint('./save') 即返回 ./save/model.ckpt-10 。"""model.ckpt.restore(tf.train.latest_checkpoint(checkpoint_path))return modelelse:return model#训练模式
def train():# 模型构建函数:用于加载已经训练好的模型,或者直接使用导入的模型model=create_model()# while True:for epoch in range(gConfig['epochs']): #按照epoch值进行循环训练# start = time.time()model.train_loss.reset_states() #重置清零loss函数Meanmodel.train_accuracy.reset_states()#重置清零指标值accuracy#开始批量循环训练,每一步所训练的数据大小都是一个批量大小64#遍历的值batch从0开始for (batch,(inp, target)) in enumerate(dataset_train.batch(gConfig['batch_size'])):start = time.time()# print(inp.shape) #(batch_size, 100)# print(target.shape) #(batch_size,)#定义一个既能完成训练任务,也能完成预测任务,训练模式时返回的是一步step(一个batch批量大小)的loss值,预测模式时返回的是预测值loss, trainAccuracy = model.step(inp, target)print ('训练集:Epoch {:} ,Batch {:} ,Loss {:.4f}, Accuracy {:.4f},Prestep {:.4f}'.format(epoch + 1, batch, loss.numpy(), trainAccuracy.numpy(), (time.time()-start)))# 定义一个验证函数,使用测试集数据对训练好的模型进行预测验证#for (batch,(inp,target)) in enumerate(dataset_test.batch(gConfig['batch_size'])):#start = time.time()# loss = model.evaluate(inp,target)# print ('测试集:Epoch {:} ,Batch {:} ,Loss {:.4f},Prestep {:.4f}'.format(epoch + 1, batch,loss.numpy(),(time.time()-start)))# 使用CheckpointManager保存模型参数到文件并自定义编号  tf.train.CheckpointManager(...).save(checkpoint_number=batch_index)# ckpt_save_path=ckpt_manager.save()# print ('保存epoch{}模型在 {}'.format(epoch+1, ckpt_save_path))#把输入的一行句子中的每个单词 转换为 “单词在字典中对应的”value值(出现频率次数)
def text_to_vector(inp):#读取记录了每个单词和其对应的出现频率次数的本地文件vocabulary_file=gConfig['vocabulary_file']tmp_vocab = []with open(vocabulary_file, "r") as f:#读取字典文件的数据,生成一个dict,也就是键值对的字典#readlines() 一次性读取多行tmp_vocab.extend(f.readlines())# 遍历每行数据,strip()表示每行数据按照默认空格进行切割tmp_vocab = [line.strip() for line in tmp_vocab]#enumerate 遍历的是(y, x) 即为{1:the}、{2:a},所以还要翻转变成{the:1}、{a:2} 存储到 字典中#tmp_vocab列表中的数据实际是出现频率最高的前10000个单词,出现频率从高到小的单词按照列表索引0值开始排列,# 因此遍历出来的单词的出现频率是从高到小的。vocab = dict([(x, y) for (y, x) in enumerate(tmp_vocab)])print(vocab)line_vec = []#strip()表示每行数据按照默认空格进行切割出每个单词for words in inp.split():# 获取单词在字典中对应value值(出现频率次数),如果单词不存在字典中的话就默认返回UNK_ID = 3line_vec.append(vocab.get(words, UNK_ID))return line_vec#预测模式
def predict(sentences):#pos积极、neg消极state=['pos','neg']# 模型构建函数:用于加载已经训练好的模型,或者直接使用导入的模型model=create_model()#把输入的一行句子中的每个单词 转换为 “单词在字典中对应的”value值(出现频率次数)indexes = text_to_vector(sentences)print(indexes)# 定义paddding填充函数,对长度不足的语句使用0进行补全。# 把每行句子的长度要么填充到100,要么截取为100。inp = pad_sequences([indexes])#inp[0]获取出一个句子,然后转换为(1, 100)inp=tf.reshape(inp[0], (1, len(inp[0])))# 定义一个既能完成训练任务,也能完成预测任务,训练模式时返回的是一步step(一个batch批量大小)的loss值,预测模式时返回的是预测值# transformer返回输出经过softmax的shape为(1, 2)的概率值predictions = model.step(inp,inp,False)#predictions[0] 取出shape为(1, 2)中的(2)的概率值#argmax 获取最大值所在的索引值pred = tf.math.argmax(predictions[0])#tensor值转换为numpy值p=np.int32(pred.numpy())#获取是pos积极 还是 neg消极的 标签return state[p]if __name__ == "__main__":# 如果配置文件中配置的是训练模式,则开始训练if gConfig['mode']=='train':train()# 如果配置文件中配置的是服务模式,则直接运行web应用程序elif gConfig['mode']=='serve':print('Sever Usage:python3 app.py')================================================================================================================================textClassiferModel.py
# coding=utf-8
import tensorflow as tf
import numpy as np
import getConfig
tf.enable_eager_execution()gConfig={}
gConfig=getConfig.get_config(config_file='config.ini')#传入 shape为(10000, 1)的0到9999一共10000个数字的一个array、shape为(1, 80)的0到79一共80个数字的一个array、Embedding的长度80
def get_angles(pos, i, d_model):"""1.i//2:即 np.arange(80)[np.newaxis, :]//2,表示shape为(1, 80)的0到79一共80个数字的一个array 整除2,即array中每个数字 整除2,最终效果如下,每两个值均为相同,数值从0到79降到0到39。最终值:array([[ 0,  0,  1,  1,  2,  2,  3,  3,  4,  4,  5,  5,  6,  6,  7,  7,8,  8,  9,  9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15,16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23,24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, 31, 31,32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, 38, 39, 39]], dtype=int32)2.2 * (i//2):即2 * (np.arange(80)[np.newaxis, :]//2),最终效果如下,每两个值均为相同,数值从0到79降到0到39,然后再重新上升回0到78,但注意的是现在每个值均是偶数,均可以整除2。最终值:array([[ 0,  0,  2,  2,  4,  4,  6,  6,  8,  8, 10, 10, 12, 12, 14, 14,16, 16, 18, 18, 20, 20, 22, 22, 24, 24, 26, 26, 28, 28, 30, 30,32, 32, 34, 34, 36, 36, 38, 38, 40, 40, 42, 42, 44, 44, 46, 46,48, 48, 50, 50, 52, 52, 54, 54, 56, 56, 58, 58, 60, 60, 62, 62,64, 64, 66, 66, 68, 68, 70, 70, 72, 72, 74, 74, 76, 76, 78, 78]], dtype=int32)3.(2 * (i//2)) / np.float32(d_model):即 (2 * (np.arange(80)[np.newaxis, :]//2)) / np.float32(80)最终值:array([[0.   , 0.   , 0.025, 0.025, 0.05 , 0.05 , 0.075, 0.075, 0.1  ,0.1  , 0.125, 0.125, 0.15 , 0.15 , 0.175, 0.175, 0.2  , 0.2  ,0.225, 0.225, 0.25 , 0.25 , 0.275, 0.275, 0.3  , 0.3  , 0.325,0.325, 0.35 , 0.35 , 0.375, 0.375, 0.4  , 0.4  , 0.425, 0.425,0.45 , 0.45 , 0.475, 0.475, 0.5  , 0.5  , 0.525, 0.525, 0.55 ,0.55 , 0.575, 0.575, 0.6  , 0.6  , 0.625, 0.625, 0.65 , 0.65 ,0.675, 0.675, 0.7  , 0.7  , 0.725, 0.725, 0.75 , 0.75 , 0.775,0.775, 0.8  , 0.8  , 0.825, 0.825, 0.85 , 0.85 , 0.875, 0.875,0.9  , 0.9  , 0.925, 0.925, 0.95 , 0.95 , 0.975, 0.975]])4.pos / np.power(10000, (2 * (i//2)) / np.float32(d_model))即 np.arange(10000)[:, np.newaxis] / np.power(10000, (2 * (np.arange(80)[np.newaxis, :]//2)) / np.float32(80))最终值的shape为 (10000, 80)最终值:array([[0.00000000e+00, 0.00000000e+00, 0.00000000e+00, ...,0.00000000e+00, 0.00000000e+00, 0.00000000e+00],[1.00000000e+00, 1.00000000e+00, 7.94328235e-01, ...,1.58489319e-04, 1.25892541e-04, 1.25892541e-04],[2.00000000e+00, 2.00000000e+00, 1.58865647e+00, ...,3.16978638e-04, 2.51785082e-04, 2.51785082e-04],...,[9.99700000e+03, 9.99700000e+03, 7.94089936e+03, ...,1.58441772e+00, 1.25854773e+00, 1.25854773e+00],[9.99800000e+03, 9.99800000e+03, 7.94169369e+03, ...,1.58457621e+00, 1.25867363e+00, 1.25867363e+00],[9.99900000e+03, 9.99900000e+03, 7.94248802e+03, ...,1.58473470e+00, 1.25879952e+00, 1.25879952e+00]])"""angle_rates = 1 / np.power(10000, (2 * (i//2)) / np.float32(d_model))return pos * angle_rates #shape为 (10000, 80)"""
在Transformer结构中使用了位置编码(Positional Encoding)来提取各个词的位置信息,并作为Encoder或Decoder的输入。
Transformer位置编码的实现方式是:通过正弦函数sin、余弦函数cos 交替编码“未来用于提取每个单词信息的”shape为(字典的大小10000,Embedding的长度80)的矩阵,正弦函数sin负责对矩阵中每行上索引值为偶数(即偶数列,从0开始算)上的元素值进行编码,余弦函数cos负责对矩阵中每行上索引值为奇数(即奇数列,从0开始算)上的元素值进行编码,然后把该编码好的shape为(字典的大小10000,Embedding的长度80)的矩阵 拓增维度为 (1,字典的大小10000,Embedding的长度80),然后再仅截取出其所编码好的矩阵中的(1,句子的最大长度100,Embedding的长度80)这一小部分,最后用截取出的这一部分位置编码信息和“shape为(batch_size,句子的最大长度100,Embedding的长度80)的”单词Embedding输出信息两者进行相加,最终求和的值作为Encoder或Decoder的输入。
"""
#定义一个函数对位置编码信息进行处理,以便能够使用正弦函数和余弦函数对位置编码信息进行编码
#位置编码:传入 字典的大小10000、Embedding的长度80
def positional_encoding(position, d_model):#np.arange(10000):表示从0遍历到9999,一共10000个数字组成一个array。np.arange(80):表示从0遍历到79,一共80个数字组成一个array。#np.newaxis:表示增加新一个维度,维度值为1#angle_rads.shape 为 (10000, 80)angle_rads = get_angles(np.arange(position)[:, np.newaxis], #(10000,) 然后拓增一个维度为 (10000, 1)np.arange(d_model)[np.newaxis, :], #(80,) 然后拓增一个维度为 (1, 80)d_model) #Embedding的长度80print("positional_encoding angle_rads:",angle_rads.shape) #(10000, 80)"""a=[1,2,3,4,5,6,7,8,9]#第一个值1表示从索引值1开始,第二个值2表示每相隔1个地获取,实际即只获取索引值为奇数上的元素值>>> print(a[1::2])[2, 4, 6, 8]#第一个值0表示从索引值0开始,第二个值2表示每相隔1个地获取,实际即只获取索引值为偶数上的元素值>>> print(a[0::2])[1, 3, 5, 7, 9]#翻转数组中元素的排列顺序,即从尾到头重新排列数组元素>>> print(a[::-1])[9, 8, 7, 6, 5, 4, 3, 2, 1]"""#正弦函数y = sin(x):shape为(10000, 40),实际即只获取每行上索引值为偶数(即偶数列,从0开始算)上的元素值sines = np.sin(angle_rads[:, 0::2])#余弦函数y = cos(x):shape为(10000, 40),实际即只获取每行上索引值为奇数(即奇数列,从0开始算)上的元素值cosines = np.cos(angle_rads[:, 1::2])print("positional_encoding sines:",sines.shape) #(10000, 40)print("positional_encoding cosines:",cosines.shape) #(10000, 40)#对shape为(10000, 40)的sin值 和 shape为(10000, 40)的cos值 在最后一个维度进行合并,结果值shape为(10000, 80)pos_encoding = np.concatenate([sines, cosines], axis=-1)print("positional_encoding pos_encoding:",pos_encoding.shape) #(10000, 80)#np.newaxis:表示增加新一个维度,维度值为1#shape 从 (10000, 80) 然后拓增一个维度为 (1, 10000, 80)pos_encoding = pos_encoding[np.newaxis, ...]print("positional_encoding pos_encoding:",pos_encoding.shape) #(1, 10000, 80)#对位置编码进行类型转换为float32再返回return tf.cast(pos_encoding, dtype=tf.float32)"""
1.Q与K的相似度计算过程是这样的:首先使用MatMul函数计算Q和K的相似度(MatMul是一种点积函数)。为了能够更好地控制计算的复杂度,使用Scale函数对MatMul的计算结果进行缩放。
2.每一次放缩点积注意力的计算结果就是一个头注意力,那么计算多次就是多头注意力。在每次计算时Q、K、V使用不同的参数进行线性变换,这样虽然进行了多次放缩点积注意力的计算,但每次计算的结果是不同的。对输入数据进行不同的线性变换操作是特征增强的一种手段,因为至少从理论上增加了有效特征,可以提高神经网络模型的预测效果。
3.放缩点积注意力的计算过程大概是这样的:首先计算每个Q与K矩阵的相似度,然后使用softmax对这个相似度向量进行归一化处理得到权重向量,最后将这个权重向量与V矩阵加权求和得到最终的attention值。
"""
#定义放缩点积注意力计算函数
# 传入的 v、k、q 三者均是“shape为(batch_size, 多头注意力的多头的头数量8, 句子的最大长度100, 10)的融合了位置编码信息的”单词Embedding输出信息x、
#  (batch_size, 1, 1, 100)的mask掩码,最终返回 放缩点积注意力输出output、注意力权重attention_weights
def scaled_dot_product_attention(q, k, v, mask):""":param q: 维度为 (batch_size, seq_len_q, depth):param k: 维度为 (batch_size, seq_len_k, depth):param v: 维度为 (batch_size, seq_len_v, depth):param mask: 维度为 (batch_size, 1, 1, 100)"""# transpose_a: 如果为真, a则在进行乘法计算前进行转置。 transpose_b: 如果为真, b则在进行乘法计算前进行转置。#使用MatMul点积相乘函数计算Q和K的相似度matmul_qk = tf.matmul(q, k, transpose_b=True)  # (..., seq_len_q, seq_len_k)#(batch_size, 多头注意力的多头的头数量8, 句子的最大长度100, 句子的最大长度100) 即 (batch_size, 多头注意力的多头的头数量8, seq_len_q, seq_len_k)print("scaled_dot_product_attention matmul_qk:",matmul_qk.shape) #(64, 8, 100, 100) 即 (batch_size, 多头注意力的多头的头数量8, 句子的最大长度100, 句子的最大长度100)#shape(k)[-1] 实际取出了(batch_size, 多头注意力的多头的头数量8, 句子的最大长度100, 10)中的最后一个维度10,作为缩放matmul_qk张量的因子(即作为缩放因子)dk = tf.cast(tf.shape(k)[-1], tf.float32)# print("scaled_dot_product_attention dk:",dk.numpy()) #10.0#1.sqrt(10) = 3.1622776601683795,即求10的平方根#2.“Q和K两个矩阵的点积计算的结果”即相似度向量matmul_qk / 3.1622776601683795:#     为了能够更好地控制计算的复杂度,使用Scale函数对MatMul的计算结果进行缩放。#     相似度向量matmul_qk 除以 3.1622776601683795 相当于使用Scale函数的效果 对 MatMul的计算结果的相似度向量matmul_qk 进行了缩放。#3.通过matmul_qk张量除以“缩放因子dk的”平方根,实现对matmul_qk张量的缩放操作。scaled_attention_logits = matmul_qk / tf.math.sqrt(dk)# (batch_size, 多头注意力的多头的头数量8, 句子的最大长度100, 句子的最大长度100)print("scaled_dot_product_attention scaled_attention_logits:",scaled_attention_logits.shape) #(64, 8, 100, 100)if mask is not None:"""1.mask的 shape为 (batch_size, 1, 1, 100)每个句子中 为填充值0 的equal之后都返回True,然后cast转换为1.0的mask掩码,最终计算 1.0 * -1e9 = -1000000000.0每个句子中 为非填充值0 的equal之后都返回False,然后cast转换为0.0的mask掩码,最终计算 0.0 * -1e9 = -0.02.对应 填充值0 的为 scaled_attention_logits - 1000000000.0,即抛弃对应 填充值0 的值对应 非填充值0 的为 scaled_attention_logits - 0.0,即值保持不变"""#因为scaled_attention_logits实质还是“Q和K两个矩阵的点积计算的结果”即相似度向量,而Q和K实质都是融合了位置编码信息的单词Embedding输出信息,#因此需要使用到mask掩码对源输入句子中的填充值0进行去除,最终对应填充值0位置上的Embedding值都被置为了极大的负数(约等于-1000000000.0)scaled_attention_logits += (mask * -1e9)print("scaled_dot_product_attention mask:", scaled_attention_logits.shape) #(64, 8, 100, 100) 即 (batch_size, 多头注意力的多头的头数量8, 句子的最大长度100, 句子的最大长度100)#使用softmax对经过缩放后的相似度向量scaled_attention_logits 进行归一化处理 得到 注意力权重attention_weightsattention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1)  # (..., seq_len_q, seq_len_k)print("scaled_dot_product_attention attention_weights:", attention_weights.shape)#(64, 8, 100, 100) 即 (batch_size, 多头注意力的多头的头数量8, 句子的最大长度100, 句子的最大长度100)#使用 注意力权重attention_weights 最后和 V矩阵(“shape为(batch_size, 多头注意力的多头的头数量8, 句子的最大长度100, 10)的融合了位置编码信息的”# 单词Embedding输出信息) 两者进行MatMul点积运算 得到最终的 放缩点积注意力输出outputoutput = tf.matmul(attention_weights, v)  # (..., seq_len_v, depth)print("scaled_dot_product_attention output:", output.shape) #(64, 8, 100, 10) 即 (batch_size, 多头注意力的多头的头数量8, 句子的最大长度100, 10)#返回 放缩点积注意力输出output、注意力权重attention_weightsreturn output, attention_weights#定义一个MultiHeadAttention类:实现多头注意力机制
class MultiHeadAttention(tf.keras.layers.Layer):# 初始化多头注意力机制的头的头数量和Embedding长度:传入 Embedding的长度80、多头注意力的多头的头数量8def __init__(self, d_model, num_heads):super(MultiHeadAttention, self).__init__()self.d_model = d_model #Embedding的长度80self.num_heads = num_heads #多头注意力的多头的头数量8#assert作用:断定 多头注意力的多头的头数量 能够被 Embedding长度 进行整除,如果不能整除,则不会再执行后面代码#判断 Embedding的长度80 % 多头注意力的多头的头数量8 == 0assert d_model % self.num_heads == 0#depth 为网络深度#Embedding的长度80 // 多头注意力的多头的头数量8 = 10,此处为整除运算self.depth = d_model // self.num_headsprint("MultiHeadAttention depth:",self.depth) #10#下面wq、wk、wv分别为Dense全连接层,神经元数量均为 Embedding的长度80,分别用于对q、k、v进行线性变换self.wq = tf.keras.layers.Dense(d_model)self.wk = tf.keras.layers.Dense(d_model)self.wv = tf.keras.layers.Dense(d_model)self.dense = tf.keras.layers.Dense(d_model)#传入x 实际可以分别为v、k、q,三者均是“shape为(batch_size, 句子的最大长度100, Embedding的长度80)的融合了位置编码信息的”单词Embedding输出信息xdef split_heads(self, x, batch_size):#实际是把 原本第三维度的Embedding的长度80 分割为 多头注意力的多头的头数量8 * 10,8替换到第三维度,10作为新的第四维度#最终v、k、q,三者实际转换成 (batch_size, 句子的最大长度100, 多头注意力的多头的头数量8, 10)。x = tf.reshape(x, (batch_size, -1, self.num_heads, self.depth))#transpose:把(batch_size, 句子的最大长度100, 多头注意力的多头的头数量8, 10) 转换为 (batch_size, 多头注意力的多头的头数量8, 句子的最大长度100, 10)return tf.transpose(x, perm=[0, 2, 1, 3])#传入 融合了位置编码信息的单词Embedding输出信息x、融合了位置编码信息的单词Embedding输出信息x、#    融合了位置编码信息的单词Embedding输出信息x、(batch_size, 1, 1, 100)的mask掩码。#实际即 v、k、q 三者均是 融合了位置编码信息的单词Embedding输出信息xdef call(self, v, k, q, mask):#q的shape为 (batch_size, 句子的最大长度100, Embedding的长度80),shape(q)[0] 实则为 batch_sizebatch_size = tf.shape(q)[0]# print("MultiHeadAttention batch_size:",batch_size.numpy()) # batch_size 64# 下面wq、wk、wv分别为Dense全连接层,神经元数量均为 Embedding的长度80,分别用于对q、k、v进行线性变换# 实际即 v、k、q 三者均是“shape为(batch_size, 句子的最大长度100, Embedding的长度80)的融合了位置编码信息的”单词Embedding输出信息xq = self.wq(q)  # (batch_size, seq_len, d_model)k = self.wk(k)  # (batch_size, seq_len, d_model)v = self.wv(v)  # (batch_size, seq_len, d_model)print("MultiHeadAttention q:",q.shape) #(64, 100, 80) 即 (batch_size, 句子的最大长度100, Embedding的长度80)print("MultiHeadAttention k:",k.shape) #(64, 100, 80)print("MultiHeadAttention v:",v.shape) #(64, 100, 80)#传入的v、k、q 三者均是“shape为(batch_size, 句子的最大长度100, Embedding的长度80)的融合了位置编码信息的”单词Embedding输出信息x,#最终函数返回输出的v、k、q的shape 都从原来的(batch_size, 句子的最大长度100, Embedding的长度80) 均转换为了# (batch_size, 多头注意力的多头的头数量8, 句子的最大长度100, 10)。q = self.split_heads(q, batch_size)  # (batch_size, num_heads, seq_len_q, depth)k = self.split_heads(k, batch_size)  # (batch_size, num_heads, seq_len_k, depth)v = self.split_heads(v, batch_size)  # (batch_size, num_heads, seq_len_v, depth)print("MultiHeadAttention split_heads q:",q.shape) #(64, 8, 100, 10) 即 (batch_size, 多头注意力的多头的头数量8, 句子的最大长度100, 10)print("MultiHeadAttention split_heads k:",k.shape) #(64, 8, 100, 10)print("MultiHeadAttention split_heads v:",v.shape) #(64, 8, 100, 10)"""1.多头注意力是由多个Scaled Dot-Product Attention(放缩点积注意力)堆叠而得到的。放缩点积注意力:点积是我们常用的计算相似度的方法之一,放缩指内积的大小是可控的。与常见的注意力机制相比,放缩点积注意力机制主要是在相似计算和内积调节控制方面进行了改进。2.多头注意力又是怎么来的呢?这个其实很好理解,每一次放缩点积注意力的计算结果就是一个头注意力,那么计算多次就是多头注意力。在每次计算时Q、K、V使用不同的参数进行线性变换,这样虽然进行了多次放缩点积注意力的计算,但每次计算的结果是不同的。对输入数据进行不同的线性变换操作是特征增强的一种手段,因为至少从理论上增加了有效特征,可以提高神经网络模型的预测效果。3.放缩点积注意力的计算过程大概是这样的:首先计算每个Q与K矩阵的相似度,然后使用softmax对这个相似度向量进行归一化处理得到权重向量,最后将这个权重向量与V矩阵加权求和得到最终的attention值。"""#调用 放缩点积注意力计算函数 来计算q、k、v的 放缩点积注意力输出 和 注意力权重# 传入的 v、k、q 三者均是“shape为(batch_size, 多头注意力的多头的头数量8, 句子的最大长度100, 10)的融合了位置编码信息的”单词Embedding输出信息x、#  (batch_size, 1, 1, 100)的mask掩码。#最终返回 放缩点积注意力输出scaled_attention、注意力权重attention_weightsscaled_attention, attention_weights = scaled_dot_product_attention(q, k, v,mask)#把 (batch_size, 多头注意力的多头的头数量8, 句子的最大长度100, 10) 转换为 (batch_size, 句子的最大长度100, 多头注意力的多头的头数量8, 10)scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3])  # (batch_size, seq_len_v, num_heads, depth)print("MultiHeadAttention scaled_attention:",scaled_attention.shape) #(64, 100, 8, 10) 即 (batch_size, 句子的最大长度100, 多头注意力的多头的头数量8, 10)#把 (batch_size, 句子的最大长度100, 多头注意力的多头的头数量8, 10) 转换为 (batch_size, 句子的最大长度100, Embedding的长度80)concat_attention = tf.reshape(scaled_attention, (batch_size, -1, self.d_model))  # (batch_size, seq_len_v, d_model)print("MultiHeadAttention concat_attention:",concat_attention.shape) #(64, 100, 80) 即 (batch_size, 句子的最大长度100, Embedding的长度80)#最后一次线性变换得到最终的多头注意力输出#dense全连接层的神经元数量均为 Embedding的长度80,所以dense全连接层的输出的shape仍然为 (batch_size, 句子的最大长度100, Embedding的长度80)output = self.dense(concat_attention)  # (batch_size, seq_len_v, d_model)print("MultiHeadAttention output:",output.shape) #(64, 100, 80) 即 (batch_size, 句子的最大长度100, Embedding的长度80)#返回 shape为(batch_size, 句子的最大长度100, Embedding的长度80)的多头注意力输出、注意力权重attention_weightsreturn output, attention_weights# 点式前馈网络Feed Forward:
#   有两个Dense全连接层组成,在Transformer结构中,Feed Forward是前馈神经网络层,
#   其作用是将Multi-head Attention(多头注意力)层输出的数据进行非线性变换后输出。
#传入 Embedding的长度80、点式前馈网络Feed Forward中第一个Dense全连接层的神经元数量1024
def point_wise_feed_forward_network(d_model, dff):return tf.keras.Sequential([tf.keras.layers.Dense(dff, activation='relu'), #神经元数量为1024 (batch_size, 句子的最大长度100, 神经元数量1024)tf.keras.layers.Dense(d_model) #神经元数量为 Embedding的长度80 (batch_size, 句子的最大长度100, 神经元数量为80)
])#定义EncoderLayer神经网络层
class EncoderLayer(tf.keras.layers.Layer):# 初始化EncoderLayer:传入 Embedding的长度80、点式前馈网络Feed Forward中第一个Dense全连接层的神经元数量1024、多头注意力的多头的头数量8、神经元失效的比例概率0.1def __init__(self, d_model, diff, num_heads, rate=0.1):super(EncoderLayer, self).__init__()#初始化多头注意力机制:传入Embedding的长度80、多头注意力的多头的头数量8self.mha = MultiHeadAttention(d_model, num_heads)# 初始化 点式前馈网络(point wise Feed Forward network):#   有两个Dense全连接层组成,在Transformer结构中,Feed Forward是前馈神经网络层,#   其作用是将Multi-head Attention(多头注意力)层输出的数据进行非线性变换后输出。#传入 Embedding的长度80、点式前馈网络Feed Forward中第一个Dense全连接层的神经元数量1024self.ffn = point_wise_feed_forward_network(d_model, diff)#Dropout层:神经元失效的比例概率0.1self.dropout1 = tf.keras.layers.Dropout(rate)self.dropout2 = tf.keras.layers.Dropout(rate)#初始化Normalization(归一化)层:进行Normalization(归一化)之后,数据分布更加集中,神经网络可以更快地找到最优解#epsilon:该参数是一个非常小的数值,防止出现除以零的情况。1e-6是科学计数法,就是1x10的-6次方就是0.000001。self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)# 传入 “shape为(batch_size, 句子的最大长度100, Embedding的长度80)的融合了位置编码信息的”单词Embedding输出信息x、#       True、(batch_size, 1, 1, 100)的mask掩码def call(self, x, training, mask):#1.调用MultiHeadAttention的call函数:#   传入 融合了位置编码信息的单词Embedding输出信息x、融合了位置编码信息的单词Embedding输出信息x、#        融合了位置编码信息的单词Embedding输出信息x、(batch_size, 1, 1, 100)的mask掩码#2.最终MultiHeadAttention的call函数 返回 “shape为(batch_size, 句子的最大长度100, Embedding的长度80)的”多头注意力输出、注意力权重attention_weightsattn_output, _ = self.mha(x, x, x, mask)  # (batch_size, input_seq_len, d_model)# Dropout层:神经元失效的比例概率0.1。实现对神经网络参数的正则化,防止过拟合。attn_output = self.dropout1(attn_output, training=training)#1.Normalization层:进行Normalization(归一化)之后,数据分布更加集中,神经网络可以更快地找到最优解#2.对“shape为(batch_size, 句子的最大长度100, Embedding的长度80)的融合了位置编码信息的”单词Embedding输出信息x 和# “shape为(batch_size, 句子的最大长度100, Embedding的长度80)的”多头注意力输出attn_output 两者进行求和,#  然后把求和的值再经过Normalization层(归一化)。#3.融合了位置编码信息的单词Embedding输出信息x 和 多头注意力输出attn_output 进行相加操作 目的是 用于加强特征out1 = self.layernorm1 (x + attn_output)  # (batch_size, input_seq_len, d_model)print("EncoderLayer out1:",out1.shape) # (64, 100, 80) 即 (batch_size, 句子的最大长度100, Embedding的长度80)#1.点式前馈网络(point wise Feed Forward network):#   有两个Dense全连接层组成,在Transformer结构中,Feed Forward是前馈神经网络层,#   其作用是将Multi-head Attention(多头注意力)层输出的数据进行非线性变换后输出。#2.前馈网络中的输出层的神经元数量为Embedding的长度80,那么终于输出数据的shape仍然为(batch_size, 句子的最大长度100, Embedding的长度80)ffn_output = self.ffn(out1)  # (batch_size, input_seq_len, d_model)print("EncoderLayer ffn_output:",ffn_output.shape) # (64, 100, 80) 即 (batch_size, 句子的最大长度100, Embedding的长度80)# Dropout层:神经元失效的比例概率0.1。实现对神经网络参数的正则化,防止过拟合。ffn_output = self.dropout2(ffn_output, training=training)#把已经经过了Normalization层(归一化)的多头注意力输出out1 和 前馈网络输出ffn_output 进行求和之后,再经过Normalization层(归一化)#前馈网络的输入out1 和 前馈网络的输出ffn_output 进行相加操作 目的是 用于加强特征out2 = self.layernorm2 (out1 + ffn_output)  # (batch_size, input_seq_len, d_model)print("EncoderLayer out2:",out2.shape) # (64, 100, 80) 即 (batch_size, 句子的最大长度100, Embedding的长度80)return out2#定义的Encoder编码器是Layer类型
class Encoder(tf.keras.layers.Layer):# 初始化编码器:#   传入 EncoderLayer层数4、Embedding的长度80、点式前馈网络Feed Forward中第一个Dense全连接层的神经元数量1024、多头注意力的多头的头数量8、#   字典的大小10000、神经元失效的比例概率0.1。def __init__(self, num_layers, d_model, dff, num_heads, input_vocab_size, rate=0.1):super(Encoder, self).__init__()self.num_layers = num_layers #EncoderLayer层数4self.d_model = d_model #Embedding的长度80#创建Embedding层:传入 字典的大小10000、Embedding的长度80self.embedding = tf.keras.layers.Embedding(input_vocab_size, d_model)"""在Transformer结构中使用了位置编码(Positional Encoding)来提取各个词的位置信息,并作为Encoder或Decoder的输入。Transformer位置编码的实现方式是:通过正弦函数sin、余弦函数cos 交替编码“未来用于提取每个单词信息的”shape为(字典的大小10000,Embedding的长度80)的矩阵,正弦函数sin负责对矩阵中每行上索引值为偶数(即偶数列,从0开始算)上的元素值进行编码,余弦函数cos负责对矩阵中每行上索引值为奇数(即奇数列,从0开始算)上的元素值进行编码,然后把该编码好的shape为(字典的大小10000,Embedding的长度80)的矩阵 拓增维度为 (1,字典的大小10000,Embedding的长度80),然后再仅截取出其所编码好的矩阵中的(1,句子的最大长度100,Embedding的长度80)这一小部分,最后用截取出的这一部分位置编码信息和“shape为(batch_size,句子的最大长度100,Embedding的长度80)的”单词Embedding输出信息两者进行相加,最终求和的值作为Encoder或Decoder的输入。"""#位置编码:传入 字典的大小10000、Embedding的长度80,最后返回的pos_encoding位置编码的shape为(1, 10000, 80)self.pos_encoding = positional_encoding(input_vocab_size, self.d_model)print("Encoder pos_encoding:",self.pos_encoding.shape) #(1, 10000, 80)#num_layers=4:表示EncoderLayer层数为4,即Encoder编码器有4层#初始化EncoderLayer:传入 Embedding的长度80、点式前馈网络Feed Forward中第一个Dense全连接层的神经元数量1024、多头注意力的多头的头数量8、神经元失效的比例概率0.1self.enc_layers = [EncoderLayer(d_model, dff, num_heads, rate)for _ in range(num_layers)]# Dropout层:神经元失效的比例概率0.1self.dropout = tf.keras.layers.Dropout(rate)#传入 (batch_size, 100)的特征数据、True、(batch_size, 1, 1, 100)的mask掩码def call(self, x, training, mask):#对 (batch_size, 100)的特征数据 获取出seq_len(句子的最大长度)为100seq_len = tf.shape(x)[1]# print("Encoder seq_len:",seq_len.numpy()) #100# 把 每个单词的embedding值x 和 (Positional Encoding)位置编码值pos_encoding 两者进行求和x = self.embedding(x)  # (batch_size, 句子的最大长度100, Embedding的长度80)print("Encoder embedding(x):",x.shape) #(64, 100, 80) 即 (batch_size, 句子的最大长度100, Embedding的长度80)#sqrt求平方根:求Embedding的长度80的平方根为8.94427190999916# 每个单词的embedding值x *= 8.94427190999916x *= tf.math.sqrt(tf.cast(self.d_model, tf.float32))print("Encoder sqrt:",x.shape) # (64, 100, 80) 即 (batch_size, 句子的最大长度100, Embedding的长度80)"""将shape为(1,句子的最大长度100,Embedding的长度80)的位置编码信息 和“shape为(batch_size,句子的最大长度100,Embedding的长度80)的”单词Embedding输出信息两者进行相加,最终求和的值作为Encoder或Decoder的输入。"""#pos_encoding[:, :seq_len, :]:从shape为(1, 10000, 80)的位置编码值pos_encoding 按照[:, :100, :]取出 (1, 句子的最大长度100, 80)x += self.pos_encoding[:, :seq_len, :]print("Encoder pos_encoding:",x.shape) # (64, 100, 80) 即 (batch_size, 句子的最大长度100, Embedding的长度80)# Dropout层:神经元失效的比例概率0.1x = self.dropout(x, training=training)#num_layers=4:表示EncoderLayer层数为4,即Encoder编码器有4层for i in range(self.num_layers):"""1.Encoder编码器 此处由4层EncoderLayer组成,每层EncoderLayer由多头注意力机制和Feed Forward前馈神经网络层组成。2.多头注意力机制的组成:v、k、q分别均是融合了位置编码信息的单词Embedding输出信息,v、k、q三者之间进行多次的放缩点积注意力计算(即MatMul点积运算),然后再对计算结果进行缩放(为了能够更好地控制计算的复杂度)。3.Feed Forward前馈神经网络层:作用是将(Multi-head Attention)多头注意力输出的数据进行非线性变换后再输出,最终把该输出作为EncoderLayer层的输出。4.Encoder编码器把4层EncoderLayer构建成一个相当于Sequential顺序的结构,第一层EncoderLayer对“融合了位置编码信息的”单词Embedding输出信息进行处理后输出,第一层EncoderLayer的输出作为第二层EncoderLayer的输入,第二层EncoderLayer的输出作为第三层EncoderLayer的输入,以此类推。"""#调用EncoderLayer的call函数:#   传入 “shape为(batch_size, 句子的最大长度100, Embedding的长度80)的融合了位置编码信息的”单词Embedding输出信息、#        True、(batch_size, 1, 1, 100)的mask掩码x = self.enc_layers[i](x, training, mask)print("Encoder x:", x.shape)  # (64, 100, 80) 即 (batch_size, 句子的最大长度100, Embedding的长度80)return x  # (batch_size, input_seq_len, d_model)#定义的Transformer是Model类型,此处定义的Transformer类实际是改造过的特别版的Transformer,因为此处只有Encoder编码器,而没有对应的Decoder解码器,
#并且当前Transformer模型处理的是分类任务,所以我们此处只用了Encoder编码器来提取特征,最后通过全连接层网络来拟合分类。
class Transformer(tf.keras.Model):# 初始化Transformer类:#   传入EncoderLayer层数4、Embedding的长度80、点式前馈网络Feed Forward中第一个Dense全连接层的神经元数量1024、多头注意力的多头的头数量8、#   字典的大小10000、神经元失效的比例概率0.1。def __init__(self, num_layers, d_model, dff, num_heads, input_vocab_size, rate=0.1):super(Transformer, self).__init__()#初始化编码器:Encoder编码器用来提取特征#   传入EncoderLayer层数4、Embedding的长度80、点式前馈网络Feed Forward中第一个Dense全连接层的神经元数量1024、多头注意力的多头的头数量8、#   字典的大小10000、神经元失效的比例概率0.1。self.encoder = Encoder(num_layers, d_model, dff, num_heads, input_vocab_size, rate)#输出层:Dense全连接层作为分类层,使用softmax把输出值转换为概率值self.ffn_out=tf.keras.layers.Dense(2, activation='softmax')#Dropout层:神经元失效的比例概率0.1self.dropout1 = tf.keras.layers.Dropout(rate)# 调用Transformer的call函数:传入(batch_size, 100)的特征数据、True、(batch_size, 1, 1, 100)的mask掩码def call(self, inp, training, enc_padding_mask):"""1.Encoder编码器 此处由4层EncoderLayer组成,每层EncoderLayer由多头注意力机制和Feed Forward前馈神经网络层组成。2.多头注意力机制的组成:v、k、q分别均是融合了位置编码信息的单词Embedding输出信息,v、k、q三者之间进行多次的放缩点积注意力计算(即MatMul点积运算),然后再对计算结果进行缩放(为了能够更好地控制计算的复杂度)。3.Feed Forward前馈神经网络层:作用是将(Multi-head Attention)多头注意力输出的数据进行非线性变换后再输出,最终把该输出作为EncoderLayer层的输出。4.Encoder编码器把4层EncoderLayer构建成一个相当于Sequential顺序的结构,第一层EncoderLayer对“融合了位置编码信息的”单词Embedding输出信息进行处理后输出,第一层EncoderLayer的输出作为第二层EncoderLayer的输入,第二层EncoderLayer的输出作为第三层EncoderLayer的输入,以此类推。"""#对输入语句使用Encoder编码器来提取特征,调用Encoder的call函数:传入 (batch_size, 100)的特征数据、True、(batch_size, 1, 1, 100)的mask掩码#返回Encoder编码器提取好出来的特征值enc_output = self.encoder(inp, training, enc_padding_mask)  # (batch_size, inp_seq_len, d_model)print("Transformer enc_output:", enc_output.shape)  # (batch_size, 句子的最大长度100, Embedding的长度80)#设定输出shape:句子的最大长度100 * Embedding的长度80out_shape=gConfig['sentence_size'] * gConfig['embedding_size']#把 (batch_size, 句子的最大长度100, Embedding的长度80) 转换为 (batch_size, 句子的最大长度100 * Embedding的长度80)#即 (batch_size, 800)enc_output=tf.reshape(enc_output,[-1, out_shape])print("Transformer enc_output reshape:", enc_output.shape)  #(batch_size, 句子的最大长度100 * Embedding的长度80) 即 (batch_size, 800)# Dropout层:神经元失效的比例概率0.1ffn = self.dropout1(enc_output,training=training)#当前Transformer模型处理的是分类任务,所以使用softmax,最后通过全连接层网络来拟合分类#输出层:Dense全连接层作为分类层,使用softmax把输出值转换为概率值ffn_out = self.ffn_out(ffn)print("Transformer ffn_out:", ffn_out.shape)  #(batch_size, 2)return ffn_out#LearningRateSchedule自适应的学习率:可以按照 epoch次数/自定义step步数 自动调整学习率
#定义一个学习率自动规划类,实现根据不同的训练集和训练速度进行自动设置学习率
class CustomSchedule(tf.keras.optimizers.schedules.LearningRateSchedule):#传入 Embedding的长度80,还定义有默认的warmup_steps预热学习步数40def __init__(self, d_model, warmup_steps=40):super(CustomSchedule, self).__init__()self.d_model = d_model#Embedding的长度80self.d_model = tf.cast(self.d_model, tf.float32)self.warmup_steps = warmup_steps #默认的warmup_steps预热学习步数40#取 arg1和arg2 两者其中一个最小值 作为 学习率计算的因子之一,#最终把“Embedding长度80的平方根的倒数 乘以 arg1和arg2两者其中一个最小值的”乘积结果 作为 学习率。#arg1为训练步数的平方根的倒数,arg2为训练步数乘以warmup_steps预热学习步数40的-1.5次方def __call__(self, step):#rsqrt:#   用于计算传入值x的平方根的倒数,rsqrt等同于 y = 1/sqrt(x),rsqrt(4.0) == 1/sqrt(4.0) == 0.5#   传入值x必须为以下类型的张量:bfloat16,half,float32,float64,complex64,complex128arg1 = tf.math.rsqrt(step)#warmup_steps ** -1.5:40 ** -5 = 9.765625e-09arg2 = step * (self.warmup_steps ** -1.5)#tf.math.rsqrt(80.0)的值为0.1118034#tf.math.minimum:获取两个数值中最小的一个# 取 arg1和arg2 两者其中一个最小值 作为 学习率计算的因子之一,# 最终把“Embedding长度80的平方根的倒数 乘以 arg1和arg2两者其中一个最小值的”乘积结果 作为 学习率return tf.math.rsqrt(self.d_model) * tf.math.minimum(arg1, arg2)#实例化一个学习率自动规划函数:传入 Embedding的长度80,返回自动规划好的学习率
learning_rate = CustomSchedule(gConfig['embedding_size'])
#Adam优化器
optimizer = tf.keras.optimizers.Adam(learning_rate)
#temp_learning_rate_schedule = CustomSchedule(gConfig['embedding_size'])#此处的Mean用于对传入的批量的loss值求一个平均值,相当于tf.reduce_mean(loss)的作用
train_loss = tf.keras.metrics.Mean(name='train_loss')
#准确率的评估方法
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')#初始化Transformer类:
#   传入EncoderLayer层数4、Embedding的长度80、点式前馈网络Feed Forward中第一个Dense全连接层的神经元数量1024、多头注意力的多头的头数量8、
#   字典的大小10000、神经元失效的比例概率0.1。
transformer = Transformer(gConfig['num_layers'], gConfig['embedding_size'], gConfig['diff'], gConfig['num_heads'],gConfig['vocabulary_size'], gConfig['dropout_rate'])"""
tf.train.Checkpoint 变量的保存与恢复1.Checkpoint 只保存模型的参数,不保存模型的计算过程,因此一般用于在具有模型源代码的时候恢复之前训练好的模型参数。如果需要导出模型(无需源代码也能运行模型).2.TensorFlow 提供了 tf.train.Checkpoint 这一强大的变量保存与恢复类,可以使用其 save() 和 restore() 方法,将 TensorFlow 中所有包含 Checkpointable State 的对象进行保存和恢复。具体而言,tf.keras.optimizer 、 tf.Variable 、 tf.keras.Layer 或者 tf.keras.Model 实例都可以被保存。其使用方法非常简单,我们首先声明一个 Checkpoint:checkpoint = tf.train.Checkpoint(model=model)3.这里 tf.train.Checkpoint() 接受的初始化参数比较特殊,是一个 **kwargs 。具体而言,是一系列的键值对,键名可以随意取,值为需要保存的对象。例如,如果我们希望保存一个继承 tf.keras.Model 的模型实例 model 和一个继承 tf.train.Optimizer 的优化器 optimizer ,我们可以这样写:checkpoint = tf.train.Checkpoint(myAwesomeModel=model, myAwesomeOptimizer=optimizer)这里 myAwesomeModel 是我们为待保存的模型 model 所取的任意键名。注意,在恢复变量的时候,我们还将使用这一键名。接下来,当模型训练完成需要保存的时候,使用:checkpoint.save(save_path_with_prefix) 就可以。 save_path_with_prefix 是保存文件的目录 + 前缀。
"""
#用于保存训练的模型
ckpt = tf.train.Checkpoint(transformer=transformer, optimizer=optimizer)#创建“对应填充值0的”mask掩码:定义一个padding的遮罩函数,用于去除“输入语句在padding时引入的”噪声
def create_padding_mask(seq):# 填充值0的equal之后都返回True,然后cast转换为1.0的mask掩码。# 非填充值0的equal之后都返回False,然后cast转换为0.0的mask掩码。seq = tf.cast(tf.math.equal(seq, 0), tf.float32)print("seq.shape:", seq.shape) # (batch_size64, 100)#tf.newaxis和np.newaxis 均用作增加维度,其增加的维度值为1return seq[:, tf.newaxis, tf.newaxis, :]  # (batch_size64, 1, 1, seq_len) 即 (batch_size64, 1, 1, 100)#定义一个既能完成训练任务,也能完成预测任务,训练模式时返回的是一步step(一个batch批量大小)的loss值,预测模式时返回的是预测值
def step(inp, tar, train_status = True):#定义一个padding的遮罩函数,用于去除“输入语句在padding时引入的”噪声# 创建“对应填充值0的”mask掩码:对应填充值0的mask掩码为1.0,对应非填充值0的mask掩码为0.0。enc_padding_mask = create_padding_mask(inp)print("enc_padding_mask.shape:",enc_padding_mask.shape) #(64, 1, 1, 100) 即 (batch_size, 1, 1, 100)#训练模式if train_status:with tf.GradientTape() as tape:#调用Transformer的call函数:传入(batch_size, 100)的特征数据、True、(batch_size, 1, 1, 100)的mask掩码#使用 Transformer 对输入语句 进行预测输出predictions = transformer(inp, True, enc_padding_mask)print("step predictions:", predictions.shape)  #(64, 2) 即 (batch_size, 2)"""报错ValueError: setting an array element with a sequence.tf.keras.utils.to_categorical函数底层的第一行代码np.array(输入, dtype='int')如果报错ValueError: setting an array element with a sequence.解决方法一:tf.enable_eager_execution() #开启紧急执行解决方法二:可以尝试使用tf.one_hot(input, num_classes) 来代替 tf.keras.utils.to_categorical(input, num_classes)"""#to_categorical:真实标签进行one-hot化,每个标签的维度为2。#标签值1的one-hot编码为[0,1],标签值0的one-hot编码为[1,0]。而积极的文本数据的标签值用0表示,消极的文本数据的标签值用1表示。tar_one_hot = tf.keras.utils.to_categorical(tar, 2)# tar_one_hot = tf.one_hot(tar, 2)print("step tar_one_hot:", tar_one_hot.shape)  # (64, 2) 即 (batch_size, 2)#使用分类交叉熵来计算loss,传入 one-hot化的真实标签、经过了softmax的预测值,计算两者的lossloss = tf.keras.losses.categorical_crossentropy(tar_one_hot, predictions)print("step loss:", loss.shape)  # (64,) 即 (batch_size,)#因为loss仍然为batch_size批量大小的loss,还要求平均得出一个lossloss = tf.reduce_mean(loss)#求导出loss对于每个参数的梯度值gradients = tape.gradient(loss, transformer.trainable_variables)#使用每个参数对应的梯度值 对参数值本身进行 更新optimizer.apply_gradients(zip(gradients, transformer.trainable_variables))"""此处的train_loss用于对传入的批量的loss值求一个平均值,相当于tf.reduce_mean(loss)的作用train_loss为 tf.keras.metrics.Mean(name='train_loss') 相当于 tf.reduce_mean(loss)train_accuracy为 tf.keras.metrics.SparseCategoricalAccuracy"""#return train_loss(loss), train_accuracy(tar, predictions)#此处使用的是SparseCategoricalAccuracy,因此传入的标签值是还没有one-hot化的值trainAccuracy = train_accuracy(tar, predictions)# print("trainAccuracy:", trainAccuracy.numpy())#训练模式时返回的是一步step(一个batch批量大小)的loss值return loss, trainAccuracy#预测模式else:#使用 Transformer 对输入语句 进行预测输出,transformer返回输出经过softmax的shape为(1, 2)的概率值predictions = transformer(inp, False, enc_padding_mask)#预测模式时返回的是预测值return predictions#定义一个验证函数,使用测试集数据对训练好的模型进行预测验证
def evaluate(inp,tar):# 定义一个padding的遮罩函数,用于去除“输入语句在padding时引入的”噪声# 创建“对应填充值0的”mask掩码:对应填充值0的mask掩码为1.0,对应非填充值0的mask掩码为0.0。enc_padding_mask = create_padding_mask(inp)# 使用 Transformer 对输入语句 进行预测输出,transformer返回输出经过softmax的shape为(1, 2)的概率值predictions= transformer(inp,False,enc_padding_mask)# to_categorical:真实标签进行one-hot化,每个标签的维度为2。# 标签值1的one-hot编码为[0,1],标签值0的one-hot编码为[1,0]。而积极的文本数据的标签值用0表示,消极的文本数据的标签值用1表示。tar = tf.keras.utils.to_categorical(tar, 2)# 使用分类交叉熵来计算loss,传入 one-hot化的真实标签、经过了softmax的预测值,计算两者的lossloss =tf.losses.categorical_crossentropy(tar, predictions)#此处的train_loss用于对传入的批量的loss值求一个平均值,相当于tf.reduce_mean(loss)的作用#train_loss为 tf.keras.metrics.Mean(name='train_loss') 相当于 tf.reduce_mean(loss)return train_loss(loss)

基于Transformer的文本情感分析编程实践(Encoder编码器-Decoder解码器框架 + Attention注意力机制 + Positional Encoding位置编码)相关推荐

  1. 基于Seq2Seq的中文聊天机器人编程实践(Encoder编码器-Decoder解码器框架 + Attention注意力机制)

    日萌社 人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新) Encoder编码器-Decoder解码器框架 + Atten ...

  2. Pytorch:Transformer(Encoder编码器-Decoder解码器、多头注意力机制、多头自注意力机制、掩码张量、前馈全连接层、规范化层、子层连接结构、pyitcast) part1

    日萌社 人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新) Encoder编码器-Decoder解码器框架 + Atten ...

  3. 实体词典 情感词典_基于词典的文本情感分析(附代码)

    一.引言 目前中文文本情感分析主要分为三个类型,第一个是由情感词典和句法结构来做的.第二个是根据机器学习来做的(Bayes.SVM等).第三个是用深度学习的方法来做的(例如LSTM.CNN.LSTM+ ...

  4. 基于LSTM分类文本情感分析

    背景介绍 文本情感分析作为NLP的常见任务,具有很高的实际应用价值.本文将采用LSTM模型,训练一个能够识别文本postive, neutral, negative三种情感的分类器. 本文的目的是快速 ...

  5. Python基于机器学习的文本情感分析详细步骤[附代码和文字解释]

    最近在研究情感分析,感谢CSDN上很多博主的文章,让我受益匪浅.因此在跑出准确率高达88%的分类结果后,写下自己的代码和总结,希望对大家有所帮助~ 目录 一.文本数据预处理 1.读取json并转化为列 ...

  6. [深度学习TF2][RNN-LSTM]文本情感分析包含(数据预处理-训练-预测)

    基于LSTM的文本情感分析 0. 前言 1. 数据下载 2. 训练数据介绍 3. 用到Word2Vector介绍 wordsList.npy介绍 wordVectors.npy介绍 4 数据预处理 4 ...

  7. 文本情感分析方法研究小结

    文本情感分析总结 1. 文本情感分析简介 何谓文本情感分析,其实很简单,利用算法来分析提取文本中表达的情感.例如分析一个句子表达的好.中.坏等判断,高兴.悲伤.愤怒等情绪.如果能将这种文字转为情感的操 ...

  8. 自然语言处理-文本情感分析

    本文为笔者学习阿里云大学的基于LSTM的文本情感分析教学视频总结. 一.何为文本情感分析 其实也就是,用户输入了一句话,我们需要靠机器来知道这句话的情感,本文会以开心和不开心为例,来进行情感分析,有点 ...

  9. 自然语言处理之文本情感分析

    1.导语 深度学习近些年取得突破性的发展,目前深度学习技术在人工智能领域应用最广泛的两方面就是CV(计算机视觉)和NLP(自然语言处理),在本次夏虹老师的<人工智能>课程上,我和我的小组成 ...

最新文章

  1. 浏览器事件捕获冒泡以及阻止冒泡
  2. cma检测_CMA检测方法
  3. 我刊成功承办第二届数据科学家大会(2018)
  4. php ip操作,ip操作 · PHP 个人常用知识总结 · 看云
  5. Leetcode 100.相同的树
  6. ADO.NET 【攻击及防御】
  7. Luogu P1455 搭配购买 题解
  8. 三国演义人物出场统计代码含义_用python分析小说人物关系(二)——实战篇
  9. 产品配件类目税目分类_HS编码知识:汽车零部件怎么归类?
  10. 大学数学学习参考书点评
  11. python 将图像变为矢量图(可字符和序列化)
  12. 数据分析在银行业应用之欺诈检测
  13. 计算机应用基础教案 电子书,计算机应用基础教案(全套)-20210511075659.pdf-原创力文档...
  14. 如何自己编写一个交通仿真软件(一)火种
  15. Qt编写安防视频监控系统61-子模块5设备控制
  16. 全网最细------爬取4k高清大图
  17. java 拉姆达 lamdba get
  18. FAIL : No keyword with name '/dev/mapper/vg1-lv1' found.(解决方法)
  19. 艺赛旗 (RPA) Python 的数据类型
  20. 稳压二极管有什么特性?稳压二极管的特点

热门文章

  1. 高质量程序设计指南C++学习总结二
  2. MySQL语句的条件查询
  3. C++中std::setw()的用法
  4. 数据分析中的常用数学模型实战教程笔记(下)
  5. 新一代网络技术与课程建设师资培训感悟
  6. QQ浏览器如何启用无痕模式
  7. 可视化损失函数空间三维图
  8. linux coredump
  9. 3d transform的(x、y、z)坐标空间及位置
  10. 贝塞尔曲线 unity两点画曲线弧线三点