人工智能实践:Tensorflow2.0笔记 北京大学MOOC(1-3)

发布于:2023-01-20 ⋅ 阅读:(168) ⋅ 点赞:(0)


说明

本文内容整理自中国大学MOOC “北京大学-人工智能实践:Tensorflow笔记” 课程,部分内容可能在原笔记内容上进行了修改,转载请注明出处。
授课老师:曹健
中国大学MOOC 人工智能实践:Tensorflow笔记课程链接
本讲目标:理解神经网络计算过程,使用基于TF2原生代码搭建第一个神经网络训练模型
本节内容:本小节将借助Tensorflow2.0库中的底层代码搭建一个最基础的神经网络并对代码进行初步优化


三、搭建第一个神经网络训练模型

本小节将搭建一个神经网络训练模型用于实现鸢尾花分类,该过程基本思路如下:

1.数据集读入
2.数据集乱序
3.生成训练集和测试集,即x_train/y_train x_test/y_test
4.配成''输入特征,标签''对每次读入一小撮'batch'
5.定义神经网路中所有可训练参数
6.嵌套循环迭代,with 结构更新参数,显示当前loss
7.计算当前参数前向传播后的准确率,显示当前acc
准备数据
搭建网络
参数优化
测试效果
acc / loss可视化

1. 准备数据

准备数据:包括数据集读入、数据集乱序、把训练集和测试集中的数据配成输入特征和标签对、生成 train 和 test 即永不相见的训练集和测试集;

1.1 鸢尾花数据集回顾

鸢尾花数据集

鸢尾花数据集共提供了 150 组鸢尾花数据,每组包括鸢尾花的花萼长、花萼宽、花瓣长、花瓣宽 4 个输入特征,同时还给出了这一组特征对应的鸢尾花类别。
类别包括狗尾鸢尾(Setosa Iris)、杂色鸢尾(Versicolour Iris)、弗吉尼亚鸢尾(Virginica Iris)三类, 分别用数字0、1、2 表示。
其中部分数据示例如下:
鸢尾花数据集部分数据示例

1.2 鸢尾花数据集读入

从sklearn包 datasets 读入数据集,代码如下:

from sklearn.datasets import load_iris
x_data = datasets.load_iris().data # 返回 iris 数据集所有输入特征
y_data = datasets.load_iris().target # 返回 iris 数据集所有标签

程序说明:以上代码从 sklearn 包中导出数据集,并将输入特征赋值给 x_data 变量,将对应标签赋值给 y_data 变量。

1.3 鸢尾花数据集乱序

人类在认识这个世界的时候信息是没有规律的、杂乱无章的涌入大脑的,所以喂入神经网络的数据集也需要被打乱顺序。
鸢尾花数据集乱序代码如下:

np.random.seed(116) # 使用相同的 seed,使输入特征/标签一一对应
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)
tf.random.set_seed(116)

程序说明:以上代码实现了让数据集乱序,因为使用了同样的随机种子,所以打乱顺序后输入特征和标签仍然是一一对应的。

1.3 将数据集分割成永不相见的训练集和测试集

为了公正评判神经网络的效果,训练集和测试集要求没有交集。
将数据集分割成永不相见的训练集和测试集,代码如下:

x_train = x_data[:-30]
y_train = y_data[:-30]
x_test = x_data[-30:]
y_test = y_data[-30:]

程序说明:以上代码将打乱后的前 120 个数据取出来作为训练集,后 30 个数据作为测试集,从而保证了训练集和测试集没有交集。

1.4 配成 [输入特征,标签] 对,之后将每次喂入一小撮(batch)

使用from_tensor_slices可以将训练集的输入特征和标签配对打包配成 [输入特征,标签] 对,之后将每次喂入一小撮(batch)。
代码如下:

train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

代码说明:以上代码使用 from_tensor_slices 把训练集的输入特征和标签配对打包,将每 32 组输入特征标签对打包为一个 batch,在喂入神经网络时会以 batch 为单位喂入。

2. 搭建网络

搭建网络:定义神经网络中的所有可训练参数。
代码如下:

w1 = tf.Variable(tf.random.truncated_normal([ 4, 3 ], stddev=0.1))
b1 = tf.Variable(tf.random.truncated_normal([ 3 ], stddev=0.1))

