【DL笔记5】一文上手TensorFlow,并搭建神经网络实现手写数字识别

从【DL笔记1】到【DL笔记N】,是我学习深度学习一路上的点点滴滴的记录,是从Coursera网课、各大博客、论文的学习以及自己的实践中总结而来。从基本的概念、原理、公式,到用生动形象的例子去理解,到动手做实验去感知,到著名案例的学习,到用所学来实现自己的小而有趣的想法......我相信,一路看下来,我们可以感受到深度学习的无穷的乐趣,并有兴趣和激情继续钻研学习。 正所谓 Learning by teaching,写下一篇篇笔记的同时,我也收获了更多深刻的体会,希望大家可以和我一同进步,共同享受AI无穷的乐趣。


之前又有很长一段时间在讲理论,上次实践还是用python实现Logistic regression。那是一个很有意义的尝试,虽然Logistic regression简单,但是真的亲手手动实现并不容易(我指的是在没有任何框架的加成下),但我们也深刻理解了内部的原理,而这么原理是模型再怎么复杂也不变的。 但是想构建更加复杂的网络,用纯python+numpy恐怕就很不容易了,主要是反向传播,涉及到大量的求导,十分麻烦。针对这种痛点,各种深度学习框架出现了,他们基本上都是帮我们自动地进行反向传播的过程,我们只用把正向传播的“图”构建出来即可。 所以,今天,我会介绍如何用TensorFlow这个深度学习最有名的的框架(之一吧,免得被杠),来实现一个3层的神经网络,来对MNIST手写数字进行识别,并且达到95%以上的测试集正确率

一、TensorFlow的运行机制和基本用法

TensorFlow运行机制:

刚开始接触TensorFlow的同学可能会发现它有点奇怪,跟我们一般的计算过程似乎不同。

首先我们要明确TensorFlow中的几个基本概念:

  • Tensor 张量,是向量、矩阵的延伸,是tf中的运算的基本对象
  • operation 操作,简称op,即加减乘除等等对张量的操作
  • graph 图,由tensor和tensor之间的操作(op)搭建而成
  • session 会话,用于启动图,将数据feed到图中,然后运算得到结果

其他的概念先放一边,我们先搞清楚上面这几个玩意儿的关系。

在TF中构建一个神经网络并训练的过程,是这样的: 先用tensor和op来搭建我们的graph,也就是要定义神经网络的各个参数、变量,并给出它们之间是什么运算关系,这样我们就搭建好了图(graph),可以想象是我们搭建好了一个管道。

然后我们启动session(想象成一个水泵),给参数、变量初始化,并把我们的训练集数据注入到上面构建好的图(graph)中,让我们的数据按照我们搭建好的管道去流动(flow),并得到最终的结果。

一句话,先搭建数据流图,然后启动会话注入数据。TF自动完成梯度下降及相应的求导过程。

TensorFlow基本用法:
  1. 定义变量 一般用下面两种方法来定义: w = tf.Variable(<initial-value>, name=<optional-name>) 或者用: w = tf.get_variable(<name>, <shape>, <initializer>)

我更常用后一种方法,因为可以直接指定initializer来赋值,比如我们常用的Xavier-initializer,就可以直接调用tf.contrib.layers.xavier_initializer(),不用我们自己去写函数进行初始化。

  1. placeholder 我们一般给X、Y定义成一个placeholder,即占位符。也就是在构建图的时候,我们X、Y的壳子去构建,因为这个时候我们还没有数据,但是X、Y是我们图的开端,所以必须找一个什么来代替。这个placeholder就是代替真实的X、Y来进行图的构建的,它拥有X、Y一样的形状。 等session开启之后,我们就把真实的数据注入到这个placeholder中即可。 定义placeholder的方法: X = tf.placeholder(<dtype>,<shape>,<name>)
  2. operation op就是上面定义的tensor的运算。比如我们定义了W和b,并给X定义了一个placeholder,那么Z和A怎么计算呢: Z = tf.matmul(X,W)+b A = tf.nn.relu(Z) 上面两个计算都属于op,op的输入为tensor,输出也为tensor,因此Z、A为两个新的tensor。 同样的,我们可以定义cost,然后可以定义一个optimizer来minimize这个cost(optimizer怎么去minimize cost不用我们操心了,我们不用去设计内部的计算过程,TF会帮我们计算,我们只用指定用什么优化器,去干什么工作即可)。这里具体就留给今天的代码实现了。
  3. session 我们构建了图之后,就知道了cost是怎么计算的,optimizer是如何工作的。 然后我们需要启动图,并注入数据。 启动session有两种形式,本质上是一样的: sess = tf.Session() sess.run(<tensor>,<feed_dic>) ... sess.close() 或者: with tf.Session() as sess: sess.run(<tensor>,<feed_dic>) ... 后者就是会自动帮我们关闭session来释放资源,不用我们手动sess.close(),因为这个经常被我们忘记。

