使用tensorflow实现CNN

前言

好久没有更新博客,在之前的博文用代码一步一步完成了手写数字识别,但是在工业应用中不需要这么复杂的实现。我们造车再也不需要自己造轮子。Tensorflow作为当今最流行的机器学习框架,也是Google的亲儿子,对其学习也是有必要性。当然tensorflow也出来很久了,在写本文的时候tensorflow已经是1.8版本。这篇文章没有什么理论知识,因为理论知识早在前面的文章说过了,也用代码实现了一个一个神经元搭建起来的卷积神经网络。本文就是使用tensorflow对一个 CNN 网络的实现,代码思路清晰,可供后续学习参考。

模型功能

这个Model数据来源于大名鼎鼎的Mnist数据集,相信搞机器学习的同学对这个数据集已经不感冒。这个数据集里面有55000张28*28的手写数字(黑白)以及对应的标签。这个数据集数据结构简单,而且有一定数据量,是研究机器学习在图像方面各种分类算法的基础。这个数据集也被tensorflow收录在自己的源码中。所以获取这个数据集相对来说也比较简单。只需要一条命令即可。

from tensorflow.examples.tutorials.mnist import input_data
mnist=input_data.read_data_sets('mnist_data',one_hot=True)#参数一:文件目录。参数二:是否为one_hot向量

这里one_hot为True其实就是说每一条数据对应的标签是一个向量。因为是手写数字,数字只有0,1,2,3,4,5,6,7,8,9
相对应的one-hot就是
0: [1,0,0,0,0,0,0,0,0,0]
1: [0,1,0,0,0,0,0,0,0,0]
2: [0,0,1,0,0,0,0,0,0,0]
3: [0,0,0,1,0,0,0,0,0,0]
4: [0,0,0,0,1,0,0,0,0,0]
5: [0,0,0,0,0,1,0,0,0,0]
6: [0,0,0,0,0,0,1,0,0,0]
7: [0,0,0,0,0,0,0,1,0,0]
8: [0,0,0,0,0,0,0,0,1,0]
9: [0,0,0,0,0,0,0,0,0,1]

这里不详细解释one-hot是什么意思,如果不懂的看客需自行百度。

网络结构

image.png
我们会一层一层的实现图上的网络结构。这里做一个简单的说明。

第一层是输入层
因为输入是一个28*28的黑白图像。所以在二维空间上面是28*28。一个像素点相当于一个神经元。那么输入层的维度就是三维[28,28,1]

第二层为一个卷积层
卷积层的尺寸是5*5,也就是window是5*5。深度为32。也就是说filter的个数是32个。相当于32个window扫描图像。那么图像就会变成28*28*32的图像。(卷积原理自行复习)

第三层是一个池化层。
这里使用的是max pooling。在之前也提到过,池化有点像另一种方式的卷积。也是一个window对图像进行扫描。这里的window的尺寸是2*2。因为池化的原理经过池化之后图像的尺寸会变小但是厚度不会发生变化,输出变成了14*14*32。(池化原理自行复习)

第四层是一个卷积层
与第二层一样不做赘述。

第五层是一个池化层
与第三层一样不做赘述。

第六层是一个全连接层
全连接层的unit的个数为1024
在第五层和第六层之间做了一个flat(平坦化)操作,也就是说把7*7*64的数据压成[7*7*64,1]。也就是3136个
神经元与1024个神经元进行全链接。

第七层还是一个全连接层
因为标签一个one-hot向量,我们的输出应该也是一个1*1*10的向量。这里的unit的个数就是10个。1024个神经元与10个神经元进行全链接。

实现

使用tensorflow搭建神经网络就跟我们小时候搭积木是一样的,不需要关注过多逻辑实现的问题。关注点应该转移到模型的设计上面来,这也是tensorflow这么火的原因。废话不多说我们开始搭积木。
这里要知道使用tensorflow是先“画”出网络结构,之后再进行数据的输入。所以在搭建神经网络的时候不需要关注数据的输入和输出的逻辑。就好比如C\C++这类的非动态语言在真正写业务逻辑之前就要把要用到的变量先定义出来。tensorflow就是先定义出网络结构。