代码说明:以上代码定义了神经网络的所有可训练参数。
由于只用了一层网络,因为输入特征是4个,输出节点数等于分类数,是3分类,故参数 w i w_i wi 为4行3列的张量, b i b_i bi 必须与 w i w_i wi 的维度一致,所以是3。

3. 参数优化

参数优化:优化这些可训练的参数,利用嵌套循环在 with 结构中求得损失函数 loss对每个可训练参数的偏导数,更改这些可训练参数。
为了查看效果,程序中可以加入每遍历一次数据集显示当前准确率,还可以画出准确率 acc 和损失函数 loss 的变化曲线图。

代码如下:

for epoch in range(epoch): #数据集级别迭代
	for step, (x_train, y_train) in enumerate(train_db): #batch级别迭代
		with tf.GradientTape() as tape: # 记录梯度信息
			前向传播过程计算y
			计算总loss
		grads = tape.gradient(loss, [ w1, b1 ])
		w1.assign_sub(lr * grads[0]) #参数自更新
		b1.assign_sub(lr * grads[1])
print("Epoch {}, loss: {}".format(epoch, loss_all/4))

代码说明:以上代码功能为:在 with 结构中计算前向传播的预测结果 y 、计算损失函数 loss 损失、分别对参数 w 1 w_1 w1 和参数 b 1 b_1 b1 计算偏导数、更新参数 w 1 w_1 w1 和参数 b 1 b_1 b1 的值、打印出这一轮 epoch 后的损失函数值。
因为训练集有 120 组数据,batch 是 32,每个 step 只能喂入 32 组数据,需要 batch 级别循环 4 次,所以 loss 除以 4,求得每次 step 迭代的平均 loss。

4. 测试效果

测试效果:计算当前参数前向传播后的准确率,显示当前准确率 acc。
代码如下:

for x_test, y_test in test_db: 
	y = tf.matmul(h, w) + b # y为预测结果
	y = tf.nn.softmax(y) # y符合概率分布
	pred = tf.argmax(y, axis=1) # 返回y中最大值的索引,即预测的分类
	pred = tf.cast(pred, dtype=y_test.dtype) #调整数据类型与标签一致
	correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
	correct = tf.reduce_sum (correct) # 将每个batch的correct数加起来
	total_correct += int (correct) # 将所有batch中的correct数加起来
	total_number += x_test.shape [0]
acc = total_correct / total_number
print("test_acc:", acc)

代码说明:以上代码通过前向传播计算出 y ,使其符合概率分布并找到最大的概率值对应的索引号,调整数据类型与标签一致,如果预测值和标签相等则 correct 变量自加一,准确率 acc 即预测对了的数量除以测试集中的数据总数。

5. acc / loss可视化

绘制准确率 acc 和损失函数 loss的变化曲线图,可更直观的展现出训练效果。
代码如下:

plt.title(Acc Curve) # 图片标题
plt.xlabel(Epoch) # x 轴名称
plt.ylabel(Acc) # y 轴名称
plt.plot(test_acc, label="$Accuracy$") # 逐点画出 test_acc 值并连线
plt.legend()
plt.show()

代码说明:以上代码可将计算出的准确率画成曲线图,并通过设置图标题、设置 x 轴名称、设置 y 轴名称,标出每个 epoch 时的准确率并画出曲线,可用同样方法画出 loss 曲线。

图像绘制结果训练过程 loss 曲线

图12 训练过程 loss 曲线
训练过程准确率 acc 曲线

图13 训练过程准确率 acc 曲线

6. 完整代码

# -*- coding: UTF-8 -*-
# 利用鸢尾花数据集,实现前向传播、反向传播,可视化loss曲线

# 导入所需模块
import tensorflow as tf
from sklearn import datasets
from matplotlib import pyplot as plt
import numpy as np

# 导入数据,分别为输入特征和标签
x_data = datasets.load_iris().data
y_data = datasets.load_iris().target

# 随机打乱数据(因为原始数据是顺序的,顺序不打乱会影响准确率)
# seed: 随机数种子,是一个整数,当设置之后,每次生成的随机数都一样(为方便教学,以保每位同学结果一致)
np.random.seed(116)  # 使用相同的seed,保证输入特征和标签一一对应
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)
tf.random.set_seed(116)