我们需要计算什么,就把相关的tensor写进中去,计算图中的placeholder需要什么数据,我们就用feed_dic={X:…,Y:…}的方法来传进去。具体我们下面的代码实现部分见!

上面就是最基本的TensorFlow的原理和用法了,我们下面开始搭建神经网络!好戏现在开始~

二、开始动手,搭建神经网络,识别手写数字

我们要面对的问题是啥呢?以前银行收到支票呀,都要人工去看上面的金额、日期等等手写数字,支票多了,工作量就很大了,而且枯燥乏味。那我们就想,能不能用机器是识别这些数字呢?

深度学习领域的大佬Yann LeCun(CNN的发明者)提供了一个手写数字数据集MNIST,可以说是深度学习的hello world了。数字长这样:

其中每个图片的大小是 28×28,我们的 数据集已经将图片给扁平化了,即由28×28,压扁成了784,也就是输入数据X的维度为784.

我们今天就设计一个简单的 3-layer-NN,让识别率达到95%以上。 假设我们的网络结构是这样的: 第一层 128个神经元,第二层 64个神经元,第三层是 Softmax输出层,有 10个神经元,因为我们要识别的数组为0~9,共10个。网络结构如下(数字代表维度):

好了,我们下面一步步地实现:

(1)加载数据,引入相关的包

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
# 下面这一行代码就可以直接从官网下载数据,下载完之后,你应该可以在目录中发现一个新文件夹“MNIST_data”
from tensorflow.examples.tutorials.mnist import input_data 
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

下面我们从数据集中,把我们的训练集、测试集都导出:

X_train,Y_train = mnist.train.images,mnist.train.labels
X_test,Y_test = mnist.test.images,mnist.test.labels
# 不妨看看它们的形状:
print(X_train.shape)  # (55000, 784)
print(Y_train.shape)  # (55000, 10)
print(X_test.shape)   # (10000, 784)
print(Y_test.shape)   # (10000, 10)

可以看出,我们的训练样本有55000个,测试集有10000个。

(2)根据网络结构,定义各参数、变量,并搭建图(graph)

tf.reset_default_graph() # 这个可以不用细究,是为了防止重复定义报错

# 给X、Y定义placeholder,要指定数据类型、形状:
X = tf.placeholder(dtype=tf.float32,shape=[None,784],name='X')
Y = tf.placeholder(dtype=tf.float32,shape=[None,10],name='Y')

# 定义各个参数:
W1 = tf.get_variable('W1',[784,128],initializer=tf.contrib.layers.xavier_initializer())
b1 = tf.get_variable('b1',[128],initializer=tf.zeros_initializer())
W2 = tf.get_variable('W2',[128,64],initializer=tf.contrib.layers.xavier_initializer())
b2 = tf.get_variable('b2',[64],initializer=tf.zeros_initializer())
W3 = tf.get_variable('W3',[64,10],initializer=tf.contrib.layers.xavier_initializer())
b3 = tf.get_variable('b3',[10],initializer=tf.zeros_initializer())

这里需要说明的有几点呢:

  1. 最好给每个tensor 都取个名字(name属性),这样报错的时候,我们可以方便地知道是哪个
  2. 形状的定义要一致,比如这里的W的形状,我们之前在讲解某些原理的时候,使用的是(当前层维度,上一层维度),但是 这里我们采用的是(上一层维度,当前层维度),所以分别是(784,128),(128,64),(64,10). 另外,X、Y的维度中的None,是样本数,由于我们同一个模型不同时候传进去的样本数可能不同,所以这里可以写 None,代表可变的
  3. W的初始化,可以直接用tf自带的initializer,但是注意不能用0给W初始化,这个问题我在之前的“参数初始化”的文章里面讲过。b可以用0初始化。

接着,我们根据上面的变量,来 计算网络中间的logits(就是我们常用的Z)、激活值