预备

定义输入、输出

input_x=tf.placeholder(tf.float32,[None,28*28])/255.
#输出是一个one hot的向量
output_y=tf.placeholder(tf.int32,[None,10])

#None means tensor 的第一维度可以是任意维度
#/255. 做均一化。因为图像像素值是(0~255)如果用原始数据计算出来的东西会很大。

输入层

由图上可以知道输入是一张张28*28的图像。那么单个输入的维度就是三维[28,28,1] 那么输入层其实就是把数据reshape一下形状。

#输入层 [28*28*1]
input_x_images=tf.reshape(input_x,[-1,28,28,1])

有人肯定会问为什么是4维的,是因为图像的个数我们不知道。那么第一个维度就用-1。python会帮我们算出来第一个维度的数值。

卷积层1

卷积层的卷积核的size是5*5,卷积的深度是32。
这里使用的是tensorflow的 layers.conv2d
这里解释一下几个常用的参数(详情见tensorflow官网的文档)

#conv1 5*5*32
#layers.conv2d parameters
#inputs 输入,是一个张量
#filters 卷积核个数,也就是卷积层的厚度
#kernel_size 卷积核的尺寸
#strides: 扫描步长
#padding: 边边补0 valid不需要补0,same需要补0,为了保证输入输出的尺寸一致,补多少不需要知道
#activation: 激活函数
conv1=tf.layers.conv2d(
    inputs=input_x_images,
    filters=32,
    kernel_size=[5,5],
    strides=1,
    padding='same',
    activation=tf.nn.relu
)

在搭建网络结构的时候我们需要额外注意输入输出的尺寸和维度,因为这是上下层紧密相连的。
这里使用padding=’same’的好处就是tensorflow会帮我们在卷积核补0,不需要我们关注补几圈0。就可以保证输入在二维是28*28。输出也是28*28。因为卷积层的深度是32,那么输出就是[28,28,32] (注意:这里是对单张图片而言的)。真正的维度是四维
[?,28,28,32] ?代表不知道具体数值,需要根据输入,tensorflow帮我们计算。我们也可以在代码运行的时候打印出来conv1的detail.

Tensor("conv2d/Relu:0", shape=(?, 28, 28, 32), dtype=float32)

池化层1

#tf.layers.max_pooling2d
#inputs 输入,张量必须要有四个维度
#pool_size: 过滤器的尺寸

pool1=tf.layers.max_pooling2d(
    inputs=conv1,
    pool_size=[2,2],
    strides=2
)

经过池化操作之后。数据维度变成了
[?,14,14,32]

卷积层2&池化层2

换汤不换药紧接着还是卷积层和池化层。

#conv2 5*5*64
conv2=tf.layers.conv2d(
    inputs=pool1,
    filters=64,
    kernel_size=[5,5],
    strides=1,
    padding='same',
    activation=tf.nn.relu
)

#输出变成了 [?,14,14,64]

#pool2 2*2
pool2=tf.layers.max_pooling2d(
    inputs=conv2,
    pool_size=[2,2],
    strides=2
)

#输出变成了[?,7,7,64]

flat(平坦化)

#flat(平坦化)
flat=tf.reshape(pool2,[-1,7*7*64])
#形状变成了[?,3136]

全连接层

#densely-connected layers 全连接层 1024
#tf.layers.dense
#inputs: 张量
#units: 神经元的个数
#activation: 激活函数
dense=tf.layers.dense(
    inputs=flat,
    units=1024,
    activation=tf.nn.relu
)

dropout

#dropout
#tf.layers.dropout
#inputs 张量
#rate 丢弃率
#training 是否是在训练的时候丢弃
dropout=tf.layers.dropout(
    inputs=dense,
    rate=0.5,
)

输出层

#输出层,不用激活函数(本质就是一个全连接层)
logits=tf.layers.dense(
    inputs=dropout,
    units=10
)
#输出形状[?,10]

OP