# 将打乱后的数据集分割为训练集和测试集,训练集为前120行,测试集为后30行
x_train = x_data[:-30]
y_train = y_data[:-30]
x_test = x_data[-30:]
y_test = y_data[-30:]

# 转换x的数据类型,否则后面矩阵相乘时会因数据类型不一致报错
x_train = tf.cast(x_train, tf.float32)
x_test = tf.cast(x_test, tf.float32)

# from_tensor_slices函数使输入特征和标签值一一对应。(把数据集分批次,每个批次batch组数据)
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

# 生成神经网络的参数,4个输入特征故,输入层为4个输入节点;因为3分类,故输出层为3个神经元
# 用tf.Variable()标记参数可训练
# 使用seed使每次生成的随机数相同(方便教学,使大家结果都一致,在现实使用时不写seed)
w1 = tf.Variable(tf.random.truncated_normal([4, 3], stddev=0.1, seed=1))
b1 = tf.Variable(tf.random.truncated_normal([3], stddev=0.1, seed=1))

lr = 0.1  # 学习率为0.1
train_loss_results = []  # 将每轮的loss记录在此列表中,为后续画loss曲线提供数据
test_acc = []  # 将每轮的acc记录在此列表中,为后续画acc曲线提供数据
epoch = 500  # 循环500轮
loss_all = 0  # 每轮分4个step,loss_all记录四个step生成的4个loss的和

# 训练部分
for epoch in range(epoch):  #数据集级别的循环,每个epoch循环一次数据集
    for step, (x_train, y_train) in enumerate(train_db):  #batch级别的循环 ,每个step循环一个batch
        with tf.GradientTape() as tape:  # with结构记录梯度信息
            y = tf.matmul(x_train, w1) + b1  # 神经网络乘加运算
            y = tf.nn.softmax(y)  # 使输出y符合概率分布(此操作后与独热码同量级,可相减求loss)
            y_ = tf.one_hot(y_train, depth=3)  # 将标签值转换为独热码格式,方便计算loss和accuracy
            loss = tf.reduce_mean(tf.square(y_ - y))  # 采用均方误差损失函数mse = mean(sum(y-out)^2)
            loss_all += loss.numpy()  # 将每个step计算出的loss累加,为后续求loss平均值提供数据,这样计算的loss更准确
        # 计算loss对各个参数的梯度
        grads = tape.gradient(loss, [w1, b1])

        # 实现梯度更新 w1 = w1 - lr * w1_grad    b = b - lr * b_grad
        w1.assign_sub(lr * grads[0])  # 参数w1自更新
        b1.assign_sub(lr * grads[1])  # 参数b自更新

    # 每个epoch,打印loss信息
    print("Epoch {}, loss: {}".format(epoch, loss_all/4))
    train_loss_results.append(loss_all / 4)  # 将4个step的loss求平均记录在此变量中
    loss_all = 0  # loss_all归零,为记录下一个epoch的loss做准备

    # 测试部分
    # total_correct为预测对的样本个数, total_number为测试的总样本数,将这两个变量都初始化为0
    total_correct, total_number = 0, 0
    for x_test, y_test in test_db:
        # 使用更新后的参数进行预测
        y = tf.matmul(x_test, w1) + b1
        y = tf.nn.softmax(y)
        pred = tf.argmax(y, axis=1)  # 返回y中最大值的索引,即预测的分类
        # 将pred转换为y_test的数据类型
        pred = tf.cast(pred, dtype=y_test.dtype)
        # 若分类正确,则correct=1,否则为0,将bool型的结果转换为int型
        correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
        # 将每个batch的correct数加起来
        correct = tf.reduce_sum(correct)
        # 将所有batch中的correct数加起来
        total_correct += int(correct)
        # total_number为测试的总样本数,也就是x_test的行数,shape[0]返回变量的行数
        total_number += x_test.shape[0]
    # 总的准确率等于total_correct/total_number
    acc = total_correct / total_number
    test_acc.append(acc)
    print("Test_acc:", acc)
    print("--------------------------")

# 绘制 loss 曲线
plt.title('Loss Function Curve')  # 图片标题
plt.xlabel('Epoch')  # x轴变量名称
plt.ylabel('Loss')  # y轴变量名称
plt.plot(train_loss_results, label="$Loss$")  # 逐点画出trian_loss_results值并连线,连线图标是Loss
plt.legend()  # 画出曲线图标
plt.show()  # 画出图像

