使用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

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

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

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

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

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

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

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

第七层还是一个全连接层
因为标签一个one-hot向量,我们的输出应该也是一个1110的向量。这里的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。就可以保证输入在二维是2828。输出也是2828。因为卷积层的深度是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 : yzhang.private@gmail.com
# @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()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容