网络结构我们搭出来了,就要开始写各种op(操作)。我们已经知道神经网络最重要的就是前向传播和反向传递。
在tensorflow里我们不需要进行复杂的求导然后进行梯度更新,这些tensorflow统统帮我们搞定。

损失

#计算误差 cross entropy(交叉熵),再用Softmax计算百分比的概率
#tf.losses.softmax_cross_entropy
#onehot_labels: 标签值
#logits: 神经网络的输出值
loss=tf.losses.softmax_cross_entropy(onehot_labels=output_y,
                                     logits=logits)

这里的loss其实也是一个张量。

训练OP

这里使用的Adam优化器,也可以使用GD优化器。

#计算误差 cross entropy(交叉熵),再用Softmax计算百分比的概率
#tf.losses.softmax_cross_entropy
#onehot_labels: 标签值
#logits: 神经网络的输出值
loss=tf.losses.softmax_cross_entropy(onehot_labels=output_y,
                                     logits=logits)

准确率OP

#精度。计算预测值和实际标签的匹配程度
#tf.metrics.accuracy
#labels:真实标签
#predictions: 预测值
#Return: (accuracy,update_op)accuracy 是一个张量准确率,update_op 是一个op可以求出精度。
#这两个都是局部变量
accuracy_op=tf.metrics.accuracy(
    labels=tf.argmax(output_y,axis=1),
    predictions=tf.argmax(logits,axis=1)
)[1] #为什么是1 是因为,我们这里不是要准确率这个数字。而是要得到一个op

数据传输

上面我们已经把图和OP都创建了。现在只需要用数据把这些连接起来就可以了。数据传输分两步。

图的初始化

#创建会话
sess=tf.Session()
#初始化变量
#group 把很多个操作弄成一个组
#初始化变量,全局,和局部
init=tf.group(tf.global_variables_initializer(),
              tf.local_variables_initializer())
sess.run(init)

数据流动

for i in range(20000):
    batch=mnist.train.next_batch(50) #从Train(训练)数据集中取‘下一个’样本
    train_loss,train_op_=sess.run([loss,train_op],{input_x:batch[0],output_y:batch[1]})
    if i%100==0:
        test_accuracy=sess.run(accuracy_op,{input_x:test_x,output_y:test_y})
        print("Step=%d, Train loss=%.4f,[Test accuracy=%.2f]"%(i,train_loss,test_accuracy))

#测试: 打印20个预测值和真实值 对
test_output=sess.run(logits,{input_x:test_x[:20]})
inferenced_y=np.argmax(test_output,1)
print(inferenced_y,'Inferenced numbers')#推测的数字
print(np.argmax(test_y[:20],1),'Real numbers')
sess.close()

全部代码

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 5/21/2018 11:21 AM
# @Author  : SkullFang
# @Contact : [email protected]
# @File    : CNN_demo1.py
# @Software: PyCharm

import numpy as np
import tensorflow as tf

#download mnist datasets
#55000 * 28 * 28 55000image
from tensorflow.examples.tutorials.mnist import input_data

mnist=input_data.read_data_sets('mnist_data',one_hot=True)#参数一:文件目录。参数二:是否为one_hot向量

#one_hot is encoding format
#None means tensor 的第一维度可以是任意维度
#/255. 做均一化
input_x=tf.placeholder(tf.float32,[None,28*28])/255.
#输出是一个one hot的向量
output_y=tf.placeholder(tf.int32,[None,10])

#输入层 [28*28*1]
input_x_images=tf.reshape(input_x,[-1,28,28,1])
#从(Test)数据集中选取3000个手写数字的图片和对应标签

test_x=mnist.test.images[:3000] #image
test_y=mnist.test.labels[:3000] #label



#隐藏层
#conv1 5*5*32
#layers.conv2d parameters
#inputs 输入,是一个张量
#filters 卷积核个数,也就是卷积层的厚度
#kernel_size 卷积核的尺寸
#strides: 扫描步长
#padding: 边边补0 valid不需要补0,same需要补0,为了保证输入输出的尺寸一致,补多少不需要知道
#activation: 激活函数
conv1=tf.layers.conv2d(
    inputs=input_x_images,
    filters=32,
    kernel_size=[5,5],
    strides=1,
    padding='same',
    activation=tf.nn.relu
)
print(conv1)