# 绘制 Accuracy 曲线
plt.title('Acc Curve')  # 图片标题
plt.xlabel('Epoch')  # x轴变量名称
plt.ylabel('Acc')  # y轴变量名称
plt.plot(test_acc, label="$Accuracy$")  # 逐点画出test_acc值并连线,连线图标是Accuracy
plt.legend()
plt.show()

7. 初步优化

7.1 本地读取鸢尾花数据集

大部分神经网络模型的数据集需要自行采集或下载,程序运行前往往需要首先加载位于本地的数据集。

本文将前述鸢尾花数据集下载后以文本文件(txt)格式保存在了本地,程序执行时将其输入至神经网络进行训练。鸢尾花数据集的 txt 文件包含部分内容如 图14 所示:

鸢尾花数据集 txt 文件内容

图14 鸢尾花数据集 txt 文件内容(部分)

读取本地数据集有两种常见的方法:
(1) 利用 open 函数打开 txt 文件,并处理成神经网络需要的数据结构,该函数格式为open(文件名 , r)
(2) 利用 pandas 中函数读取,并处理成神经网络需要的数据结构,该函数格式为
pd.read_csv( 文件名 ,header=第几行作为表头,sep= 分割符号)

其中利用 pandas 中函数读取代码如下:

import pandas as pd
import numpy as np

df = pd.read_csv("iris.txt",header = 0,sep=",")      #读取本地文件
data = df.values		#去掉索引并取值
x_data = [lines[0:4] for lines in data]      #取输入特征
x_data = np.array(x_data,float)		#转换为numpy格式
y_data = [lines[4] for lines in data]      #取标签 
for i in range(len(y_data)):
	if y_data[i] == setosa:
		y_data[i] = 0
	elif y_data[i] == versicolor:
		y_data[i] = 1
	elif y_data[i] == virginica:
		y_data[i] = 2
	else
		raise ValueError()
y_data = np.array(y_data)

代码说明:以上代码通过读取本地文件、取特征输入、取标签并将其转换为规定格式,实现本地数据集的读取。

利用 open 函数打开 txt 文件并读取数据代码如下:

import numpy as np

f = open("iris.txt","r") # 取本地文件
next(f)		# 去掉首行
contents = f.readlines() # 按行读取
x_data=[];y_data=[];
for content in contents:
	temp = content.split(",") # 按逗号分隔
	x_data.append( np.array(temp[0:4],dtype=float) ) # 取输入特征
	if temp[4] == "setosa\n": # 判断标签并赋值
		y_data.append(0)
	elif temp[4] == "versicolor\n":
		y_data.append(1)
	elif temp[4] == "virginica\n":
		y_data.append(2)
	else:
		raise ValueError()
x_data = np.array(x_data)
y_data = np.array(y_data)

代码说明:以上代码通过读取本地文件、取特征输入、取标签并将其转换为规定格式,实现本地数据集的读取。

7.2 解决神经网络中的“梯度爆炸”问题

7.2.1 “梯度爆炸”问题的出现

本文中的数据集较为简单,前文中利用了简单网络结构进行拟合,仅考虑输入层与输出层构建单层神经网络。参数定义如下:

w1 = tf.Variable(tf.random.truncated_normal[4,3],stddev = 0.1,seed = 1))
b1 = tf.Variable(tf.random.truncated_normal[3],stddev = 0.1,seed = 1))

若将学习率设置为 0.5,训练后将出现 “梯度爆炸” 情况,即此时神经网络训练结果不能有效收敛,参数反复在最优解附近跳动,该训练过程 loss 曲类似 图15 所示:
梯度爆炸时的 loss 曲线

图15 梯度爆炸时的 loss 曲线

7.2.2 “梯度爆炸”问题的解决思路 与 常见的数据预处理函数

