机器学习里的Hello World——TensorFlow 2.0在MNIST数据集上的尝试
首先,TensorFlow 2.0已经正式发布很久啦,TensorFlow 2.0真香~
我刚开始用TensorFlow的时候,还是1.4版本。有一说一,我觉得1.x版本的TensorFlow真心不怎么好用,虽然很灵活,但实现模型太过繁琐,接口很乱,还有很多冗余接口。
后来刚接触到Keras,便觉得这是一股清流,Keras封装的接口非常简洁,你完全可以使用Keras以极快的速度完成模型的构建。但它也有个明显的缺点——因为封装程度比较高,它的灵活性不足,这个时候还是要靠TensorFlow 1.x。
在我快要转投Pytorch阵营的时候,TensorFlow 2.0出来啦~然后……
MNIST数据集作为机器学习里的“Hello World”,本文以MNIST数据集来演示TensorFlow 2.0的简单使用。
转载请注明来源:https://blog.csdn.net/aaronjny/article/details/103595937
一、使用TensorFlow 2.0的低级接口编写模型
机器学习框架很难实时跟进业内的最新研究成果,一些比较新的模型在框架里面可能是没有的。如果我们需要手动去实现一些自定义的模型,TensorFlow 2.0的低级接口给我们留下了足够的灵活性,以完成此项工作。
当然了,这里我们不需要写什么新模型,就简单用低级接口实现一个MNIST上的全连接神经网络。
简单介绍下MNIST数据集(我觉得大家都是知道的,但以防万一,我还是多说一下= =),这是一个手写数字(0-9)的数据集,共7万张图片,其中6万张作为训练集,1万张作为验证集(又叫开发集)。每张手写数字图片都是28*28的灰度图片。
我们可以通过tf.keras直接获取数据集。
import tensorflow as tf
(x_train, y_train), (x_dev, y_dev) = tf.keras.datasets.mnist.load_data()
print(x_train.shape, y_train.shape, x_dev.shape, y_dev.shape)
我们看一下数据维度:
(60000, 28, 28) (60000,) (10000, 28, 28) (10000,)
OK,没有问题,和我们前面说的一致。因为是编写全连接神经网络,所以需要把28*28的图片,reshape成长度为784的一维向量,我们先对数据进行预处理,并封装成tf.data.Dataset对象:
# -*- coding: utf-8 -*-
# @File : data.py
# @Author : AaronJny
# @Time : 2019/12/17
# @Desc :
import tensorflow as tf
def preprocess_1d(x, y):
"""
预处理数据,28*28的图片将被展平为长度为784的向量
"""
x = tf.cast(x, dtype=tf.float32)
# 按像素值的深浅压缩为[0,1]之间
x = x / 255.
# 展平图片
x = tf.reshape(x, (-1, 28 * 28))
# 将标签转成one-hot形式
y = tf.cast(y, dtype=tf.int32)
y = tf.one_hot(y, 10)
return x, y
def load_data_1d(batch_size=128):
"""
加载mnist数据集.返回两个tf.data.Dataset对象,分别为训练数据集和验证数据集。
x.shape==(None,784),y.shape==(None,10)
"""
# 利用keras下载并加载数据集
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
# 封装成tf.data.Dataset数据集对象
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train))
# 设置每次迭代的mini_batch大小
train_db = train_db.batch(batch_size)
# 对数据进行预处理
train_db = train_db.map(preprocess_1d)
# 打乱数据顺序
train_db = train_db.shuffle(10000)
# 封装数据集对象
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
# 设置mini_batch
test_db = test_db.batch(batch_size)
# 进行数据预处理
test_db = test_db.map(preprocess_1d)
return train_db, test_db
有几点需要注意一下:
- 为提升训练效果,图片像素按比例压缩到[0,1]之间
- 为了将图片输入到全连接神经网络中,图片从[28,28]被展平为[784,]
- 为提升训练效果,训练数据集被随机打乱,而模型不会在验证集上学习,所以验证集没必要打乱顺序
接下来我们实现一个全连接模型:
# -*- coding: utf-8 -*-
# @File : model1.py
# @Author : AaronJny
# @Time : 2019/12/17
# @Desc :
import matplotlib.pyplot as plt
import tensorflow as tf
from data import load_data_1d
# 获取训练数据集和验证数据集
train_db, dev_db = load_data_1d(128)
# ########创建参数#########
# 使用xavier进行初始化
initializer = tf.initializers.GlorotNormal()
# 第一层全连接层参数
w1 = tf.Variable(initializer((784, 256)))
b1 = tf.Variable(initializer((256,)))
# 第二层全连接层参数
w2 = tf.Variable(initializer((256, 128)))
b2 = tf.Variable(initializer((128,)))
# 第三层(输出层)参数(10分类问题,所以节点数为10)
w3 = tf.Variable(initializer((128, 10)))
b3 = tf.Variable(initializer((10,)))
# 用一个list保存全部可训练参数,便于后面进行反向传播
trainable_vars = [w1, b1, w2, b2, w3, b3]
# 学习率
learn_rate = 0.01
# 训练多少个epoch
epochs = 30
# 记录训练损失值的列表,用于绘图
train_losses = []
# 记录验证集准确率的列表,用于绘图
val_accs = []
# ##########开始训练了##########
# 训练epochs个epoch
for epoch in range(1, epochs + 1):
print('epoch {}...'.format(epoch))
# 训练一个epoch
for step, (x, y) in enumerate(train_db, start=1):
# 对于每一个mini_batch
with tf.GradientTape() as tape:
# ###前向传播####
# 第一层relu(wx+b)
z1 = x @ w1 + b1
a1 = tf.nn.relu(z1)
# 第二层
z2 = a1 @ w2 + b2
a2 = tf.nn.relu(z2)
# 第三层,softmax(wx+b)
z3 = a2 @ w3 + b3
a3 = tf.nn.softmax(z3)
# 计算loss
loss = tf.keras.losses.categorical_crossentropy(y, a3)
loss = tf.reduce_mean(loss)
# ######反向传播######
# 对所有可训练参数求偏导
grads = tape.gradient(loss, trainable_vars)
# 梯度下降
for var, grad in zip(trainable_vars, grads):
# w=w-lr*dw
var.assign_sub(learn_rate * grad)
# 记录本次训练损失值
train_losses.append(float(loss))
# 一个epoch训练完成了,进行一次验证
# 因为用了mini_batch,所以要用一个列表临时缓存一下每个mini_batch的acc
tmp_accs = []
for x, y in dev_db:
# 预测
z1 = x @ w1 + b1
a1 = tf.nn.relu(z1)
z2 = a1 @ w2 + b2
a2 = tf.nn.relu(z2)
z3 = a2 @ w3 + b3
a3 = tf.nn.softmax(z3)
# 获取预测值
y_pred = tf.argmax(a3, axis=1)
# 正确标签
y_true = tf.argmax(y, axis=1)
# 计算正确率
acc = tf.reduce_mean(tf.cast(tf.equal(y_pred, y_true), dtype=tf.float32))
# 缓存
tmp_accs.append(float(acc))
# 计算平均正确率
val_accs.append(sum(tmp_accs) / len(tmp_accs))
# 输出一下acc
print('Val Accuracy', val_accs[-1])
# 训练完成了,绘制出训练loss变化图和验证acc变化图
# loss图
plt.figure()
plt.plot(train_losses, 'b')
# acc图
plt.figure()
plt.plot(val_accs, 'r')
plt.show()
简单训练30个epoch,能够看到,在训练过程中,loss逐步降低,验证集准确率逐步提高。30个epoch以后,验证集准确率约为96%.
二、使用tf.keras实现一个全连接神经网络
只使用低级接口实现神经网络毕竟还是太麻烦了,要写很多行代码,开发效率很低。幸好,tf.keras已经为我们封装好了相关接口,下面,我们使用tf.keras实现一个全连接神经网络。(当然,如果你需要实现比较新的模型,或者自定义程度很高的网络,还是会需要用到低级接口,这个时候你就会感激TensorFlow的灵活性)
# -*- coding: utf-8 -*-
# @File : model2.py
# @Author : AaronJny
# @Time : 2019/12/18
# @Desc :
import tensorflow as tf
from data import load_data_1d
# 加载数据集
train_db, dev_db = load_data_1d(128)
# 构建模型
model = tf.keras.Sequential([
# 第一层,节点为256,**函数为relu
tf.keras.layers.Dense(256, activation=tf.nn.relu),
tf.keras.layers.Dense(128, activation=tf.nn.relu),
tf.keras.layers.Dense(64, activation=tf.nn.relu),
# 输出层,10分类问题,**函数为softmax
tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
# 输出一下模型的结构
model.build((None, 784))
model.summary()
# 编译模型,配置优化器、损失函数以及监控指标
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss=tf.keras.losses.categorical_crossentropy,
metrics=['accuracy'])
# 调用tf.keras封装的训练接口,开始训练
model.fit_generator(train_db, epochs=20, validation_data=dev_db)
可以看到,代码非常简洁,短短几行代码就完成了任务。训练20个epoch,模型在开发集上的准确率为98%.
Epoch 18/20
469/469 [==============================] - 5s 12ms/step - loss: 0.0111 - accuracy: 0.9958 - val_loss: 0.0866 - val_accuracy: 0.9797
Epoch 19/20
469/469 [==============================] - 5s 11ms/step - loss: 0.0101 - accuracy: 0.9965 - val_loss: 0.0848 - val_accuracy: 0.9806
Epoch 20/20
469/469 [==============================] - 6s 12ms/step - loss: 0.0069 - accuracy: 0.9974 - val_loss: 0.0917 - val_accuracy: 0.9803
三、使用tf.keras实现一个卷积神经网络(类LeNet结构)
处理图片,还是卷积神经网络更合适一些。接下来让我们用tf.keras实现一个类似于LeNet结构的卷积神经网络。在这之前,为了满足输入要求,先处理一下数据集:
def preprocess_2d(x, y):
"""
预处理数据,将[28,28]reshape为[28,28,1].
"""
x = tf.cast(x, dtype=tf.float32)
# 按像素值的深浅压缩为[0,1]之间
x = x / 255.
# h28,w28,信道1
x = tf.reshape(x, (-1, 28, 28, 1))
# 将标签转成one-hot形式
y = tf.cast(y, dtype=tf.int32)
y = tf.one_hot(y, 10)
return x, y
def load_data_2d(batch_size=128):
"""
加载mnist数据集.返回两个tf.data.Dataset对象,分别为训练数据集和验证数据集。
x.shape==(None,28,28,1),y.shape==(None,10)
"""
# 利用keras下载并加载数据集
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
# 封装成tf.data.Dataset数据集对象
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train))
# 设置每次迭代的mini_batch大小
train_db = train_db.batch(batch_size)
# 对数据进行预处理
train_db = train_db.map(preprocess_2d)
# 打乱数据顺序
train_db = train_db.shuffle(10000)
# 封装数据集对象
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
# 设置mini_batch
test_db = test_db.batch(batch_size)
# 进行数据预处理
test_db = test_db.map(preprocess_2d)
return train_db, test_db
数据处理过程和第一部分的数据处理过程差不多,没什么好说的,只是数据的shape改变了一下。下面开始构建网络:
# -*- coding: utf-8 -*-
# @File : model3.py
# @Author : AaronJny
# @Time : 2019/12/18
# @Desc :
import tensorflow as tf
from data import load_data_2d
# 加载数据集
train_db, dev_db = load_data_2d(128)
# 构建模型
model = tf.keras.Sequential([
# 第一个卷积层,padding保持卷积后图片大小不变,依然是(28,28)
tf.keras.layers.Conv2D(6, (3, 3), padding='same'),
# 添加BN层,将数据调整为均值0,方差1
tf.keras.layers.BatchNormalization(),
# 最大池化层,池化后图片长宽减半
tf.keras.layers.MaxPooling2D((2, 2), 2),
# relu**层
tf.keras.layers.ReLU(),
# 第二个卷积层
tf.keras.layers.Conv2D(16, (5, 5)),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.MaxPooling2D((2, 2), 2),
tf.keras.layers.ReLU(),
# 将节点展平为(None,-1)的形式,以作为全连接层的输入
tf.keras.layers.Flatten(),
# 第一个全连接层,120个节点
tf.keras.layers.Dense(120),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.ReLU(),
# 第二个全连接层
tf.keras.layers.Dense(84),
tf.keras.layers.BatchNormalization(),
tf.keras.layers.ReLU(),
# 输出层,使用softmax**
tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
# 输出网络结构
model.build((None, 28, 28, 1))
model.summary()
# 编译模型
model.compile(tf.keras.optimizers.Adam(0.001), loss=tf.keras.losses.categorical_crossentropy, metrics=['accuracy'])
# 开始训练
model.fit_generator(train_db, epochs=20, validation_data=dev_db)
训练20个epoch,模型在验证集上的准确率为99%。
Epoch 19/20
469/469 [==============================] - 13s 28ms/step - loss: 9.4982e-05 - accuracy: 1.0000 - val_loss: 0.0328 - val_accuracy: 0.9914
Epoch 20/20
469/469 [==============================] - 13s 28ms/step - loss: 7.2764e-05 - accuracy: 1.0000 - val_loss: 0.0330 - val_accuracy: 0.9915
结语
毕竟只是Hello World,没什么难度,仅用来介绍TensorFlow 2.0的简单用法,希望对你有所帮助。
上一篇: Mybatis结果生成键值对的实例代码
下一篇: PHP获取真实客户端的真实IP