卷积神经网络与循环神经网络实战 — 手写数字识别及诗词创作
文章目录
- 卷积神经网络与循环神经网络实战 --- 手写数字识别及诗词创作
- 一、神经网络相关知识
- 1. 深度学习
- 2. 人工神经网络回顾
- 3. 卷积神经网络(CNN)
- 3.1 卷积层
- 3.2 汇集(池化)层
- 3.3 深层卷积神经网络
- 3.4 卷积神经网络的演化和几个代表性模型
- 3.5 卷积神经网络在非图像数据上的应用举例
- 4. 循环神经网络(RNN)
- 4.1 Hopfield神经网络
- 4.2 循环神经网络
- 二、运行环境
- 四、积神经网络完成手写字体识别(Sequential)
- 五、卷积神经网络完成手写字体识别
- 六、循环神经网络完成诗词创作
- 七、使用异步序列到序列编码器-解码器的方法完成诗词创作任务
- 循环神经网络的三种模式
- 八、拓展练习
- 使用卷积神经网络对cifar10数据进行10分类预测,并在训练结束后,以corgi.jpg为例,尝试画出某一个卷积层的输出特征向量映射,以观察卷积神经网络的运作逻辑。
- cifar10数据集介绍
一、神经网络相关知识
1. 深度学习
– 1986年,Rina Dechter提出这个词,以搜索和推理为核心
– 2000年,I. Aizenberg 引入到神经网络研究
– 具有多层结构神经网络模型的机器学习方法
– 是一大类机器学习方法的总称
• 深度学习也与神经网络与贝叶斯学习等结合起来,并不限于狭义的神经网络的概念
2. 人工神经网络回顾
3. 卷积神经网络(CNN)
• 用于解决图像识别领域的问题
• 原有多层感知器解决图像识别面临欠学习或参数过多问题
• 图像中待识别对象的像素点间存在空间关联关系
• 图像对象中存在多种不变性因素:
– 位置,大小,变形并不影响字符的属性
3.1 卷积层
• 单个卷积核:局部信息提取
• 所有位置扫描,一个卷积核得到一个特征图(Feature Map)
• 多个卷积核称作多个通道(channel),用于不同特征提取
• 一维卷积:相应位置加权求和
• 超参数(hyper-parameters)
–卷积核尺寸
–步幅(stride)
–边宽或边衬(pad)
• 对训练过程与结果有重要影响
• 权值共享(weight sharing):一轮扫描中卷积核参数不变
• 前向传播后,通过输出端误差进行反向传播优化
• 多个并行卷积核构成多个通道,提取不同特征
• 图像尺寸𝑛×𝑛,卷积核尺寸𝑘×𝑘,步幅为s ,边衬为p且对称 ,则特征图维数:⌊(n+2p-k)/s+1⌋
• 挤压(squashing):对卷积结果进行非线性运算
• 非线性激活函数
– 传统S型函数(sigmoid) 或双曲正切函数(tanh)
– 常用ReLU函数:矫正线性单元(Rectified Linear Unit)
3.2 汇集(池化)层
• 汇集(“池化”):对卷积后特征图进行降采样
–最大汇集(max pooling):选局部区域最大值
–平均汇集(Average pooling):选区域数值平均值
• 两个参数:汇集区域大小和步幅
3.3 深层卷积神经网络
- AlexNet
– 使用局部感受野、权值共享、最大汇集等措施减少参数量
– 自由参数:~6.1×10^7
– 数据扩增(data augmentation):利用预处理增加训练样本数目
3.4 卷积神经网络的演化和几个代表性模型
• 1990年:LeNet-1
• 2012年:AlexNet
• 2013年:ZFNet
• 2014年:VGG 网络
• 2014年:GoogLeNet
• 2015年:残差网络(ResNet)
• 2017年:密集网络(DenseNet)
• 2018年:多尺度密度网络(MSDNet)
3.5 卷积神经网络在非图像数据上的应用举例
4. 循环神经网络(RNN)
4.1 Hopfield神经网络
4.2 循环神经网络
二、运行环境
Python 3.9.13 (其它与tensorflow对应版本均可)
PyCharm 2022.3 (Professional Edition非硬性要求)
jupyter notebook (备选)
tensorflow-gpu== 2.6.0 (使用GPU进行加速,需单独安装该版本,详见下面文档)
cuDNN== 8.1 (tensorflow-gpu显卡依赖)
CUDA== 11.2 (tensorflow-gpu显卡依赖)
numpy==1.19.2 (python科学计算库)
keras == 2.6.0(开源人工神经网络库)
tensorflow和keras版本对应关系:
建议执行:
pip insatll googlemaps # 同时根据提示内容安装或降级所有依赖包
官方TensorFlow安装文档 (安装必看)
折腾了一下午,妥协了! 除非你自己电脑的tensorflow是1.x版本的,否则不建议使用自己的电脑运行之后的代码,(总之,很难改2.x就对了)反正就是出不了结果,虚拟环境也是如此(gpu虚拟环境报错)。
这里选择阿里云的 天池Notebook 使用也还算比较方便。使用方法自行百度。
官网链接: https://tianchi.aliyun.com/notebook-ai/
四、积神经网络完成手写字体识别(Sequential)
使用TensorFlow的Sequential建立卷积神经网络,完成手写字体识别任务。
# -*- coding: utf-8 -*-
# @Author : Xenon
# @Date : 2023/2/5 23:14
# @IDE : 天池notebook Python3.7.13
"""使用TensorFlow的Sequential建立卷积神经网络,完成手写字体识别任务。"""import os
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, optimizers, datasets
from tensorflow.keras.layers import Dense, Dropout, Flatten
from tensorflow.keras.layers import Conv2D, MaxPooling2Dos.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'def mnist_dataset():"""数据集的读取使用TensorFlow下载mnist数据集,取训练集中的前20000个样本作为本实验的训练集,测试集的所有样本(20000个)作为本实验的测试集。对于样本,将其属性值调整为tf.float32类型并归一化,标签调整为tf.int64类型,随机打乱顺序,训练样本每100个分为一个batch,测试样本只有一个batch。:return: 训练集ds, 测试集test_ds(TensorFlow的dataset格式)"""(x, y), (x_test, y_test) = datasets.mnist.load_data()x = x.reshape(x.shape[0], 28, 28, 1)x_test = x_test.reshape(x_test.shape[0], 28, 28, 1)ds = tf.data.Dataset.from_tensor_slices((x, y))ds = ds.map(prepare_mnist_features_and_labels)ds = ds.take(20000).shuffle(20000).batch(100)test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test))test_ds = test_ds.map(prepare_mnist_features_and_labels)test_ds = test_ds.take(20000).shuffle(20000).batch(20000)return ds, test_dsdef prepare_mnist_features_and_labels(x, y):x = tf.cast(x, tf.float32) / 255.0y = tf.cast(y, tf.int64)return x, yif __name__ == '__main__':# 模型的建立(顶层代码)使用Sequential,建立一个模型并将模型实例化为对象model。model = keras.Sequential([# 该模型要求具有7层(不计入输入层),分别为:Conv2D(32, (5, 5), activation='relu', padding='same'), # 卷积层:二维卷积层,输出深度32,卷积核5*5,激活函数为relu,零填充方式为等宽卷积;MaxPooling2D(pool_size=2, strides=2), # 池化层:最大池化层,区域为2*2,步长为2Conv2D(64, (5, 5), activation='relu', padding='same'), # 卷积层:二维卷积层,输出深度64,卷积核5*5,激活函数为relu,零填充方式为等宽卷积;MaxPooling2D(pool_size=2, strides=2), # 池化层:最大池化层,区域为2*2,步长为2;Flatten(), # Flatten()层:用于将输入特征张量拉平为一维向量,在理论角度不算作一层;layers.Dense(128, activation='tanh'), # 全连接层:节点数为128,激活函数为tanh;layers.Dense(10)]) # 全连接层:节点数为10,无激活函数。optimizer = optimizers.Adam(0.0001) # 设置optimizer,使用Adam算法,参数为0.0001# 使用model.compile配置训练过程,将optimizer输入,自动将标签独热向量化。model.compile(optimizer=optimizer,loss='sparse_categorical_crossentropy', # 损失方程为交叉熵损失函数metrics=['accuracy'] # 评价指标为准确率)train_ds, test_ds = mnist_dataset()# 读取数据集,使用model.fit方法,对训练集进行5轮训练(参数epochs=5)。model.fit(train_ds, epochs=5)# 使用model. evaluate方法,对测试集进行测试。model.evaluate(test_ds)
输出结果:(天池)
五、卷积神经网络完成手写字体识别
使用TensorFlow的基础功能编写卷积神经网络,手动编写权重向量与前向计算逻辑,使用梯度带进行反向传播,完成手写字体识别任务。
# -*- coding: utf-8 -*-
# @Author : Xenon
# @Date : 2023/2/5 23:14
# @IDE : PyCharm(2022.3.2) Python3.9.13
"""使用TensorFlow的基础功能编写卷积神经网络,手动编写权重向量与前向计算逻辑,使用梯度带进行反向传播,完成手写字体识别任务。"""
import os
# import tensorflow as tf
import tensorflow.compat.v1 as tf
from keras.api._v1 import keras
from keras.api._v1.keras import datasets, optimizersfrom tensorflow.python.keras.layers import Conv2D, MaxPooling2D
from tensorflow.python.layers import layers
from tensorflow.python.layers.core import Flatten# from tensorflow.keras import layers, optimizers, datasets
# from tensorflow.keras.layers import Dense, Dropout, Flatten# 版本兼容问题 https://zhuanlan.zhihu.com/p/352494279 仍然失败,建议使用tf1.x算了
tf.disable_v2_behavior()
tf.disable_eager_execution()os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'def mnist_dataset():"""数据集的读取使用TensorFlow下载mnist数据集,取训练集中的前20000个样本作为本实验的训练集,测试集的所有样本(20000个)作为本实验的测试集。对于样本,将其属性值调整为tf.float32类型并归一化,标签调整为tf.int64类型,随机打乱顺序,训练样本每100个分为一个batch,测试样本只有一个batch。:return:训练集ds, 测试集test_ds(TensorFlow的dataset格式)"""(x, y), (x_test, y_test) = datasets.mnist.load_data()x = x.reshape(x.shape[0], 28, 28, 1)x_test = x_test.reshape(x_test.shape[0], 28, 28, 1)ds = tf.data.Dataset.from_tensor_slices((x, y))ds = ds.map(prepare_mnist_features_and_labels)ds = ds.take(20000).batch(100)test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test))test_ds = test_ds.map(prepare_mnist_features_and_labels)test_ds = test_ds.take(20000).batch(20000)return ds, test_dsdef prepare_mnist_features_and_labels(x, y):x = tf.cast(x, tf.float32) / 255.0y = tf.cast(y, tf.int64)return x, y# 模型的建立
class myConvModel(keras.Model):"""定义类myConvModel,继承自父类keras.Model,在构造函数中运行父类的构造函数,定义属性见下文注释:"""def __init__(self):super(myConvModel, self).__init__()# l1_conv:二维卷积层,输出深度32,卷积核5*5,激活函数为relu,零填充方式为等宽卷积self.l1_conv = Conv2D(filters=32,kernel_size=(5, 5),activation='relu', padding='same')# l2_conv:二维卷积层,输出深度64,卷积核5*5,激活函数为relu,零填充方式为等宽卷积self.l2_conv = Conv2D(filters=64,kernel_size=(5, 5),activation='relu', padding='same')# pool:最大池化层,区域为2*2,步长为2self.pool = MaxPooling2D(pool_size=(2, 2), strides=2)# flat:Flatten()层,用于将输入特征张量拉平为一维向量,在理论角度不算作一层;self.flat = Flatten()# dense1:全连接层,节点数为128,激活函数为tanh;self.dense1 = layers.Dense(100, activation='tanh')# dense2:全连接层,节点数为10,无激活函数。self.dense2 = layers.Dense(10)@tf.functiondef call(self, x):"""对输入的样本属性集x进行前向计算。分别经过属性l1_conv,pool,l2_conv,pool,flat,dense1,dense2计算,conv输入值为x,下一个属性的输入值是上一个属性的输出值。最后再进行softmax函数处理。:param x: 样本属性集x:return: 前向计算结果logits"""h1 = self.l1_conv(x)h1_pool = self.pool(h1)h2 = self.l2_conv(h1_pool)h2_pool = self.pool(h2)flat_h = self.flat(h2_pool)dense1 = self.dense1(flat_h)logits = self.dense2(dense1)probs = tf.nn.softmax(logits, axis=-1)return probsif __name__ == '__main__':model = myConvModel() # 将myConvModel实例化为对象modeloptimizer = optimizers.Adam() # 设置optimizer,使用Adam算法,参数为0.0001# 使用model.compile配置训练过程,要求将optimizer输入,损失方程为交叉熵损失函数,自动将标签独热向量化,评价指标为准确率.model.compile(optimizer=optimizer,loss='sparse_categorical_crossentropy',metrics=['accuracy'])train_ds, test_ds = mnist_dataset()# 读取数据集,使用model.fit方法,对训练集进行5轮训练(参数epochs=5)model.fit(train_ds, epochs=5)# 使用model. evaluate方法,对测试集进行测试.model.evaluate(test_ds)
输出结果:
六、循环神经网络完成诗词创作
使用TensorFlow建立循环神经网络,完成诗词创作任务。限制只取前500首唐诗。
# -*- coding: utf-8 -*-
# @Author : Xenon
# @Date : 2023/2/5 23:15
# @IDE : PyCharm(2022.3.2) Python3.9.13
"""使用TensorFlow建立循环神经网络,完成诗词创作任务。限制只取前500首唐诗。"""import collections
# import tensorflow as tf
import tensorflow.compat.v1 as tf # 版本兼容问题,我使用的是tf2.6.0
from keras.api._v1.keras import optimizers
from tensorflow import keras# from tensorflow.keras import layers
# from tensorflow.keras import layers, optimizers, datasetsstart_token = 'bos'
end_token = 'eos'def process_dataset(fileName):"""使用process_dataset函数读取与数据集附注:本实验中,每个字是一个样本,在理论上与实际训练过程中,每首诗是一个样本序列,在dataset数据中,每首诗+诗的字数是一个样本序列。:param fileName: 文件名:return: 样本序列instances, 字到编号的索引word2id, 编号到字的索引id2word"""# 根据给定的文件名,读取每一行诗词,去掉结尾的换行符,把第一个英文冒号“:”前面的诗名去除,# 在剩余内容的前面添加开始符'bos',后面添加结束符'eos',忽略长度大于200的诗,其它诗加入样本集。examples = []with open(fileName, 'r', encoding='utf-8') as fd:times = 0for line in fd:outs = line.strip().split(':')content = ''.join(outs[1:])ins = [start_token] + list(content) + [end_token]if len(ins) > 200:continueexamples.append(ins)times += 1# todo 若只训练500首古诗(古文?)请打开下面两行注释if times >= 500:breakcounter = collections.Counter()for e in examples:for w in e:counter[w] += 1# 统计各个字的频率,按照字频从大到小的顺序为字编号,将填充符号'PAD',未知字符标记'UNK'加入字表,生成字到编号、编号到字的索引(字典类型)sorted_counter = sorted(counter.items(), key=lambda x: -x[1])words, _ = zip(*sorted_counter)words = ('PAD', 'UNK') + words[:len(words)]word2id = dict(zip(words, range(len(words))))id2word = {word2id[k]: k for k in word2id}# 将诗词中的字转化为数字编号indexed_examples = [[word2id[w] for w in poem]for poem in examples]# 统计各个诗词的字数seqlen = [len(e) for e in indexed_examples]# 将编号化的诗(列表),与诗的长度(整数)合并成一个样本序列。依次生成所有样本序列。instances = list(zip(indexed_examples, seqlen))return instances, word2id, id2worddef poem_dataset():"""编写poem_dataset函数,进一步处理数据:return: 数据集ds, 字到编号的索引word2id, 编号到字的索引id2word"""# 使用process_dataset函数,得到返回值instances, word2id, id2wordinstances, word2id, id2word = process_dataset('./poems.txt')# 利用from_generator,将返回值转化为TensorFlow的dataset,传入的参数分别为:# 函数lambda: [ins for ins in instances]:遍历其中的样本,生成dataset# 数据类型(tf.int64, tf.int64):编号化的诗词与字数都是TensorFlow的64位整型# 形状(tf.TensorShape([None]),tf.TensorShape([]),编号化的诗词是一维向量,长度待定,字数是一个变量。ds = tf.data.Dataset.from_generator(lambda: [ins for ins in instances],(tf.int64, tf.int64),(tf.TensorShape([None]), tf.TensorShape([])))# 使用shuffle函数打乱数据集ds = ds.shuffle(buffer_size=10240)# 使用padded_batch函数,将每个样本序列填充并分批,即:每100个样本序列分为一批(batch),每个batch的样本序列长度不一,此时均填充到与当前batch的最长序列相同# (注:样本序列中的诗词长度不一,填充到最长,而诗词长度固定为一个变量,无需填充,也无需改变数值)ds = ds.padded_batch(100, padded_shapes=(tf.TensorShape([None]), tf.TensorShape([])))# 使用map函数,对样本序列进行处理,每个样本序列由原有的[编号化的诗词,诗词长度]改为[编号化的诗词去掉最后一个字,编号化的诗词去掉第一个字,诗词长度-1]# (注:编号化的诗词去掉最后一个字,是训练的输入序列,编号化的诗词去掉第一个字,是训练的真实标签,# 这样,对一个序列的训练过程中,输入样本与真实标签内容相同但错开一位,每个输入样本(一个字)对应的标签是这首诗的下一个字,# 也就是说,训练的目的是根据一首诗的当前字,预测下一个字)ds = ds.map(lambda x, seqlen: (x[:, :-1], x[:, 1:], seqlen - 1))return ds, word2id, id2wordclass myRNNModel(keras.Model):"""编写模型myRNNModel,参数为词到编号的索引w2id,继承自keras.Model"""def __init__(self, w2id): # 执行父类的构造函数super(myRNNModel, self).__init__()self.v_sz = len(w2id) # v_sz:训练集中的不同字的数量(包括开始符、结束符、填充标记、未知字符标记)# embed_layer:使用keras.layers.Embedding进行嵌入,每一个字是一个样本,# 如果经过独热向量处理,样本的属性数量是词表长度,现在经过嵌入,得到的是64个编码(64个属性),效果与独热向量近似。self.embed_layer = tf.keras.layers.Embedding(self.v_sz, 64,batch_input_shape=[None, None])# rnncell:使用keras.layers.SimpleRNNCell,建立一个时间步的RNN隐含层,有128个神经元self.rnncell = tf.keras.layers.SimpleRNNCell(128)# rnn_layer:使用keras.layers.RNN,利用rnncell,建立所有时间步的RNN隐含层,每个时间步返回一个输出self.rnn_layer = tf.keras.layers.RNN(self.rnncell, return_sequences=True)# dense:一个全连接层,神经元数量为v_sz,进行一个分类任务,类别数是v_sz。self.dense = tf.keras.layers.Dense(self.v_sz)@tf.functiondef call(self, inp_ids):""" 用于训练。将输入值用embed_layer嵌入,由[batch中的样本序列数,样本序列中的样本数(填充)]变为[batch中的样本序列数,样本序列中的样本数(填充),64]。用rnn_layer处理,此时的输出事实上是隐状态,每个字都将返回长度为128的隐状态。用dense得到输出,每个字的长度为128的隐状态都将进行处理,各自得到一个长度为v_sz的输出。:param inp_ids: 样本集inp_ids:return: 预测结果(概率)logits"""inp_emb = self.embed_layer(inp_ids)rnn_out = self.rnn_layer(inp_emb)logits = self.dense(rnn_out)return logits@tf.functiondef get_next_token(self, x, state):"""用于测试输入一个字,用embed_layer嵌入,用rnncell.call执行一个时间步的RNN算法,用dense得到输出,用argmax得到预测结果:param x: 当前输入样本:param state: 上一个时刻的隐状态:return: 当前预测结果(标签)out, 当前时刻的隐状态state"""inp_emb = self.embed_layer(x)h, state = self.rnncell.call(inp_emb, state)logits = self.dense(h)out = tf.argmax(logits, axis=-1)return out, statedef mkMask(input_tensor, maxLen):shape_of_input = tf.shape(input_tensor)shape_of_output = tf.concat(axis=0, values=[shape_of_input, [maxLen]])oneDtensor = tf.reshape(input_tensor, shape=(-1,))flat_mask = tf.sequence_mask(oneDtensor, maxlen=maxLen)return tf.reshape(flat_mask, shape_of_output)def reduce_avg(reduce_target, lengths, dim):"""用于辅助交叉熵损失函数的计算根据每个batch中各个输入预测的loss(包括填充符),以及每个batch中每首诗的长度,计算不含填充符的loss其中,设置mkMask函数,根据batch的最长长度与每首诗的长度,使用tf.sequence_mask,生成一个矩阵,其中非填充符为True,填充符为False。将False处的损失设为0(可以利用矩阵对应位置相乘),计算每个序列的总损失,然后除以每个序列的无填充诗词长度,计算每个序列的平均损失。:param reduce_target: 一个batch中各样本(非样本序列)的损失:param lengths: 各序列的真实长度:param dim: 需要求均值的维度:return: 各序列的平均损失red_avg"""shape_of_lengths = lengths.get_shape()shape_of_target = reduce_target.get_shape()if len(shape_of_lengths) != dim:raise ValueError(('Second input tensor should be rank %d, ' +'while it got rank %d') % (dim, len(shape_of_lengths)))if len(shape_of_target) < dim + 1:raise ValueError(('First input tensor should be at least rank %d, ' +'while it got rank %d') % (dim + 1, len(shape_of_target)))rank_diff = len(shape_of_target) - len(shape_of_lengths) - 1mxlen = tf.shape(reduce_target)[dim]mask = mkMask(lengths, mxlen)if rank_diff != 0:len_shape = tf.concat(axis=0, values=[tf.shape(lengths), [1] * rank_diff])mask_shape = tf.concat(axis=0, values=[tf.shape(mask), [1] * rank_diff])else:len_shape = tf.shape(lengths)mask_shape = tf.shape(mask)lengths_reshape = tf.reshape(lengths, shape=len_shape)mask = tf.reshape(mask, shape=mask_shape)mask_target = reduce_target * tf.cast(mask, dtype=reduce_target.dtype)red_sum = tf.reduce_sum(mask_target, axis=[dim], keepdims=False)red_avg = red_sum / (tf.cast(lengths_reshape, dtype=tf.float32) + 1e-30)return red_avg###
@tf.function
def compute_loss(logits, labels, seqlen):"""计算交叉熵损失函数要求使用TensorFlow中的交叉熵损失函数,规定样本预测标签为logits形式,需要用softmax函数处理,真实标签未经独热向量处理,需要自动处理。然后返回平均损失。:param logits: Logits形式的预测标签:param labels: 未经独热向量处理的真实标签:param seqlen::return: 平均损失loss"""losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=labels)losses = reduce_avg(losses, seqlen, dim=1)return tf.reduce_mean(losses)@tf.function
def train_one_step(model, optimizer, x, y, seqlen):"""进行训练(一次优化)在梯度带中,调用模型的call方法(注意该方法如何调用?),预测样本的标签,并调用compute_loss函数计算损失值。利用梯度带进行一次随机梯度下降法训练。:param model: 模型:param optimizer: 优化器(用于指定优化方法):param x: 属性集:param y: 真实标签集:param seqlen::return: 损失loss"""with tf.GradientTape() as tape:logits = model(x)loss = compute_loss(logits, y, seqlen)grads = tape.gradient(loss, model.trainable_variables)optimizer.apply_gradients(zip(grads, model.trainable_variables))return lossdef train(epoch, model, optimizer, ds):"""进行训练(全部优化过程)根据当前训练回合数epoch,模型model,优化器optimizer,数据集ds进行训练,每次读取一个batch,将model,optimizer,batch的属性集x,标签集y(事实上是错了一位的属性集),诗词长度seqlen输入train_one_step中,进行一次训练。附注:之前的实验中,模型自身没有时间步,此时“每个时间步”指梯度下降法的每个时间步,在RNN中,模型自身有时间步(每输入样本序列中的一个样本为一个时间步),为避免混淆,梯度下降法的一个时间步称为“一次优化”:param epoch: 当前训练回合数:param model: 模型:param optimizer: 优化器:param ds: 数据集:return: 损失loss"""loss = 0.0accuracy = 0.0for step, (x, y, seqlen) in enumerate(ds):loss = train_one_step(model, optimizer, x, y, seqlen)print('epoch', epoch, ': loss', loss.numpy())return loss# 执行训练(顶层函数)设置使用Adam法进行优化,参数为0.0005
optimizer = optimizers.Adam(0.0005)
# 使用poem_dataset()读取数据集
train_ds, word2id, id2word = poem_dataset()
# 实例化myRNNModel
model = myRNNModel(word2id)
# 执行3个epoch的训练,每次调用一遍train函数
for epoch in range(3):loss = train(epoch, model, optimizer, train_ds)def gen_sentence():"""用于测试生成随机的初始状态state(注:根据相关源代码,若生成一个序列的初始状态,只取其中第一个),以开始符bos作为初始输入cur_token,执行50次get_next_token方法,每一次执行,更新输入cur_token为执行后的输出,状态state为执行后的状态,将更新后的输入与状态作为下一次执行的参数。收集输出值,并将其重新转化为汉字。:return: 转化为汉字的诗词,每个字存储为列表的一个元素[id2word[t] for t in collect]"""state = [tf.random.normal(shape=(1, 128), stddev=0.5)]cur_token = tf.constant([word2id['bos']], dtype=tf.int32)collect = []for _ in range(50):cur_token, state = model.get_next_token(cur_token, state)collect.append(cur_token.numpy()[0])return [id2word[t] for t in collect]# 将gen_sentence的返回值拼接成一个字符串,进行输出(顶层代码)
print(''.join(gen_sentence()))
输出结果:(代码不是我写的,无能为力。。。)
每次运行结果不同,但是一言难尽。上面的代码中默认只训练前500首,可以将其注释(见第39行code),训练时间稍长,下面的我其中一次运行的输出结果:
就吐槽一句,chatGPT要是见到这玩意儿估计能气死。。。。。
七、使用异步序列到序列编码器-解码器的方法完成诗词创作任务
循环神经网络的三种模式
循环神经网络可以应用到很多不同类型的机器学习任务中,我们根据这些任务的特点将其分为以下三种模式:序列到类别的模式、同步的序列到序列模式、异步的序列到序列模式。
参考:邱锡鹏《神经网络与深度学习》
代码实现:
时间不够充裕,后面有时间单独发一篇,敬请关注。
八、拓展练习
使用卷积神经网络对cifar10数据进行10分类预测,并在训练结束后,以corgi.jpg为例,尝试画出某一个卷积层的输出特征向量映射,以观察卷积神经网络的运作逻辑。
cifar10数据集介绍
CIFAR-10数据集由10类32x32的彩色图片组成,一共包含60000张图片,每一类包含6000图片。其中50000张图片作为训练集,10000张图片作为测试集。CIFAR-10数据集被划分成了5个训练的batch和1个测试的batch,每个batch均包含10000张图片。测试集batch的图片是从每个类别中随机挑选的1000张图片组成的,训练集batch以随机的顺序包含剩下的50000张图片。不过一些训练集batch可能出现包含某一类图片比其他类的图片数量多的情况。训练集batch包含来自每一类的5000张图片,一共50000张训练图片。下图显示的是数据集的类,以及每一类中随机挑选的10张图片,如图 1 CIFAR-10数据集:
数据集下载:
下载极慢,建议去官网下载 CIFAR-10数据集
代码实现:
见后面博文...