分析产生梯度爆炸的原因,考虑到使用梯度下降思想时,其计算公式为
w n e w = w o l d − l r × ∂ L ∂ W w_{new}=w_{old}-lr \times \frac{\partial{L}}{\partial{W}} wnew=woldlr×WL其中参数更新量为学习率与损失函数偏导数相乘,若二者乘积过大,则会导致梯度爆炸。 因此,解决梯度爆炸问题可针对学习率进行调整,也可对数据进行调整。
解决方法可为:
(1) 逐步减小学习率:例如0.1、0.01 等;
(2) 对数据进行预处理后再输入神经网络,减小偏差值的大小,抑制梯度爆炸,即数据归一化与标准化,其主要方法有线性归一化非线性归一化Z-Score 标准化

线性归一化:将数据映射到[0,1]区间中,计算公式如下:
x ∗ = x − min ⁡ { x } max ⁡ { x } − min ⁡ { x } x^{*}=\frac{x-\min \{x\}}{\max \{x\}-\min \{x\}} x=max{x}min{x}xmin{x}
非线性归一化(log 函数转换):使数据映射到[0,1]区间上,计算公式如下:
x ∗ = log ⁡ 10 x log ⁡ 10 max ⁡ { x } x^{*}=\frac{\log _{10}x}{\log _{10}\max \{x\}} x=log10max{x}log10x
Z-Score 标准化:使每个特征中的数值平均值变为
x ∗ = x − m e a n { x } s t d { x } x^{*}=\frac{x-mean\{x\}}{std \{x\}} x=std{x}xmean{x}

以线性归一化为例,其代码实现如下:

def normalize(data):
    x_data = data.T # 每一列为同一属性,转置到每一行
    for i in range(4):
        x_data[i] = (x_data[i] - tf.reduce_min(x_data[i])) / (tf.reduce_max(x_data[i]) - tf.reduce_min(x_data[i]))
    return x_data.T # 转置回原格式

我们也可以使用 sklearn库preprocessing包中的StandardScaler类对数据进行常见的Z-Score 标准化操作,其代码实现如下:

from sklearn.preprocessing import StandardScaler

scaler=StandardScaler()   #类的实例化
x_train = scaler.fit_transform(x_train) #使用 fit_transform()对训练集先拟合数据,再标准化
x_test = scaler.transform(x_test) #使用 transform()对测试集进行数据标准化  

代码说明:(训练集和测试集采用不同函数的原因)
首先,由于我们一般默认训练集与测试集遵循同一分布,所以两者要进行统一的标准化处理。
但在此处,我们使用了不同的与处理函数。
fit_transform() 的作用就是先拟合数据,然后转化它将其转化为标准形式。
tranform() 的作用是通过找中心和缩放等实现标准化。
由于在训练集中调用 fit_transform函数时,其实找到了均值 μ \mu μ 和方差 δ 2 \delta^2 δ2,即我们已经找到了转换规则,我们把这个规则利用在训练集上,同样,我们可以直接将其运用到测试集上(甚至交叉验证集),所以在测试集上的处理,我们只需要标准化数据而不需要再次拟合数据。用一幅图展示如下:训练集和测试集采用了不同的标准化函数
#:本小段参考自 博客园 fit_transform和transform的区别

7.2.2 效果展示

以下实验对使用Z-Score 标准化进行数据预处理前后神经网络的迭代情况进行了对比。

对本节前述的鸢尾花识别程序进行修改,进行对比实验 (实验一及实验二)。
实验一:取 eopch=500,lr=0.3,不对鸢尾花数据做任何预处理;
实验二:取 eopch=500,lr=0.3,使用 StandardScaler类对鸢尾花数据进行Z-Score 标准化处理。

两程序运行后绘制的 loss-acc 曲线分别如图17 、图18 所示。

程序运行结果
实验一:未进行数据预处理后的 loss-acc 曲线

图17 实验一:未进行数据预处理后的 loss-acc 曲线
实验二:对数据进行Z-Score 标准化预处理后的 loss-acc 曲线
图18 实验二:对数据进行Z-Score 标准化预处理后的 loss-acc 曲线

对比实验结果分析
由于实验一未对数据进行预处理且学习率设置过高,程序运行时出现了一小段的 “梯度爆炸情况”;
而在实验二中,虽然学习率与实验一相同,但由于数据预处理过程对数据变化幅度进行了压缩,减小了偏差值的大小,进而抑制了“梯度爆炸情况”,故程序运行过程中未出现“梯度爆炸情况”。

7.3 引入 “指数衰减学习率”

7.3.1 指数衰减学习率

