tensorflow2基础 3节 mnist实践
(前言:在tensorflow基础中没有学习过的方法会在实践中仔细拓展)
1.数据处理
1.1 加载数据
from tensorflow.keras import datasets
import tensorflow as tf
(x, y), (x_test, y_test) = datasets.mnist.load_data()
train_db = tf.data.Dataset.from_tensor_slices((x, y))
#Dataset.from_tensro_slices将数据转化成Dataset对象
Dataset:一种为 TensorFlow 模型创建输入管道的新方式。我们可以理解为Dataset是统一了元素类型的List。我们完全可以用类似List的形式遍历数据。但是不能用train_db[i]这种索引的方式。
1.2 随即打散
通过Dataset.shuffle(buffer_size)工具可以设置Dataset对象随机打散数据之间的顺序。buffer_size指参数指定缓冲池大小,一般设置为一个较大的常数即可。Dataset提供的工具函数会返回新的Dataset对象,可以通过db = db.step1().setp2().step()3等方式完成数据处理,非常方便。
1.3 批训练
为了利用显卡的并行运算能力,一般计算会同时计算多个样本,我们称为批训练,其中样本数量称为Batch Size。Dataset设置批训练方式如下:
train_db = train_db.batch(128)
#设置样本数量为128的批训练
1.4 预处理
由于数据集的数据格式一般都不能满足直接输入模型的条件,因此需要我们去预先处理数据,例如mnist数据集的预先处理如下:
def preprocess(x, y):
x = tf.cast(x, dtype = tf.float32)/255
#标准化,将图片数据标准化到[0,1]
x = tf.reshape(x,[-1,28*28])
#将图片打平
y = tf.cast(y, dtype=tf.int32)
#标签转换成整型张量
y = tf.one_got(y, depth=10)
#转换成one-hot数组
return x,y
如果我们遍历Dataset数据对每个数据调用一次preprocess函数,非常的不方便。Dataset的方法map(func),可以非常方便地调用用户自定义的处理逻辑,func是我们自定义的函数,实现如下:
train_db = train_db.map(preprocess)
#map方法让所有数据都经过了preprocess处理
1.5 循环训练
epoch:遍历一次数据集大小的数据称为一次epoch。通常我们训练时需要多个epoch才能达到好的效果,我们可以通过Dataset.repeat(epoch)实现
train_db = train_db.repeat(20)
#设置后 当遍历train_db时重复20次epoch才会结束队train_db的遍历
总结一下代码贴在下面:
def preprocess(x, y):
x = tf.cast(x, dtype = tf.float32)/255
#标准化,将图片数据标准化到[0,1]
x = tf.reshape(x,[-1,28*28])
#将图片打平
y = tf.cast(y, dtype=tf.int32)
#标签转换成整型张量
y = tf.one_got(y, depth=10)
#转换成one-hot数组
return x,y
(x, y), (x_test, y_test) = datasets.mnist.load_data()
train_db = tf.data.Dataset.from_tensor_slices((x, y))
batchsz = 512 #batch size
train_db = tf.data.Dataset.from_tensor_slices((x, y))
train_db = train_db.shuffle(1000)#打散
train_db = train_db.batch(batchsz)#批训练
train_db = train_db.map(preprocess)#预处理
train_db = train_db.repeat(20)#epoch
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_db = test_db.shuffle(1000).batch(batchsz).map(preprocess)
#对测试数据我们使用相同的操作
loss_list = [] #记录学习过程
accurate_list = []#记录学习过程
2.梯度下降
我们之前介绍了tensorflow具有自动求导的功能,通过GradientTape。
GradientTape:tape在英文中是磁带的意思,会记录函数式并且对函数式进行自动的求导。但是对每一个函数式和变量进行求导的计算量巨大,tensorflow的处理方式是:
a. 只有用tf.Variable(tensor)声明的张量能求导(还有其他声明为Variable的方法)。
b.使用GradientTape.watch(tensor)声明的张量可以求导
c.使用上下文管理器with,with中的式子才可以求导
在代码前先说一下关键字zip:见下面的小例子
for p,g in zip([1,3,5],[2,4,6,7]):
print(p)
print(g)
"""
1
2
3
4
5
6
"""
#同时遍历直到其中一个容器遍历完为止不再遍历。
梯度下降:
#这里使用均方误差作为损失函数(如果看过我的深度学习入门专栏,那里使用的是交叉熵误差)
for step,(x,y) in enumerate(train_db):
x = tf.reshape(x,[-1,784])
#x.shape=(512, 784) y.shape=(512, 10)
with tf.GradientTape() as tape:
#layer1
h1 = x @ w1 + b1
h1 = tf.nn.relu(h1) #使用relu激活函数
#layer2
h2 = h1 @ w2 + b2
h2 = tf.nn.relu(h2)
#output
out = h2 @ w3 + b3
loss = tf.square(y - out)
#思考一个问题,y和out类型相同吗?
#我们在preprocess中将x定义为float32所以out为float32
#我们在preprocess中将y定义为int32
#但是tf.one_hot(y)返回的元素类型是float32,这就合理了
loss = tf.reduce_mean(loss)#求平均值
grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])
for p,g in zip([w1, b1, w2, b2, w3, b3],grads):
p.assign_sub(lr * g)
#assign_sub是tf.Variable张量的特有方法,等价于p = tf.assign_sub(p,lr*g)
3. 记录学习过程
实际上以上代码已经可以完成mnist数据集的识别了,但是为了方便调整参数或使用一些优化方法,我们总是会写一些代码记录学习过程。
loss_list.append(loss)
# print(step,"loss",float(loss))
#loss是float32型张量,所以加个float,变成浮点数输出
total = 0.0 # 记录测试数据量
total_correct = 0.0 # 记录正确分类数据量
for x, y in test_db:
# layer1.
h1 = x @ w1 + b1
h1 = tf.nn.relu(h1)
# layer2
h2 = h1 @ w2 + b2
h2 = tf.nn.relu(h2)
# output
out = h2 @ w3 + b3
pred = tf.argmax(out, axis=1)
# 将预测结果从one_hot转为一维张量
y = tf.argmax(y, axis=1)
# 正确标签也转为一维度向量
correct = tf.equal(pred, y)
# 比较各个数据预测结果
total_correct += tf.reduce_sum(tf.cast(correct, dtype=tf.int32)).numpy()
# 将bool类型张量转为int32,对元素进行求和。此时张量数值就是正确分类个数。使用numpy()将标量型张量转为普通张量
total += x.shape[0]
# 一次循环的数据量
accurate_list.append(total_correct / total)
if step % 120 == 0:
print("===epoch:",step/120," ===accuracy:",total_correct/total," ===loss:",float(loss)," ===")
4. 保存参数
对于我们学习用笔记本电脑而言,我们这个三层的全连接神经网络训练时间可以接受,但是大型神经网络,甚至带有卷积层的小网络训练时间往往要几个小时甚至几天几个星期。所以保存训练好的参数十分重要,需要我们分类的数据我们之间使用参数前向传播就好了,但是对学习来说,保存参数用处不是那么大。
def save_params(params,file_name='params.pkl'):
with open(file_name,'wb') as f:
pickle.dump(params,f)
def load_params(file_name='params.pkl'):
with open(file_name,'rb') as f:
params = pickle.load(f);
return params
def delete_params(file_name = 'params.pkl'):
path = os.getcwd()
if os.path.exists(path+"/"+file_name):
os.remove(path+"/"+file_name)
5.画图
plt.figure("loss")
y1 = np.array(loss_list)
x1 = np.range(0,len(loss_list))
plt.xlabel("BatchSize/100")
plt.ylabel("loss value")
plt.plot(x1,y1)
plt.figure("accuracy")
y2 = np.array(accurate_list)
x2 = np.range(0,len(accurate_list))
plt.xlabel("BatchSize/1000")
plt.ylabel("accuracy")
plt.show()
6. 整合
写了一个整合函数,若存在params.pkl文件就直接加载参数计算测试数据正确率,若不存在则开始训练。
def mnist_classify():
file_name = 'params.pkl'
path = os.getcwd()
#delete_params()
if os.path.exists(path + "/" + file_name):
params = load_params()#加载参数
w1 = params['w1']
b1 = params['b1']
w2 = params['w2']
b2 = params['b2']
w3 = params['w3']
b3 = params['b3']
total = 0.0 # 记录测试数据量
total_correct = 0.0 # 记录正确分类数据量
for x, y in test_db:
# layer1.
h1 = x @ w1 + b1
h1 = tf.nn.relu(h1)
# layer2
h2 = h1 @ w2 + b2
h2 = tf.nn.relu(h2)
# output
out = h2 @ w3 + b3
pred = tf.argmax(out, axis=1)
# 将预测结果从one_hot转为一维张量
y = tf.argmax(y, axis=1)
# 正确标签也转为一维度向量
correct = tf.equal(pred, y)
# 比较各个数据预测结果
total_correct += tf.reduce_sum(tf.cast(correct, dtype=tf.int32)).numpy()
# 将bool类型张量转为int32,对元素进行求和。此时张量数值就是正确分类个数。使用numpy()将标量型张量转为普通张量
total += x.shape[0]
print("train has achieved at last time!if you want to train again,"
"please detele params.pkl with function delete_params!")
print("accuracy:",total_correct/total)
#if
else:train()
然后我们把mnist实践所有代码贴出来(复制下来可以直接跑):
import matplotlib.pyplot as plt
import numpy as np
import os
from tensorflow.keras import datasets
import tensorflow as tf
import pickle
def preprocess(x, y):
x = tf.cast(x, dtype = tf.float32)/255
#标准化,将图片数据标准化到[0,1]
x = tf.reshape(x,[-1,28*28])
#将图片打平
y = tf.cast(y, dtype=tf.int32)
#标签转换成整型张量
y = tf.one_hot(y, depth=10)
#转换成one-hot数组
return x,y
(x, y), (x_test, y_test) = datasets.mnist.load_data()
train_db = tf.data.Dataset.from_tensor_slices((x, y))
print(x.shape[0])
batchsize = 500 #batch size
train_db = tf.data.Dataset.from_tensor_slices((x, y))
train_db = train_db.shuffle(1000)#打散
train_db = train_db.batch(batchsize)#批训练
train_db = train_db.map(preprocess)#预处理
train_db = train_db.repeat(20)#epoch
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_db = test_db.shuffle(1000).batch(batchsize).map(preprocess)
loss_list = []
accurate_list = []
def train():
lr = 0.1 #学习率
#第一层参数
w1 = tf.Variable(tf.random.normal([784,256],stddev=0.1))
b1 = tf.Variable(tf.zeros([256]))
#第二层参数
w2 = tf.Variable(tf.random.normal([256,128],stddev=0.1))
b2 = tf.Variable(tf.zeros([128]))
#第三层参数
w3 = tf.Variable(tf.random.normal([128,10],stddev=0.1))
b3 = tf.Variable(tf.zeros([10]))
#这里使用均方误差作为损失函数(如果看过我的深度学习入门专栏,那里使用的是交叉熵误差)
#mnist 6万张train数据,batch size = 500 20次epoch共循环120/batchsize=2400次
for step,(x,y) in enumerate(train_db):
x = tf.reshape(x,[-1,784])
#x.shape=(512, 784) y.shape=(512, 10)
with tf.GradientTape() as tape:
#layer1
h1 = x @ w1 + b1
h1 = tf.nn.relu(h1) #使用relu激活函数
#layer2
h2 = h1 @ w2 + b2
h2 = tf.nn.relu(h2)
#output
out = h2 @ w3 + b3
loss = tf.square(y - out)
#思考一个问题,y和out类型相同吗?
#我们在preprocess中将x定义为float32所以out为float32
#我们在preprocess中将y定义为int32
#但是tf.one_hot(y)返回的元素类型是float32,这就合理了
loss = tf.reduce_mean(loss)#求平均值
grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])
for p,g in zip([w1, b1, w2, b2, w3, b3],grads):
p.assign_sub(lr * g)
#assign_sub是tf.Variable张量的特有方法,等价于p = tf.assign_sub(p,lr*g)
loss_list.append(loss)
# print(step,"loss",float(loss))
#loss是float32型张量,所以加个float,变成浮点数输出
total = 0.0 # 记录测试数据量
total_correct = 0.0 # 记录正确分类数据量
for x, y in test_db:
# layer1.
h1 = x @ w1 + b1
h1 = tf.nn.relu(h1)
# layer2
h2 = h1 @ w2 + b2
h2 = tf.nn.relu(h2)
# output
out = h2 @ w3 + b3
pred = tf.argmax(out, axis=1)
# 将预测结果从one_hot转为一维张量
y = tf.argmax(y, axis=1)
# 正确标签也转为一维度向量
correct = tf.equal(pred, y)
# 比较各个数据预测结果
total_correct += tf.reduce_sum(tf.cast(correct, dtype=tf.int32)).numpy()
# 将bool类型张量转为int32,对元素进行求和。此时张量数值就是正确分类个数。使用numpy()将标量型张量转为普通张量
total += x.shape[0]
# 一次循环的数据量
accurate_list.append(total_correct / total)
if step % 120 == 0:
print("===epoch:",step/120," ===accuracy:",total_correct/total," ===loss:",float(loss)," ===")
#for
params = {}
params['w1'] = w1
params['b1'] = b1
params['w2'] = w2
params['b2'] = b2
params['w3'] = w3
params['b3'] = b3
save_params(params)#保存参数
plt.figure("loss")
y1 = np.array(loss_list)
x1 = range(0, len(loss_list))
plt.xlabel("BatchSize/100")
plt.ylabel("loss value")
plt.plot(x1, y1)
plt.figure("accuracy")
y2 = np.array(accurate_list)
x2 = range(0, len(accurate_list))
plt.plot(x2, y2)
plt.xlabel("BatchSize/1000")
plt.ylabel("accuracy")
plt.show()
def save_params(params,file_name='params.pkl'):
with open(file_name,'wb') as f:
pickle.dump(params,f)
def load_params(file_name='params.pkl'):
with open(file_name,'rb') as f:
params = pickle.load(f);
return params
def delete_params(file_name = 'params.pkl'):
path = os.getcwd()
if os.path.exists(path+"/"+file_name):
os.remove(path+"/"+file_name)
def mnist_classify():
file_name = 'params.pkl'
path = os.getcwd()
delete_params()
if os.path.exists(path + "/" + file_name):
params = load_params()#加载参数
w1 = params['w1']
b1 = params['b1']
w2 = params['w2']
b2 = params['b2']
w3 = params['w3']
b3 = params['b3']
total = 0.0 # 记录测试数据量
total_correct = 0.0 # 记录正确分类数据量
for x, y in test_db:
# layer1.
h1 = x @ w1 + b1
h1 = tf.nn.relu(h1)
# layer2
h2 = h1 @ w2 + b2
h2 = tf.nn.relu(h2)
# output
out = h2 @ w3 + b3
pred = tf.argmax(out, axis=1)
# 将预测结果从one_hot转为一维张量
y = tf.argmax(y, axis=1)
# 正确标签也转为一维度向量
correct = tf.equal(pred, y)
# 比较各个数据预测结果
total_correct += tf.reduce_sum(tf.cast(correct, dtype=tf.int32)).numpy()
# 将bool类型张量转为int32,对元素进行求和。此时张量数值就是正确分类个数。使用numpy()将标量型张量转为普通张量
total += x.shape[0]
print("train has achieved at last time!if you want to train again,"
"please detele params.pkl with function delete_params!")
print("accuracy:",total_correct/total)
#if
else:train()
mnist_classify()
7.结果分析
打印结果
===epoch: 0.0 ===accuracy: 0.1128 ===loss: 0.8662410378456116 ===
===epoch: 1.0 ===accuracy: 0.6628 ===loss: 0.07242922484874725 ===
===epoch: 2.0 ===accuracy: 0.7571 ===loss: 0.057004377245903015 ===
===epoch: 3.0 ===accuracy: 0.8028 ===loss: 0.0505426786839962 ===
===epoch: 4.0 ===accuracy: 0.8266 ===loss: 0.04485265538096428 ===
===epoch: 5.0 ===accuracy: 0.8415 ===loss: 0.04237598180770874 ===
===epoch: 6.0 ===accuracy: 0.8553 ===loss: 0.03907330334186554 ===
===epoch: 7.0 ===accuracy: 0.8651 ===loss: 0.03546715900301933 ===
===epoch: 8.0 ===accuracy: 0.8694 ===loss: 0.036105699837207794 ===
===epoch: 9.0 ===accuracy: 0.8743 ===loss: 0.03427095338702202 ===
===epoch: 10.0 ===accuracy: 0.8805 ===loss: 0.03233451023697853 ===
===epoch: 11.0 ===accuracy: 0.8847 ===loss: 0.030765902251005173 ===
===epoch: 12.0 ===accuracy: 0.8879 ===loss: 0.03320581838488579 ===
===epoch: 13.0 ===accuracy: 0.8906 ===loss: 0.031756166368722916 ===
===epoch: 14.0 ===accuracy: 0.8946 ===loss: 0.028029320761561394 ===
===epoch: 15.0 ===accuracy: 0.8964 ===loss: 0.02943616546690464 ===
===epoch: 16.0 ===accuracy: 0.899 ===loss: 0.027767568826675415 ===
===epoch: 17.0 ===accuracy: 0.901 ===loss: 0.027679290622472763 ===
===epoch: 18.0 ===accuracy: 0.903 ===loss: 0.026561297476291656 ===
===epoch: 19.0 ===accuracy: 0.9042 ===loss: 0.0253906212747097 ===
可以看到,最后的准确率有0.9左右。损失函数值已经降低到了0.025左右。对于全连接的神经网络,这种表现还不错。学习率在整合时我调整为0.1了,因为1e-2的学习率太低,最后的准确率只有0.7左右。可以看到损失函数图像和准确率图像也很“漂亮”,没有发现什么问题,mnist实战到此完成!