欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

机器学习里的Hello World——TensorFlow 2.0在MNIST数据集上的尝试

程序员文章站 2024-03-07 21:49:51
...

首先,TensorFlow 2.0已经正式发布很久啦,TensorFlow 2.0真香~

我刚开始用TensorFlow的时候,还是1.4版本。有一说一,我觉得1.x版本的TensorFlow真心不怎么好用,虽然很灵活,但实现模型太过繁琐,接口很乱,还有很多冗余接口。

后来刚接触到Keras,便觉得这是一股清流,Keras封装的接口非常简洁,你完全可以使用Keras以极快的速度完成模型的构建。但它也有个明显的缺点——因为封装程度比较高,它的灵活性不足,这个时候还是要靠TensorFlow 1.x。

在我快要转投Pytorch阵营的时候,TensorFlow 2.0出来啦~然后……

机器学习里的Hello World——TensorFlow 2.0在MNIST数据集上的尝试

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%.

机器学习里的Hello World——TensorFlow 2.0在MNIST数据集上的尝试

二、使用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的简单用法,希望对你有所帮助。