#输出变成了 [28*28*32]

#pooling layer1 2*2
#tf.layers.max_pooling2d
#inputs 输入,张量必须要有四个维度
#pool_size: 过滤器的尺寸

pool1=tf.layers.max_pooling2d(
    inputs=conv1,
    pool_size=[2,2],
    strides=2
)
print(pool1)
#输出变成了[?,14,14,32]

#conv2 5*5*64
conv2=tf.layers.conv2d(
    inputs=pool1,
    filters=64,
    kernel_size=[5,5],
    strides=1,
    padding='same',
    activation=tf.nn.relu
)

#输出变成了  [?,14,14,64]

#pool2 2*2
pool2=tf.layers.max_pooling2d(
    inputs=conv2,
    pool_size=[2,2],
    strides=2
)

#输出变成了[?,7,7,64]

#flat(平坦化)
flat=tf.reshape(pool2,[-1,7*7*64])


#形状变成了[?,3136]

#densely-connected layers 全连接层 1024
#tf.layers.dense
#inputs: 张量
#units: 神经元的个数
#activation: 激活函数
dense=tf.layers.dense(
    inputs=flat,
    units=1024,
    activation=tf.nn.relu
)

#输出变成了[?,1024]
print(dense)

#dropout
#tf.layers.dropout
#inputs 张量
#rate 丢弃率
#training 是否是在训练的时候丢弃
dropout=tf.layers.dropout(
    inputs=dense,
    rate=0.5,
)
print(dropout)

#输出层,不用激活函数(本质就是一个全连接层)
logits=tf.layers.dense(
    inputs=dropout,
    units=10
)
#输出形状[?,10]
print(logits)

#计算误差 cross entropy(交叉熵),再用Softmax计算百分比的概率
#tf.losses.softmax_cross_entropy
#onehot_labels: 标签值
#logits: 神经网络的输出值
loss=tf.losses.softmax_cross_entropy(onehot_labels=output_y,
                                     logits=logits)
# 用Adam 优化器来最小化误差,学习率0.001 类似梯度下降
print(loss)
train_op=tf.train.GradientDescentOptimizer(learning_rate=0.001).minimize(loss)


#精度。计算预测值和实际标签的匹配程度
#tf.metrics.accuracy
#labels:真实标签
#predictions: 预测值
#Return: (accuracy,update_op)accuracy 是一个张量准确率,update_op 是一个op可以求出精度。
#这两个都是局部变量
accuracy_op=tf.metrics.accuracy(
    labels=tf.argmax(output_y,axis=1),
    predictions=tf.argmax(logits,axis=1)
)[1] #为什么是1 是因为,我们这里不是要准确率这个数字。而是要得到一个op

#创建会话
sess=tf.Session()
#初始化变量
#group 把很多个操作弄成一个组
#初始化变量,全局,和局部
init=tf.group(tf.global_variables_initializer(),
              tf.local_variables_initializer())
sess.run(init)

for i in range(20000):
    batch=mnist.train.next_batch(50) #从Train(训练)数据集中取‘下一个’样本
    train_loss,train_op_=sess.run([loss,train_op],{input_x:batch[0],output_y:batch[1]})
    if i%100==0:
        test_accuracy=sess.run(accuracy_op,{input_x:test_x,output_y:test_y})
        print("Step=%d, Train loss=%.4f,[Test accuracy=%.2f]"%(i,train_loss,test_accuracy))

#测试: 打印20个预测值和真实值 对
test_output=sess.run(logits,{input_x:test_x[:20]})
inferenced_y=np.argmax(test_output,1)
print(inferenced_y,'Inferenced numbers')#推测的数字
print(np.argmax(test_y[:20],1),'Real numbers')
sess.close()

猜你喜欢

转载自blog.csdn.net/skullFang/article/details/80434973