A1 = tf.nn.relu(tf.matmul(X,W1)+b1,name='A1')
A2 = tf.nn.relu(tf.matmul(A1,W2)+b2,name='A2')
Z3 = tf.matmul(A2,W3)+b3

为什么我们只用算到Z3就行了呢,因为TensorFlow中,计算损失有专门的函数,一般都是直接用Z的值和标签Y的值来计算,比如

对于sigmoid函数,我们有: tf.nn.sigmoid_cross_entropy_with_logits(logits=,labels=)来计算,

对于Softmax,我们有: tf.nn.softmax_cross_entropy_with_logits(logits=,labels=)来计算。 这个logits,就是未经激活的Z;labels,就是我们的Y标签。

因此我们如何 定义我们的cost呢:

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=Z3,labels=Y))

注意,为什么要用 reduce_mean()函数呢?因为经过softmax_cross_entropy_with_logits计算出来是,是所有样本的cost拼成的一个向量,有m个样本,它就是m维,因此我们需要去平均值来获得一个整体的cost。

定义好了cost,我们就可以 定义optimizer来minimize cost了:

trainer = tf.train.AdamOptimizer().minimize(cost)

也是一句话的事儿,贼简单了。这里我们采用Adam优化器,用它来minimize cost。当然,我们可以在AdamOptimizer()中设置一些超参数,比如leaning_rate,但是这里我直接采用它的默认值了,一般效果也不错。

至此,我们的整个计算图,就搭建好了,从X怎么一步步的加上各种参数,并计算cost,以及optimizer怎么优化cost,都以及明确了。接下来,我们就可以启动session,放水了!

(3)启动图,注入数据,进行迭代

废话不多说,直接上代码:

with tf.Session() as sess:
    # 首先给所有的变量都初始化(不用管什么意思,反正是一句必须的话):
    sess.run(tf.global_variables_initializer())

    # 定义一个costs列表,来装迭代过程中的cost,从而好画图分析模型训练进展
    costs = []

    # 指定迭代次数:
    for it in range(1000):
        # 这里我们可以使用mnist自带的一个函数train.next_batch,可以方便地取出一个个地小数据集,从而可以加快我们的训练:
        X_batch,Y_batch = mnist.train.next_batch(batch_size=64)

        # 我们最终需要的是trainer跑起来,并获得cost,所以我们run trainer和cost,同时要把X、Y给feed进去:
        _,batch_cost = sess.run([trainer,cost],feed_dict={X:X_batch,Y:Y_batch})
        costs.append(batch_cost)

        # 每100个迭代就打印一次cost:
        if it%100 == 0:
            print('iteration%d ,batch_cost: '%it,batch_cost)

    # 训练完成,我们来分别看看来训练集和测试集上的准确率:
    predictions = tf.equal(tf.argmax(tf.transpose(Z3)),tf.argmax(tf.transpose(Y)))
    accuracy = tf.reduce_mean(tf.cast(predictions,'float'))
    print("Training set accuracy: ",sess.run(accuracy,feed_dict={X:X_train,Y:Y_train}))
    print("Test set accuracy:",sess.run(accuracy,feed_dict={X:X_test,Y:Y_test}))

运行,查看输出结果:

iteration0 ,batch_cost:  2.3507476
iteration100 ,batch_cost:  0.32707167
iteration200 ,batch_cost:  0.571893
iteration300 ,batch_cost:  0.2989539
iteration400 ,batch_cost:  0.1347334
iteration500 ,batch_cost:  0.24421218
iteration600 ,batch_cost:  0.13563904
iteration700 ,batch_cost:  0.26415896
iteration800 ,batch_cost:  0.1695988
iteration900 ,batch_cost:  0.17325541
Training set accuracy:  0.9624182
Test set accuracy:  0.9571

嚯,感觉不错!训练很快,不到5秒,已经达到我们的要求了,而且我们仅仅是迭代了1000次啊。