做完数据标准化,上述网络已经可以跑通,下面对网络进行部分优化,增加**“指数衰减学习率”**的概念。

我们已经知道,在神经网络训练中,学习率是一个非常重要的超参数
如果学习率设置过大,训练过程就可能无法收敛到最优解,会在最优解两边进行震荡(即所谓“梯度爆炸”现象);
如果学习率设置过小,学习速度就可能非常慢,会大大降低优化速度,需要很多轮的迭代才能达到一个比较理想的优化效果。
一个重要的想法是引入动态的学习率,使得开始训练时学习率较大,程序可以快速得到一个比较优的解;然后随着迭代的次数来逐步减少学习率,让模型在训练后期更加稳定。
指数衰减学习率可在训练初期赋予网络较大学习率,并在训练过程中逐步减小,该方法能有效增加网络收敛速度。

在 tensorflow 中可使用以下函数产生指数衰减的学习率:

tf.keras.optimizers.schedules.ExponentialDecay(lr_starter,decay_step,decay_rate,staircase,name)

其产生的动态学习率decayed_lr满足: d e c a y e d _ l r = l r _ s t a r t e r × d e c a y _ r a t e g l o b a l _ s t e p d e c a y _ s t e p s decayed\_lr= lr\_starter \times decay\_rate ^ {\frac{global\_step}{decay\_steps}} decayed_lr=lr_starter×decay_ratedecay_stepsglobal_step
函数中当 staircase 为 True 时,学习率呈现阶梯状递减

7.3.2 效果展示

以下实验对引入 “指数衰减学习率” 前后神经网络的迭代情况进行了对比。

对本节前述的鸢尾花识别程序进行修改,进行对比实验 (实验三、实验四、实验五)。
实验三:取 eopch=500,对鸢尾花数据进行Z-Score 标准化处理,不使用指数衰减学习率,取 lr 为常量 0.1;
实验四:取 eopch=500,对鸢尾花数据进行Z-Score 标准化处理,使用 “tf. keras. optimizers. schedules. ExponentialDecay” 函数产生指数衰减学习率:
函数参数lr_starter=0.4, global_step=50, decay_step=0.95, staircase=True
实验五:取 eopch=500,对鸢尾花数据进行Z-Score 标准化处理,使用 “tf. keras. optimizers. schedules. ExponentialDecay” 函数产生指数衰减学习率:
函数参数lr_starter=0.4, global_step=50, decay_step=0.95, staircase=False

三个实验程序运行后绘制的 loss-acc-lr 曲线分别如图19 、图20、图21 所示。

程序运行结果
实验三:取学习率为常量0.1时的 loss-acc-lr 曲线

图19 实验三:取学习率为常量0.1时的 loss-acc-lr 曲线
实验四:使用指数衰减学习率且取 "staircase=True" 时的 loss-acc-lr 曲线
图20 实验四:使用指数衰减学习率且取 “staircase=True” 时的 loss-acc-lr 曲线
实验五:使用指数衰减学习率且取 "staircase=False" 时的 loss-acc-lr 曲线
图21 实验五:使用指数衰减学习率且取 “staircase=False” 时的 loss-acc-lr 曲线

对比实验结果分析
(1) 对比实验四、实验五可知:参数staircase决定学习率是否连续变化,即当 staircase=False 时学习率连续变化,当 staircase=True 时学习率呈阶梯状变化。
(2)由于采用了指数衰减学习率,实验四、实验五即使采用了更大的初始化学习率,依然实现了比实验三更快的迭代速度。说明了引入动态的 “指数衰减学习率” 能够加快神经网络的收敛速度。

7.4 初步优化后的完整代码

以下给出经过初步优化后的完整代码(即上述实验五代码),请注意和之前代码进行对比。

# -*- coding: UTF-8 -*-
# 利用鸢尾花数据集,实现前向传播、反向传播,可视化loss曲线

# 导入所需模块
import tensorflow as tf
from sklearn import datasets
from matplotlib import pyplot as plt
from sklearn.preprocessing import StandardScaler
import numpy as np

# 导入数据,分别为输入特征和标签
x_data = datasets.load_iris().data
y_data = datasets.load_iris().target

# 随机打乱数据
np.random.seed(116)
np.random.shuffle(x_data)
np.random.seed(116)
np.random.shuffle(y_data)