我们不妨将结果可视化一下,随机抽查一些图片,然后输出对应的预测: 将下列代码放到上面的session中(不能放在session外部,否则没法取出相应的值),重新运行:

    # 这里改了一点上面的预测集准确率的代码,因为我们需要知道预测结果,所以这里我们单独把Z3的值给取出来,这样通过分析Z3,即可知道预测值是什么了。
    z3,acc = sess.run([Z3,accuracy],feed_dict={X:X_test,Y:Y_test})
    print("Test set accuracy:",acc)

    # 随机从测试集中抽一些图片(比如第i*10+j张图片),然后取出对应的预测(即z3[i*10+j]):
    fig,ax = plt.subplots(4,4,figsize=(15,15))
    fig.subplots_adjust(wspace=0.1, hspace=0.7)
    for i in range(4):
        for j in range(4):
            ax[i,j].imshow(X_test[i*10+j].reshape(28,28))
            # 用argmax函数取出z3中最大的数的序号,即为预测结果:
            predicted_num  = np.argmax(z3[i*10+j])        
            # 这里不能用tf.argmax,因为所有的tf操作都是在图中,没法直接取出来
            ax[i,j].set_title('Predict:'+str(predicted_num))
            ax[i,j].axis('off')

得到结果:

可见,我们的模型是真的训练出来了,而且效果不错。这个图中,右下角的那个奇怪的“4”都给识别出来了。唯一有争议的是第三排第三个的那个数字,我感觉是4,不过也确实有点像6,结果模式识别它为6。

总的来说还是很棒的,接下来我觉得增大迭代次数,迭代它个10000次!然后看更多的图片(100张图片)。效果如下:

可见,准确率提高到了97%以上! 再展示一下图片:

至此,我们的实验就完成了。我们成功地利用TensorFlow搭建了一个三层神经网络,并对手写数字进行了出色的识别!


对于TensorFlow更丰富更相信的使用,大家可以去TensorFlow中文社区或者TensorFlow官网了解。这里也推荐大家试试TensorFlow的高度封装的api——Keras,也是一个深度学习框架,它可以更加轻松地搭建一个网络。之后的文章我也会介绍keras的使用。


原文发布于微信公众号 - SimpleAI(SimpleAI_1)

原文发表时间:2018-09-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏AI研习社

从原理到实战 英伟达教你用PyTorch搭建RNN(下)

编者按:本文为《从原理到实战 英伟达教你用PyTorch搭建RNN》的下篇,阅读上篇请点击这里。文章原载于英伟达博客,AI 研习社编译。 ? 代码实操 在开...

4074

社交图中的社区检测

在进行社交网络分析时,一个常见的问题是如何检测社区,如相互了解或者经常互动的一群人。社区其实就是连通性非常密集的图的子图。

7388
来自专栏大数据挖掘DT机器学习

如何使用sklearn进行数据挖掘?

1.1 数据挖掘的步骤 数据挖掘通常包括数据采集,数据分析,特征工程,训练模型,模型评估等步骤。使用sklearn工具可以方便地进行特征工程和模型训练工作,在...

3716
来自专栏大数据文摘

一行R代码实现繁琐的可视化

21911
来自专栏机器学习实践二三事

机器学习基本概念-4

Hyperparameters 在ML中,我们常说的就是train,但是实际什么是train呢? 通俗点说,就是学习参数(hyperparameters) ...

2096
来自专栏数据科学学习手札

(数据科学学习手札08)系统聚类法的Python源码实现(与Python,R自带方法进行比较)

聚类分析是数据挖掘方法中应用非常广泛的一项,而聚类分析根据其大体方法的不同又分为系统聚类和快速聚类,其中系统聚类的优点是可以很直观的得到聚类数不同时具体类中包括...

2665
来自专栏北京马哥教育

Python数据挖掘:Kmeans聚类数据分析及Anaconda介绍

糖豆贴心提醒,本文阅读时间8分钟 今天我们来讲一个关于Kmeans聚类的数据分析案例,通过这个案例让大家简单了解大数据分析的基本流程,以及使用Python实现...

49013
来自专栏新智元

Facebook开源PyTorch版本fairseq翻译模型,训练速度提高50%

【新智元导读】FAIR的开源序列到序列(sequence-to-sequence)引擎现在可以在PyTorch使用了。FAIR今天发布了fairseq-py,这...

47411
来自专栏机器之心

资源 | 让手机神经网络速度翻倍:Facebook开源高性能内核库QNNPACK

为了将最新的计算机视觉模型部署到移动设备中,Facebook 开发了一个用于低密度卷积的优化函数库——QNNPACK,用在最佳神经网络中。

1044
来自专栏CreateAMind

keras中文文档

Keras是一个极简和高度模块化的神经网络库,Keras由纯Python编写而成并基于Theano或Tensorflow。Keras 为支持快速实验而生,如果你...

2355

扫码关注云+社区