# 将打乱后的数据集分割为训练集和测试集
x_train = x_data[:-30]
y_train = y_data[:-30]
x_test = x_data[-30:]
y_test = y_data[-30:]

# 利用StandardScaler类对数据进行 Z-Score 标准化处理
scaler=StandardScaler()   #实例化
x_train = scaler.fit_transform(x_train) #使用fit_transform(data)一步达成结果
x_test = scaler.transform(x_test)

# 转换x的数据类型
x_train = tf.cast(x_train, tf.float32)
x_test = tf.cast(x_test, tf.float32)

# from_tensor_slices函数使输入特征和标签值一一对应。
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

# 生成神经网络的参数,4个输入特征故,输入层为4个输入节点;因为3分类,故输出层为3个神经元
w1 = tf.Variable(tf.random.truncated_normal([4, 3], stddev=0.1, seed=1))
b1 = tf.Variable(tf.random.truncated_normal([3], stddev=0.1, seed=1))

# 使用 “tf. keras. optimizers. schedules. ExponentialDecay” 函数产生指数衰减学习率:\
#       函数参数lr_starter=0.4, global_step=50, decay_step=0.95, staircase=False。
lr_starter = 0.4  # 初始化学习率为0.4
lr = tf.keras.optimizers.schedules.ExponentialDecay(lr_starter,50,0.95, staircase=False)

train_loss_results = []
test_acc = []
test_lr = []  # 将每轮的lr记录在此列表中,为后续画lr曲线提供数据
epoch = 500  # 循环500轮
loss_all = 0

# 训练部分
for epoch in range(epoch):  #数据集级别的循环
    # 更新参数lr并记录到列表test_lr[]中
    lr_ = lr(epoch).numpy()
    test_lr.append(lr_)
    for step, (x_train, y_train) in enumerate(train_db):  #batch级别的循环
        with tf.GradientTape() as tape:
            y = tf.matmul(x_train, w1) + b1
            y = tf.nn.softmax(y)
            y_ = tf.one_hot(y_train, depth=3)
            loss = tf.reduce_mean(tf.square(y_ - y))
            loss_all += loss.numpy()
        grads = tape.gradient(loss, [w1, b1])
        w1.assign_sub(lr_ * grads[0])  # 参数w1自更新
        b1.assign_sub(lr_ * grads[1])  # 参数b自更新

    print("Epoch {}, loss: {}".format(epoch, loss_all/4))
    print("Test_lr:", lr_)
    train_loss_results.append(loss_all / 4)
    loss_all = 0

    # 测试部分
    total_correct, total_number = 0, 0
    for x_test, y_test in test_db:
        y = tf.matmul(x_test, w1) + b1
        y = tf.nn.softmax(y)
        pred = tf.argmax(y, axis=1)
        pred = tf.cast(pred, dtype=y_test.dtype)
        correct = tf.cast(tf.equal(pred, y_test), dtype=tf.int32)
        correct = tf.reduce_sum(correct)
        total_correct += int(correct)
        total_number += x_test.shape[0]
    acc = total_correct / total_number
    test_acc.append(acc)
    print("Test_acc:", acc)
    print("--------------------------")

# 绘制 loss 曲线
plt.subplot(2, 2, 1)
plt.title('Loss Function Curve')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.plot(train_loss_results, label="$Loss$")
plt.legend()

# 绘制 Accuracy 曲线
plt.subplot(2, 2, 2)
plt.title('Acc Curve')
plt.xlabel('Epoch')
plt.ylabel('Acc')
plt.plot(test_acc, label="$Accuracy$")
plt.legend()

# 绘制 Learning Rate 曲线
plt.subplot(2, 1, 2)
plt.title('Learning Rate Curve')
plt.xlabel('Global steps')
plt.ylabel('Learning Rate')
plt.plot(test_lr, label="$lr$")
plt.legend()
plt.show()

传送门

上一节介绍了TensorFlow2.1 中的基本概念与常用函数。

人工智能实践:Tensorflow2.0笔记 北京大学MOOC(1-2)

下一节将进入第二讲,对本讲建立的神经网络进行进一步优化。

人工智能实践:Tensorflow2.0笔记 北京大学MOOC(2-1)<未完工待续。。。>

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到