4 months ago

本文采用mnist数据集、单层神经网络实现几种梯度下降方法:批量梯度下降BGD、随机梯度下降SGD、小批量梯度下降MBGD

这三种分类依据是:每次迭代使用的数据量的大小
在梯度下降计算过程中的技术细节不同,也衍生出很多梯度下降方法

tf.train.GradientDescentOptimizer
tf.train.AdagradOptimizer
tf.train.MomentumOptimizer
tf.train.RMSPropOptimizer
tf.train.AdadeltaOptimizer
tf.train.AdamOptimizer

这些是方法论,相当于汽车不同型号的发动机,而我们今天讨论的是发动机每转一圈需要给它添加多少燃油的问题,不要混淆
具体理论细节详见参考资料,这里做简单代码实现

1.mnist API介绍

对于mnist数据集,已经有各种介绍,这里不做赘述,仅仅介绍其tensorflow使用方法

1)官网下载以下四个数据集,存放到同一个目录,路径记为filepath

train-images-idx3-ubyte.gz: training set images (9912422 bytes)
train-labels-idx1-ubyte.gz: training set labels (28881 bytes)
t10k-images-idx3-ubyte.gz: test set images (1648877 bytes)
t10k-labels-idx1-ubyte.gz: test set labels (4542 bytes)

2)数据集导入

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets(filepath, one_hot=True) #指定刚刚下载的数据集存放路径

3)方法调用

mnist.train.images #训练集图片(特征值)
mnist.train.labels #训练集标签(目标值)
mnist.test.images #测试集图片(特征值)
mnist.test.labels #测试集标签(目标值)
mnist.train.next_batch(100) #提供批量获取功能:第一次调用前100个数据,第二次接下来调用后面100个数据,依此类推

2.批量梯度下降(Batch Gradient Descent,BGD)

常规使用方法,每次梯度迭代使用所有的训练集,关键点在于求和


计算开销大,但是收敛曲线一直朝着减小的方向,迭代次数相对SGD要少
这里用单层神经网络(只有一个输出层)做简单实现

import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets(filepath, one_hot=True) #指定刚刚下载的数据集存放路径
# 特征值占位
x=tf.placeholder(tf.float32,[None,784])
# 目标值占位
true=tf.placeholder(tf.float32,[None,10])
# 初始化权重
w=tf.Variable(tf.random_normal([784,10]))
b=tf.Variable(tf.random_normal([10,]))
# 线性操作
l=tf.matmul(x,w)+b
# 损失函数
loss=tf.reduce_sum(tf.nn.softmax_cross_entropy_with_logits(labels=true,logits=l))
# 优化器
opti=tf.train.GradientDescentOptimizer(0.001).minimize(loss)
# 选用全部数据集进行训练
input=mnist.train.images
output=mnist.train.labels
# 创建会话,开始训练
sess=tf.Session()
sess.run(tf.global_variables_initializer())
loss_map=[]
for i in range(100):
    sess.run(opti,{x:input,true:output})
    loss_map.append(sess.run(loss,{x:input,true:output}))
# 展示梯度下降过程
plt.plot([i for i in range(len(loss_map))],loss_map)
plt.show()
# 计算准确率
predict=tf.nn.softmax(l)
temp=tf.equal(tf.arg_max(predict,1),tf.arg_max(true,1))
temp=tf.cast(temp,tf.float32)
accurancy=tf.reduce_mean(temp)
print("准确率为:",sess.run(accurancy,{x:mnist..images,true:mnist.train.labels}))
sess.close()

运行结果如下


运算过程虽然波折,但是还是一直朝着损失函数下降的方向

3.随机梯度下降(Stochastic Gradient Descent,SGD)

随机梯度下降是每次迭代使用一个样本(不需要求和了)来对参数进行更新,使得训练速度加快。

import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets(filepath, one_hot=True) #指定刚刚下载的数据集存放路径
# 特征值占位
x=tf.placeholder(tf.float32,[None,784])
# 目标值占位
true=tf.placeholder(tf.float32,[None,10])
# 初始化权重
w=tf.Variable(tf.random_normal([784,10]))
b=tf.Variable(tf.random_normal([10,]))
# 线性操作
l=tf.matmul(x,w)+b
# 损失函数
loss=tf.reduce_sum(tf.nn.softmax_cross_entropy_with_logits(labels=true,logits=l))
# 优化器
opti=tf.train.GradientDescentOptimizer(0.001).minimize(loss)
# 选用全部数据集进行训练
input=mnist.train.images
output=mnist.train.labels
# 创建会话,开始训练
sess=tf.Session()
sess.run(tf.global_variables_initializer())
loss_map=[]
sess=tf.Session()
sess.run(tf.global_variables_initializer())
# 记录损失函数
loss_map=[]
for i in range(100):
        #每次只训练一个样本
    input1=np.reshape(input[i,:],(-1,784))
    output1=np.reshape(output[i,:],(-1,10))
    sess.run(opti,{x:input1,true:output1})
    loss_map.append(sess.run(loss,{x:input1,true:output1}))
    
plt.figure(figsize=(30,20))
plt.plot([i for i in range(len(loss_map))],loss_map)
plt.show()
predict=tf.nn.softmax(l)
temp=tf.equal(tf.arg_max(predict,1),tf.arg_max(true,1))
temp=tf.cast(temp,tf.float32)
accurancy=tf.reduce_mean(temp)
print("准确率为:",sess.run(accurancy,{x:mnist.test.images,true:mnist.test.labels}))


从损失函数变化过程来看,一会儿变大,一会儿变小,完全处于随机状态
所以随机梯度下降“随机”的含义在这里,并不是每次迭代随机选取一个样本

所以需要更多的迭代次数,从而保证总体趋势处于下降状态
迭代次数取10000次试一下


额,结果依然是糟糕,这下彻底明白了随机的定义

4、小批量梯度下降(Mini-Batch Gradient Descent, MBGD)

对批量梯度下降以及随机梯度下降的一个折中办法。其思想是:每次迭代,使用batch_size个样本来对参数进行更新。

import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets(filepath, one_hot=True) #指定刚刚下载的数据集存放路径
# 特征值占位
x=tf.placeholder(tf.float32,[None,784])
# 目标值占位
true=tf.placeholder(tf.float32,[None,10])
# 初始化权重
w=tf.Variable(tf.random_normal([784,10]))
b=tf.Variable(tf.random_normal([10,]))
# 线性操作
l=tf.matmul(x,w)+b
# 损失函数
loss=tf.reduce_sum(tf.nn.softmax_cross_entropy_with_logits(labels=true,logits=l))
# 优化器
opti=tf.train.GradientDescentOptimizer(0.001).minimize(loss)
# 选用全部数据集进行训练
input=mnist.train.images
output=mnist.train.labels
# 创建会话,开始训练
sess=tf.Session()
sess.run(tf.global_variables_initializer())
loss_map=[]
sess=tf.Session()
sess.run(tf.global_variables_initializer())
# 记录损失函数
loss_map=[]
#定义每次传入的数据数量
batch_size=100
#跑完全部训练集(这个过程称为epoch)需要多少个batch_size
num=int(mnist.train.images.shape[0]/batch_size)
for i in range(1000):#跑1000次epoch(即训练集训练1000次)
    for i in range(num):
            # 内置next_batch方法,默认从训练集中按顺序选择batch_size个数据
        input,output=mnist.train.next_batch(batch_size)
        sess.run(opti,{x:input,true:output})
        loss_map.append(sess.run(loss,{x:input,true:output}))
        
plt.plot([i for i in range(len(loss_map))],loss_map)
plt.show()
# 计算准确率
predict=tf.nn.softmax(l)
temp=tf.equal(tf.arg_max(predict,1),tf.arg_max(true,1))
temp=tf.cast(temp,tf.float32)
accurancy=tf.reduce_mean(temp)
print("准确率为:",sess.run(accurancy,{x:mnist.test.images,true:mnist.test.labels}))


收敛情况依然是混乱,好在准确率有了很大提升,主要是迭代次数足够多(55000/1000x100=5500)

5.小结

BGD:每次55000个数据进行训练,迭代100次,准确率50%
SGD:每次1个数据进行训练,迭代10000次,准确率10%
MBGD:每次1000个数据进行训练,迭代100次,准确率90%
由此可见:MBGD的计算开销适中,准确率最高

参考资料:
批量梯度下降(BGD)、随机梯度下降(SGD)以及小批量梯度下降(MBGD)的理解
THE MNIST DATABASE
卷积神经网络训练三个概念(epoch,迭代次数,batchsize)

 
4 months ago

这里仅仅介绍搭建神经网络需要使用的函数,尚且属于“术”的层面
至于“道”的层面,即网络结构的设计,调参及优化,目前看起来更像是老中医的经验之谈,整个行业尚且还在探索阶段,暂且按下不表

第1步:网络的输入

神经网络的输入可以用占位符声明

tf.placeholder(dtype,shape=None,name=None)

第2步:搭建网络

1)全连接神经网络:主要使用矩阵乘法和加法

(1)矩阵乘法
tf.matmul(a,b,transpose_a=False,transpose_b=False,adjoint_a=False,adjoint_b=False,
a_is_sparse=False,b_is_sparse=False,name=None)
(2)矩阵加法
tf.math.add(x,y,name=None)

当然,乘法不需要这么复杂,直接用 + 就可以了

(3)为了防止过拟合,可以添加dropout操作(用于全连接层,包括全连接神经网络的全连接层;一般用于层级深的网络)
tf.nn.dropout(x,keep_prob,noise_shape=None,seed=None,name=None)
(4)激活函数
tf.sigmoid(x, name=None)
tf.tanh(x, name=None)
tf.nn.relu(features, name=None)
tf.nn.leaky_relu(features,alpha=0.2,name=None)
tf.nn.relu6(features, name=None)
tf.nn.crelu(features, name=None)
tf.nn.selu(features, name=None)
tf.nn.softplus(features, name=None)
tf.nn.softsign(features,name=None)

一般不列出多分类激活函数tf.nn.softmax(),因为在使用softmax的交叉熵损失函数时会自动激活softmax,无需单独激活

2)卷积神经网络:卷积、池化、加法

(1)卷积函数
tf.nn.conv2d(input,filter,strides,padding,use_cudnn_on_gpu=True,data_format='NHWC',
dilations=[1, 1, 1, 1],name=None)
(2)池化函数
tf.nn.max_pool(value,ksize,strides,padding,data_format='NHWC',name=None)
tf.nn.avg_pool(value,ksize,strides,padding,data_format='NHWC',name=None)
(3)为了防止过拟合,可以添加dropout操作(用于全连接层,包括全连接神经网络的全连接层;一般用于层级深的网络)
tf.nn.dropout(x,keep_prob,noise_shape=None,seed=None,name=None)
(4)为了加快收敛速度,可以添加BN操作(常用在激活函数前)
tf.nn.moments(x,axes,shift=None,name=None,keep_dims=False)
tf.nn.batch_normalization(x,mean,variance,offset,scale,variance_epsilon,name=None)

这两个函数必须先后同时使用

(5)激活函数

同上

第3步:创建损失函数

根据第2步搭建的网络,利用网络的输出值构造合适的损失函数,常用的三个函数如下:

tf.math.reduce_sum(input_tensor,axis=None,keepdims=None,name=None,
reduction_indices=None,keep_dims=None)
tf.nn.sigmoid_cross_entropy_with_logits(_sentinel=None,labels=None,logits=None,name=None)
tf.nn.softmax_cross_entropy_with_logits(_sentinel=None,labels=None,logits=None,dim=-1,name=None)

第4步:选择优化器(即梯度下降法)

常用的梯度下降函数有:

tf.train.GradientDescentOptimizer
tf.train.AdagradOptimizer
tf.train.MomentumOptimizer
tf.train.RMSPropOptimizer
tf.train.AdadeltaOptimizer
tf.train.AdamOptimizer

第5步:评估模型准确率

1)对比真实值和预测值

tf.math.equal(x,y,name=None)

使用对比函数的返回值是bool型,需要转换成数值型(全部转换成0,1)

2)类型转换

tf.cast(input,type,name=None)

3)利用平均值函数计算准确率

tf.math.reduce_mean(input_tensor,axis=None,keepdims=None,name=None,
reduction_indices=None,keep_dims=None)

第6步:保存和加载模型

创建类 tf.train.Saver()
利用其方法 save()保存模型
模型加载时,利用其方法restore()加载到内存

参考资料:
张平:《图解深度学习与神经网络》
tensorflow官方API
Tensorflow之神经网络nn模块详解

 
4 months ago

基础学习首推纸质书籍,更加适合深度思考,后期可以使用在线资料做补充和持续学习

一、书籍

1.入门级第一本:Python神经网络编程


这本书使用大量的篇幅讲解与函数导数相关的知识, 甚至达到啰嗦的程度.
对数学基础薄弱同学来说, 简直就是雪中送炭. 只需要非常少的数学知识就可以做出一个 hello world 版的神经网络.
看这本书主要是为了建立信心. AI研习社免费下载

2.入门第二本: 深度学习入门 基于Python的理论与实现


这本书的反向传播部分写得非常好, 避开了枯燥的公式推导, 使用计算图的形式, 用最少的脑细胞就说明了反向传播的基本原理.
AI研习社免费下载

3.入门第三本: CS231n 斯坦福李飞飞深度视觉识别课程[公开课]

这个视频资料最有价值的地方是代码实现. 所有的理论都有非常优秀的配套代码.
这些代码是以作业的方式发布的, GitHub上有大量的优秀答案.推荐阅读Burton2000同学的版本

书的使用方法:
以上资料都要到手, 要一起使用.
按照顺序快速通读一遍, 不要求一遍读懂, 有个印象, 知道相关知识点在哪里就可以.
这些资料各有侧重点, 学到某个知识点, 总有一个资料讲的比另一个更详细, 更通俗.

二、免费GPU

1.谷歌云

谷歌云平台提供付费GPU、TPU,但充值后有300美金送,而且用完后据说还可以再领两次,相当于免费GPU了
可惜现在注册越来越严格,已经不支持大陆带“银联”标志的信用卡,应该是大陆羊毛党太厉害了吧

2,Colab 官网

这个也是谷歌的产品,完全免费提供GPU、TPU,通用谷歌账号,完全是jupyter notebook操作模式

三、在线课程

网上课程现在漫天飞,质量良莠不齐,对于“万门大学”这种,貌似课程很多,但瞄了几眼他们但视频后难免嗤之以鼻
吴恩达、李飞飞的课程固然经典,但是太老气,推荐一下自己看过的“年轻”而且还不错的课程
英文不要怕,Chrome浏览器 + GoogleTranslate插件 了解一下

1.fastai 官网

纯净版 无广告 高质量 完全免费 课程,特别适合海内外广大客户

四、软件、框架

至于软件和框架:起步阶段学一半都够了, 多了别学, 遇到不懂再补.

1.基础软件:python

纯 Python 对于入门深度学习中的理论部分来说非常重要, 因为可以对自己推导的公式用代码验证.

2.工具包:numpy、matplotlib

3.框架:TensorFlow 和 PyTorch

在实际的学习过程中, 建议配套 TensorFlow 和 PyTorch 一起学习,这两个框架相关资料太多啦,都很好
Python 用于理解公式; 框架用于加速模型训练, 快速查看结果.
至于其他都框架,简答介绍如下:

五、补充编程资料

有详细讲解框架中张量运算方法的资料是比较推荐的, 主要是方便与纯 Python 代码相互对比验证. 这里推荐两本 :

1.张平 <图解深度学习与神经网络:从张量到TensorFlow实现图解深度学习与神经网络>


数学大神,几乎所有的数学公式都给出了配套的 数字 计算例子, 非常难得. 强烈推荐入手, 对于理解基础理论非常有帮助
有一章介绍了几个经典的神经网络结构,简介通俗易懂,特别好
最后一章寥寥两页总结出构建神经网络的基本顺序和步骤,是对全书最好的总结,足见作者的细心和功底

2.肖智清 <神经网络与PyTorch实战>

六、理论补充

上面的推荐, 刻意的回避了有关机器学习的内容, 主要是为了加快入门速度.
如果觉得某个知识点怎么都看不懂, 应该是你缺失了某个关键前提知识点.
你可以略过不学, 也可以参考这 5 本作为补充 :
1.李航 <统计学习方法>
2.周志华 <机器学习>
3.Ian, Goodfellow <深度学习>
4.诸葛越,葫芦娃 <百面机器学习 算法工程师带你去面试>
5.猿辅导研究团队 <深度学习核心技术与实践>

七、进阶篇:美国在读研究生课本

1.An Introduction to Statistical Learning

Gareth James, Daniela Witten, Trevor Hastie, Robert Tibshirani

2.Pattern Recognition and Machine Learning

Christopher Bishop

3.Artificial Intelligence - A Modern Approach

Stuart Russell and Peter Norvig

4.Machine Learning - An Algorithmic Perspective

Stephen Marsland

5.Deep Learning

Ian Goodfellow, Joshua Bendigo, and Aaron Courville

6.Introduction to Time Series and Forecasting

Peter Brockwell and Richard Davis

参考资料:
自学深度学习之计算机视觉的入门资料推荐
深度学习简介
比Jupyter还好用的Google数据分析神器Colab
Colab提供了免费TPU,机器之心帮你试了试
Machine Learning Books for Beginners
Deep Learning Frameworks 2019
为你推荐一份深度学习书单,一起来学习吧

 
4 months ago

一、背景介绍

GoogleNet和VGG是2014年ImageNet竞赛的双雄,这两类模型结构有一个共同特点是go deeper,即网络层级加深
vgg继承了LeNet以及AlexNet的一些框架
GoogleNet做了更大胆的网络上的尝试,该模型虽然有22层,但大小却比alexnet和vgg都小很多(参数量仅为AlexNet的1/12),性能优越

通常,获得高质量模型最保险的做法就是增加模型的深度(层数)或者是其宽度(每一层卷积核核或者神经元数)
但是这里一般设计思路的情况下会出现三种缺陷:
1.参数太多,若训练数据集有限,容易过拟合,;
2.网络越大计算复杂度越大,难以应用;
3.网络越深,梯度越往后穿越容易消失,难以优化模型

googlenet的主要思想就是围绕着提高模型质量的两个方向同时又规避三种风险的思路去做的:
1.深度:层数更深,采用了22层
为了避免上述提到的梯度消失问题,googlenet巧妙的在不同深度处增加了两个loss来保证梯度回传消失的现象。

2.宽度:增加了多种核1x1,3x3,5x5,还有直接max pooling的
但是如果简单的将这些应用到feature map上的话,concat起来的feature map厚度将会很大
所以在googlenet中为了避免这一现象提出的Inception Module:在3x3前,5x5前,max pooling后分别加上了1x1的卷积核起到了降低feature map厚度的作用。

二、GoogleNet网络结构

下面采用层层推进的方式介绍GoogleNet网络结构

1.Inception Module:网中网结构

GoogleNet最令人称道的就是它的Inception Module(网中网NIN network in network)结构
整个网络由9个这样的Inception Module构成
下面一张简单的图介绍NIN结构


卷积层采用不同尺寸规格的卷积核,增加特征提取的多样性,然后把不同的卷积结果进行深度上的“连接”
举个例子理解起来更直观

# -*- coding: utf-8 -*-
import tensorflow as tf
#"1个高3宽3深度2(3x3x2)的输入张量"
inputTensor=tf.constant(
        [
        [
        [[2,5],[3,3],[8,2]],
        [[6,1],[1,2],[5,4]],
        [[7,9],[2,-3],[-1,3]]
        ]
        ],tf.float32
        )
#
session=tf.Session()
#"3个高1宽1深度2(1x1x2)的卷积核"
filter112_3=tf.constant(
        [
        [[[1,2,-1],[-1,-2,2]]]
        ],tf.float32
        )
result1=tf.nn.conv2d(inputTensor,filter112_3,[1,1,1,1],'SAME')
print(session.run(result1))
#"2个高2宽2深度2(2x2x2)的卷积核"
filter222_2=tf.constant(
        [
        [[[3,-1],[1,2]],[[-2,1],[2,3]]],
        [[[-1,1],[-3,7]],[[4,2],[5,4]]]
        ],tf.float32)
result2=tf.nn.conv2d(inputTensor,filter222_2,[1,1,1,1],'SAME')
print(session.run(result2))
#"最大池化"
maxPool_33=tf.nn.max_pool(inputTensor,[1,3,3,1],[1,1,1,1],'SAME')
print(session.run(maxPool_33))
#"深度方向上拼接"
result=tf.concat([result1,result2,maxPool_33],3)
print(session.run(result))

2.在卷积层添加并行pooling用于提高效率

3.采用1x1的卷积核减少计算成本

直接导致GoogleNet该模型虽然有22层,但大小却比alexnet和vgg都小很多(参数量仅为AlexNet的1/12,当然也有很大一部分原因是去掉了全连接层),性能优越

4.Batch Normalization(批量归一化BN)

对输入数据做了归一化处理,就是将每个特征在所有样本上的值转归一化成均值0方差1。
这样我们保证训练数据里数值都同样量级上,从而使得训练的时候数值更加稳定。

对于浅层模型来说,通常数据归一化预处理足够有效。输出数值在只经过几个神经层后通常不会出现剧烈变化。
但对于深层神经网络来说,情况一般比较复杂。因为每一层里都对输入乘以权重后得到输出。
当很多层这样的相乘累计在一起时,一个输出数据较大的改变都可以导致输出产生巨大变化,从而带来不稳定性。

批量归一化层的提出是针对这个情况。它将一个批量里的输入数据进行归一化然后输出。
如果我们将批量归一化层放置在网络的各个层之间,那么就可以不断的对中间输出进行调整,从而保证整个网络的中间输出的数值稳定性。

Batch Normalization批量归一化(简称BN)是由著名的GoogleNet首次提出
它有效地解决了在网络层数很深地情况下,收敛速度很慢地问题
使用BN操作可以加大网络梯度下降的学习率,加速网络收敛
在实际应用中,批量归一化的收敛非常快,并且具有很强的泛化能力,某种情况下完全可以替代正则化和弃权。

BN的地位:与激活函数层、卷积层、全连接层、池化层一样

BN的本质原理:在网络的每一层输入的时候(通常在激活函数前),也就是先做一个归一化处理(归一化至:均值0、方差为1)

GoogleNet的完整结构如下


参考资料:
GoogLeNet学习心得

 
4 months ago

1.引入

对输入数据做了归一化处理,就是将每个特征在所有样本上的值转归一化成均值0方差1。
这样我们保证训练数据里数值都同样量级上,从而使得训练的时候数值更加稳定。
对于浅层模型来说,通常数据归一化预处理足够有效。输出数值在只经过几个神经层后通常不会出现剧烈变化。
但对于深层神经网络来说,情况一般比较复杂。因为每一层里都对输入乘以权重后得到输出。
当很多层这样的相乘累计在一起时,一个输出数据较大的改变都可以导致输出产生巨大变化,从而带来不稳定性。

批量归一化层的提出是针对这个情况。它将一个批量里的输入数据进行归一化然后输出。
如果我们将批量归一化层放置在网络的各个层之间,那么就可以不断的对中间输出进行调整,从而保证整个网络的中间输出的数值稳定性。

Batch Normalization批量归一化(简称BN)是由著名的GoogleNet首次提出
它有效地解决了在网络层数很深地情况下,收敛速度很慢地问题
使用BN操作可以加大网络梯度下降的学习率,加速网络收敛
在实际应用中,批量归一化的收敛非常快,并且具有很强的泛化能力,某种情况下完全可以替代正则化和弃权。

BN的地位:与激活函数层、卷积层、全连接层、池化层一样

BN的本质原理:在网络的每一层输入的时候(通常在激活函数前),也就是先做一个归一化处理(归一化至:均值0、方差为1)

2.原理介绍

假设有数列B,该数列的BN操作过程如下:


1)计算数列的均值
2)计算数列的方差
3)归一化处理
4)引入两个可训练参数,对数据进行缩放和平移,避免归一化处理后网络学习到的特征分布丢失
在卷积神经网络中的卷积层使用BN处理,相当于卷积计算:经过简单公式变换,可以理解为先进行depthwise_conv2d卷积,接着加偏置,再进行depthwise_conv2d卷积,再加偏置

3.使用tensorflow实现

1)计算均值和方差

tf.nn.moments(x, axes, name=None, keep_dims=False):
各参数说明如下:

x:输入张量。
axes:指定沿哪个轴计算平均值和方差。正常每个维度都要计算,所以对于一维张量一维张量就是[0],二维张量就是[0,1],三  维张量就是[0,1,2],四维张量就是[0,1,2,3]
name:名称。
keep_dims:是否保留维度,即形状是否和输入一样。

2)批量归一化

tf.nn.batch_normalization(x,mean,variance,offset,scale,variance_epsilon,name=None):
各个参数的说明如下:

x:代表任意维度的输入张量。
mean:代表样本的均值。一维张量,长度与x的深度保持一致
variance:代表样本的方差。一维张量,长度与x的深度保持一致
offset:代表偏移,即相加一个转化值,也是公式中的beta。一维张量,长度与x的深度保持一致
scale:代表缩放,即乘以一个转化值,也是公式中的gamma。
variance_epsilon:是为了避免分母为0的情况下,给分母加上的一个极小值,可以取1e-8。
name:名称。

一维张量的批量归一化

import tensorflow as tf
x=tf.constant([1,10,23,15],tf.float32)
#"计算均值和方差"
# 需要指定计算的维度:一维张量就是[0],二维张量就是[0,1],三维张量就是[0,1,2],四维张量就是[0,1,2,3]
mean,variance=tf.nn.moments(x,[0])
#"BatchNormalize"
r=tf.nn.batch_normalization(x,mean,variance,0,1,1e-8)
session=tf.Session()
print(session.run(r))

运行结果[-1.4096959 -0.28193915 1.3470427 0.34459233]
三维张量的批量归一化

x=tf.placeholder(tf.float32,[None,2,2,2])
dim=list(range(len(x.shape)-1))
mean,var=tf.nn.moments(x,dim)
bn=tf.nn.batch_normalization(x,mean,var,(3,8),(2,5),1e-8)
input=np.array([
    [[[1,12],[6,18]],[[9,13],[4,11]]],
    [[[2,19],[7,17]],[[3,15],[8,11]]]
])
sess=tf.Session()
sess.run(bn,{x:input})
sess.close()

4.批量归一化的简单用法

上面的函数虽然参数不多,但是需要几个函数联合起来使用
于是TensorFlow中的layers模块里又实现了一次BN函数,相当于把几个函数合并到了一起,使用起来更加简单
下面来介绍一下,使用时需要引入:from tensorflow.contrib.layers.python.layers import batch_norm
或者直接调用tf.contrib.layers.batch_norm()该函数的定义如下:

def batch_norm(inputs,
               decay=0.999,
               center=True,
               scale=False,
               epsilon=0.001,
               activation_fn=None,
               param_initializers=None,
               param_regularizers=None,
               updates_collections=ops.GraphKeys.UPDATE_OPS,
               is_training=True,
               reuse=None,
               variables_collections=None,
               outputs_collections=None,
               trainable=True,
               batch_weights=None,
               fused=False,
               data_format=DATA_FORMAT_NHWC,
               zero_debias_moving_mean=False,
               scope=None,
               renorm=False,
               renorm_clipping=None,
               renorm_decay=0.99):

一些参数看名字也大概知道什么了,具体详见下方第二份参考资料

5.带BN操作的卷积神经网络

这次来实现一个带有BN操作的两层神经网络

import tensorflow as tf
import numpy as tf
# 输入值占位
x=tf.placeholder(tf.float32,[None,1,2,3])
# 1.第一层神经网络
# 1)卷积后加偏置
k1=tf.constant([[[[1,0],[0,1],[0,0]]]],tf.float32)
conv1=tf.nn.conv2d(x,k1,[1,1,1,1],"SAME")
b1=tf.Variable(tf.random_normal([conv1.shape[-1].value,]))
l1=conv1+b1
# 2)BN操作
axes1=list(range(len(l1.shape)-1))
mean1,var1=tf.nn.moments(l1,axes)
bias1=tf.Variable(tf.random_normal([conv1.shape[-1].value,]))
scaler1=tf.Variable(tf.random_normal([conv1.shape[-1].value,]))
bn1=tf.nn.batch_normalization(l1,mean1,var1,bias1,scaler1,1e-8)
# 3)relu激活
act1=tf.nn.relu(bn1)
# 2.第二层神经网络
# 1)卷积后加偏置
k2=tf.constant([[[[0,2],[2,0]]]],tf.float32)
conv2=tf.nn.conv2d(act1,k2,[1,1,1,1],"SAME")
b2=tf.Variable(tf.random_normal([conv2.shape[-1].value,]))
l2=conv2+b2
# 2)BN操作
axes=list(range(len(l2.shape)-1))
mean2,var2=tf.nn.moments(l2,axes)
bias2=tf.Variable(tf.random_normal([conv2.shape[-1].value,]))
scaler2=tf.Variable(tf.random_normal([conv2.shape[-1].value,]))
bn2=tf.nn.batch_normalization(l2,mean2,var2,bias2,scaler2,1e-8)
# 3)relu激活
act2=tf.nn.relu(bn2)

# 传入输入值
input=np.array([
    [[[1,2,3],[4,5,6]]],
    [[[11,12,13],[14,15,16]]],
    [[[21,23,21],[23,24,22]]]
],np.float32)

# 创建会话
sess=tf.Session()
sess.run(tf.global_variables_initializer())
print(sess.run(act2,{x:input}))
sess.close()

6.指数移动平均

假设已经训练好一个带有BN操作的卷积神经网络,但是使用它在进行预测时,往往每次只输入一个样本
那么经过该网络是,计算单个样本的平均值和方差的意义就不大了,需要把训练集中的均值和方差拿来使用
而在训练过程中,由于mean和variance是和batch内的数据有关的,从而使得每个batch得到的均值和方差也不同
由此引入指数移动平均:把训练的过程中每个step得到的mean和variance,叠加计算对应的moving_average(滑动平均),并最终保存下来以便在predict的过程中使用

1)指数移动平均(指数加权平均)公式如下:



公式太抽象,先举一个活生生的例子:

英国伦敦365天温度数据,要求计算移动平均值,则公式中的参数实际意义如下:
Vt:1~t天的温度平均值;
β:加权系数
θt:第t天的温度
计算过程如下:(取β=0.9,V0=0,共有n天)

V0=0;
V1=0.9*V0+ 0.1*θ1;
V2=0.9*V1+ 0.1*θ2;
…
Vn=0.9*Vn-1+ 0.1*θn

2)API

(1)调用类对象
ema = tf.train.ExponentialMovingAverage(0.9)  0.9:就是其衰减因子
(2)传入参数
total=ema.apply([next1,next2,nex3...])
这里传入的参数是一个变量列表(Variable List或者Tensor List),可以同时计算多个对象的加权平均数
total1=a*total0+(1-a)*current1, total2=a*total1+(1-a)*current2
(3)开始计算:tf.Session().run(total)
(4)取值:total1=ema.average(next1)

然后来一段代码看的更清楚

import tensorflow as tf
#"初始化变量,即 t=1 时的值",注意这里的trainable
x=tf.Variable(initial_value=5,dtype=tf.float32,trainable=False,name='v')
#"创建计算移动平均的对象"
exp_moving_avg=tf.train.ExponentialMovingAverage(0.7)
#先申请函数作用于x,注意这里的参数必须是一个 Variable List 或者 Tensor List
update_moving_avg=exp_moving_avg.apply([x])
#"创建会话"
session=tf.Session()
session.run(tf.global_variables_initializer())
for i in range(4):
    #"打印指数移动平均值"
    session.run(update_moving_avg)
    print('第{}次的移动平均值:'.format(i+1),end="")
    #计算指数移动平均值,注意这里的参数必须是 A Variable object,只能从上面的apply参数中的列表里取一个
    print(session.run(exp_moving_avg.average(x)))
    session.run(x.assign_add(5))
sess.close()

运行结果:
第1次的移动平均值:5.0
第2次的移动平均值:6.5
第3次的移动平均值:9.05
第4次的移动平均值:12.335

上面这段代码看起来真是相当弱智:现实情况的原始数据肯定不是每次都+5这么简单,应该如何应对呢
关键点在于average()这个方法,特别弱智,参数只能是一个Variable对象,只能每次迭代是重新指定变量值了

import tensorflow as tf
#导入原始数据:这里为了便鉴别程序,构造了和刚刚参数相同的原始参数
x=list(range(5,21,5))
input=tf.Variable(initial_value=5.0,dtype=tf.float32,trainable=False,name="input")
exp_moving_avg=tf.train.ExponentialMovingAverage(0.7)
update=exp_moving_avg.apply([input])
sess.run(tf.global_variables_initializer())
for i in range(4):
    sess.run(tf.assign(input,x[i]))
    print("第%d次迭代:" %(i+1),"x值为",end="")
    print(sess.run(input),end=",")
    sess.run(update)
    print("mvg值为",end="")
    result=sess.run(exp_moving_avg.average(input))
    print(result)
sess.close()

经过多次调试,终于成功,这里坑比较多

7.带BN操作和指数移动平均的卷积神经网络

现在尝试将第5步 带BN操作带卷积神经网络 加上指数移动平均

#"输入值的占位符"
x=tf.placeholder(tf.float32,[None,1,2,3])
#"设置是否是训练阶段的占位符" tf.cond()需要用到这个参数
trainable=tf.placeholder(tf.bool)
#"移动平均"
ema=tf.train.ExponentialMovingAverage(0.7)
ema_var_list=[]
#"----------第一层-----------"
#"2个1行1列3深度的卷积核"
k1=tf.Variable(tf.constant([
                [[[1,0],[0,1],[0,0]]],
                ],tf.float32)
        )
#"偏置"
b1=tf.Variable(tf.zeros(2))
#"卷积结果加偏置"
c1=tf.nn.conv2d(x,k1,[1,1,1,1],'SAME')+b1
beta1=tf.Variable(tf.zeros(c1.get_shape()[-1].value))
gamma1=tf.Variable(tf.ones(c1.get_shape()[-1].value))
#"计算每一深度上的均值和方差"
m1,v1=tf.nn.moments(c1,[0,1,2])
ema_var_list+=[m1,v1]
#"为了保存均值和方差的指数移动平均"
m1_ema=tf.Variable(tf.zeros(c1.get_shape()[-1]),trainable=False)
v1_ema=tf.Variable(tf.zeros(c1.get_shape()[-1]),trainable=False)
#"BN操作" tf.cond() 类似于 if...else...
c1_BN=tf.cond(trainable,
        lambda:tf.nn.batch_normalization(c1,m1,v1,beta1,gamma1,1e-8),
        lambda: tf.nn.batch_normalization(c1,m1_ema,
                                          v1_ema,beta1,gamma1,1e-8)
        )
#"relu激活"
r1=tf.nn.relu(c1_BN)
#"----------第二层-----------"
#"2个1行1列2深度的卷积核"
k2=tf.Variable(tf.constant(
        [
        [[[2,0],[0,2]]]
        ],tf.float32
        ))
#"偏置"
b2=tf.Variable(tf.zeros(2))
#"卷积结果加偏置"
c2=tf.nn.conv2d(r1,k2,[1,1,1,1],'SAME')+b2
beta2=tf.Variable(tf.zeros(c2.get_shape()[-1]))
gamma2=tf.Variable(tf.ones(c2.get_shape()[-1]))
#"计算每一深度上的均值和方差"
m2,v2=tf.nn.moments(c2,[0,1,2])
ema_var_list+=[m2,v2]
#"为了保存均值和方差的指数移动平均"
m2_ema=tf.Variable(tf.zeros(c1.get_shape()[-1]),trainable=False)
v2_ema=tf.Variable(tf.zeros(c1.get_shape()[-1]),trainable=False)
#"BN操作"
c2_BN=tf.cond(trainable,
        lambda:tf.nn.batch_normalization(c2,m2,v2,beta2,gamma2,1e-8),
        lambda:tf.nn.batch_normalization(c2,m2_ema,
                                         v2_ema,beta2,gamma2,1e-8)
        )
#"relu激活"
r2=tf.nn.relu(c2_BN)
update_moving_avg=ema.apply(ema_var_list)
# 初始化输入值
list1=np.array([[[[1,2,3],[4,5,6]]]],np.float32)
list2=np.array([[[[11,12,13],[14,15,16]]]],np.float32)
list3=np.array([[[[21,23,21],[23,24,22]]]],np.float32)
input=(list1,list2,list3)
#"创建会话"
session=tf.Session()
session.run(tf.global_variables_initializer()) #初始化全局变量
session.run(tf.local_variables_initializer()) #初始化临时变量
coord=tf.train.Coordinator() #线程协调员
threads=tf.train.start_queue_runners(sess=session,coord=coord) #收集图中所有线程,开启线程
for i in range(len(input)):
    #arrs=session.run(i)
    print('---第%(num)d 批array---'%{'num':i+1})
    print(i)
    _,c1_arr=session.run([update_moving_avg,c1],
                        feed_dict={x:input[i],trainable:True})
    print('---第%(num)d 次迭代后第1个卷积层(卷积结果加偏置)的值---'%{'num':i+1})
    print(c1_arr)
    #"将计算的指数移动平均的值赋值给 Variable 对象"
    session.run(m1_ema.assign(ema.average(m1)))
    session.run(v1_ema.assign(ema.average(v1)))
    session.run(m2_ema.assign(ema.average(m2)))
    session.run(v2_ema.assign(ema.average(v2)))
    print('---第%(num)d 次迭代后第1个卷积层的均值的移动平均值---'%{'num':i+1})
    print(session.run(m1_ema))
coord.request_stop() #请求线程停止
coord.join(threads) #线程回收
session.close()

参考资料:
批量归一化的参数优化
第十八节,TensorFlow中使用批量归一化(BN)
译文 | 批量归一化:通过减少内部协变量转移加速深度网络训练
Optimization Algorithms优化算法
指数加权移动平均
tf.train.ExponentialMovingAverage
关于tf.train.ExponentialMovingAverage使用的详细解析
tf.cond()的用法

 
4 months ago

1x1的卷积核对于二维张量的确没有什么用
但是对于二维以上张量,可以降低张量深度,从而减少计算量,从而节约训练时间,具有极大的作用

在举例之前,我们先搞清楚一次卷积计算过程中的计算量是多少
具体推导过程见参考资料,这里直接给出结论

一次卷积的时间按复杂度:  O(K*K*C_{in}*M*M*C_{out})
忽略bias参数,简化之后参数:  K*K*C_{in}*C_{out}

M 每个卷积核输出特征图 (Feature Map) 的边长
K 每个卷积核 (Kernel) 的边长
C_{in} 每个卷积核的通道数,也即输入通道数,也即上一层的输出通道数。
C_{out} 本卷积层具有的卷积核个数,也即输出通道数。

简单来说:卷积核尺寸 x 输出特征图尺寸
理论看起来很枯燥,直接拿实例来说话


该卷积过程的乘法计算量:5*5*200*30*40*55=330,000,000
接着,考虑如下卷积过程

此时,卷积过程分为两次:先利用1x1的卷积核降维,然后升维
计算量:1x1x200x30x40x20+5x5x20x30x40x55=37,800,000

可以明显看出,使用1x1卷积核先降维再升维的办法,直接将计算量降低了一个量级,效果非常明显

该方法在GoogleNet中的网中网(Inception Module)中使用(原始模块是左图,右图中是加入了1×1卷积进行降维)

参考资料:
一次卷积的计算量到底有多少
原 1X1卷积核到底有什么作用
张平:《图解深度学习与神经网络》

 
4 months ago

一、网络结构简介

VGGNet是牛津大学计算机视觉组和Google DeepMind公司一起研发的深度卷积神经网络
并取得了2014年Imagenet比赛定位项目第一名和分类项目第二名

VGGNet比AlexNet的网络层数多,输入依然是224x224x3,输出依然为1000
不再使用尺寸较大卷积核(如11x11,7x7,5x5),而使用尺寸较小的3x3卷积核
VGG版本很多,常用的是VGG16,VGG19网络,本次我们以VGG16为例进行介绍

二、手撸代码

import tensorflow as tf

# 构建待训练数据
input=tf.placeholder(tf.float32,[None,224,224,3])
# 1.第一层:64个3*3*3的卷积核same卷积,加偏置后激活
#   输入:224x224x3 输出:224x224x64
with tf.variable_scope("layer1",reuse=tf.AUTO_REUSE):
    k1=tf.Variable(tf.random_normal((3,3,3,64)),name="k1")
    conv1=tf.nn.conv2d(input,k1,(1,1,1,1),"SAME")
    b1=tf.Variable(tf.random_normal((64,)),name="b1")
    act1=tf.nn.relu(conv1+b1)
    
# 2.第二层:64个3*3*64的卷积核same卷积,加偏置后激活
#   输入:224x224x64 输出:224x224x64
with tf.variable_scope("layer2",reuse=tf.AUTO_REUSE):
    k2=tf.Variable(tf.random_normal((3,3,64,64)),name="k2")
    conv2=tf.nn.conv2d(act1,k2,(1,1,1,1),"SAME")
    b2=tf.Variable(tf.random_normal((64,)),name="b2")
    act2=tf.nn.relu(conv2+b2)
    
# 2x2最大池化,步长为2
# 输入:224x224x64 输出:112x112x64
p1=tf.nn.max_pool(act2,(1,2,2,1),(1,2,2,1),"SAME")

# 3.第三层:128个3*3*64的卷积核same卷积,加偏置后激活
#   输入:112x112x64 输出:112x112x128
with tf.variable_scope("layer3",reuse=tf.AUTO_REUSE):
    k3=tf.Variable(tf.random_normal((3,3,64,128)),name="k3")
    conv3=tf.nn.conv2d(p1,k3,(1,1,1,1),"SAME")
    b3=tf.Variable(tf.random_normal((128,)),name="b3")
    act3=tf.nn.relu(conv3+b3)
    
# 4.第四层:128个3*3*128的卷积核same卷积,加偏置后激活
#   输入:112x112x128 输出:112x112x128
with tf.variable_scope("layer4",reuse=tf.AUTO_REUSE):
    k4=tf.Variable(tf.random_normal((3,3,128,128)),name="k4")
    conv4=tf.nn.conv2d(act3,k4,(1,1,1,1),"SAME")
    b4=tf.Variable(tf.random_normal((128,)),name="b4")
    act4=tf.nn.relu(conv4+b4)
    
# 2x2最大池化,步长为2
# 输入:112x112x128 输出:56x56x128
p2=tf.nn.max_pool(act4,(1,2,2,1),(1,2,2,1),"SAME")

# 5.第五层:256个3*3*128的卷积核same卷积,加偏置后激活
#   输入:56x56x128 输出:56x56x256
with tf.variable_scope("layer5",reuse=tf.AUTO_REUSE):
    k5=tf.Variable(tf.random_normal((3,3,128,256)),name="k5")
    conv5=tf.nn.conv2d(p2,k5,(1,1,1,1),"SAME")
    b5=tf.Variable(tf.random_normal((256,)),name="b5")
    act5=tf.nn.relu(conv5+b5)
    
# 6.第六层:256个3*3*256的卷积核same卷积,加偏置后激活
#   输入:56x56x256 输出:56x56x256
with tf.variable_scope("layer6",reuse=tf.AUTO_REUSE):
    k6=tf.Variable(tf.random_normal((3,3,256,256)),name="k6")
    conv6=tf.nn.conv2d(act5,k6,(1,1,1,1),"SAME")
    b6=tf.Variable(tf.random_normal((256,)),name="b6")
    act6=tf.nn.relu(conv6+b6)
    
# 7.第七层:256个3*3*256的卷积核same卷积,加偏置后激活
#   输入:56x56x256 输出:56x56x256
with tf.variable_scope("layer7",reuse=tf.AUTO_REUSE):
    k7=tf.Variable(tf.random_normal((3,3,256,256)),name="k7")
    conv7=tf.nn.conv2d(act6,k7,(1,1,1,1),"SAME")
    b7=tf.Variable(tf.random_normal((256,)),name="b7")
    act7=tf.nn.relu(conv7+b7)
    
# 2x2最大池化,步长为2
# 输入:56x56x256 输出:28x28x256
p3=tf.nn.max_pool(act7,(1,2,2,1),(1,2,2,1),"SAME")

# 8.第八层:512个3*3*256的卷积核same卷积,加偏置后激活
#   输入:28x28x256 输出:28x28x512
with tf.variable_scope("layer8",reuse=tf.AUTO_REUSE):
    k8=tf.Variable(tf.random_normal((3,3,256,512)),name="k8")
    conv8=tf.nn.conv2d(p3,k8,(1,1,1,1),"SAME")
    b8=tf.Variable(tf.random_normal((512,)),name="b8")
    act8=tf.nn.relu(conv8+b8)
    
# 9.第九层:512个3*3*512的卷积核same卷积,加偏置后激活
#   输入:28x28x512 输出:28x28x512
with tf.variable_scope("layer9",reuse=tf.AUTO_REUSE):
    k9=tf.Variable(tf.random_normal((3,3,512,512)),name="k9")
    conv9=tf.nn.conv2d(act8,k9,(1,1,1,1),"SAME")
    b9=tf.Variable(tf.random_normal((512,)),name="b9")
    act9=tf.nn.relu(conv9+b9)
    
# 10.第十层:512个3*3*512的卷积核same卷积,加偏置后激活
#   输入:28x28x512 输出:28x28x512
with tf.variable_scope("layer10",reuse=tf.AUTO_REUSE):
    k10=tf.Variable(tf.random_normal((3,3,512,512)),name="k10")
    conv10=tf.nn.conv2d(act9,k10,(1,1,1,1),"SAME")
    b10=tf.Variable(tf.random_normal((512,)),name="b10")
    act10=tf.nn.relu(conv10+b10)
    
# 2x2最大池化,步长为2
# 输入:28x28x512 输出:14x14x512
p4=tf.nn.max_pool(act10,(1,2,2,1),(1,2,2,1),"SAME")

# 11.第十一层:512个3*3*512的卷积核same卷积,加偏置后激活
#   输入:14x14x512 输出:14x14x512
with tf.variable_scope("layer11",reuse=tf.AUTO_REUSE):
    k11=tf.Variable(tf.random_normal((3,3,512,512)),name="k11")
    conv11=tf.nn.conv2d(p4,k11,(1,1,1,1),"SAME")
    b11=tf.Variable(tf.random_normal((512,)),name="b11")
    act11=tf.nn.relu(conv11+b11)
    
# 12.第十二层:512个3*3*512的卷积核same卷积,加偏置后激活
#   输入:14x14x512 输出:14x14x512
with tf.variable_scope("layer12",reuse=tf.AUTO_REUSE):
    k12=tf.Variable(tf.random_normal((3,3,512,512)),name="k12")
    conv12=tf.nn.conv2d(act11,k12,(1,1,1,1),"SAME")
    b12=tf.Variable(tf.random_normal((512,)),name="b12")
    act12=tf.nn.relu(conv12+b12)
    
# 13.第十三层:512个3*3*512的卷积核same卷积,加偏置后激活
#   输入:14x14x512 输出:14x14x512
with tf.variable_scope("layer13",reuse=tf.AUTO_REUSE):
    k13=tf.Variable(tf.random_normal((3,3,512,512)),name="k13")
    conv13=tf.nn.conv2d(act12,k13,(1,1,1,1),"SAME")
    b13=tf.Variable(tf.random_normal((512,)),name="b13")
    act13=tf.nn.relu(conv13+b13)
    
# 2x2最大池化,步长为2
# 输入:14x14x512 输出:7x7x512
p5=tf.nn.max_pool(act13,(1,2,2,1),(1,2,2,1),"SAME")

# 拉伸成一维数据,作为全连接层的输入
num=p5.shape[1].value*p5.shape[2].value*p5.shape[3].value
fc_input=tf.reshape(p5,(-1,num))

# 14.第十四层:全连接层,4096个神经元
with tf.variable_scope("layer14",reuse=tf.AUTO_REUSE):
    k14=tf.Variable(tf.random_normal((num,4096)),name="k14")
    b14=tf.Variable(tf.random_normal((4096,)),name="b14")
    l14=tf.matmul(fc_input,k14)+b14
    act14=tf.nn.relu(l14)
    
# 15.第十五层:全连接层,4096个神经元
with tf.variable_scope("layer15",reuse=tf.AUTO_REUSE):
    k15=tf.Variable(tf.random_normal((4096,4096)),name="k15")
    b15=tf.Variable(tf.random_normal((4096,)),name="b15")
    l15=tf.matmul(act14,k15)+b15
    act15=tf.nn.relu(l15)
    
# 16.第十六层:全连接层1000个神经元
with tf.variable_scope("layer16",reuse=tf.AUTO_REUSE):
    k16=tf.Variable(tf.random_normal((4096,1000)),name="k16")
    b16=tf.Variable(tf.random_normal((1000,)),name="b16")
    l16=tf.matmul(act15,k16)+b16
    #act16=tf.nn.softmax(l16)#交叉熵损失函数会自动加上损失函数,无需改行代码

代码中大量使用命名空间,这是一种规范的写法,详见参考资料
以上代码加上损失函数和梯度下降部分,就可以训练一个VGGNet了

三、小结

1.该网络主要是泛化性能很好
容易迁移到其他的图像识别项目上,可以下载VGGNet训练好的参数进行很好的初始化权重操作(传说中的迁移学习)

2.将卷积层提升到卷积块的概念
卷积块有2~3个卷积层构成,使网络有更大感受野的同时能降低网络参数
同时多次使用ReLu激活函数有更多的线性变换,学习能力更强

对VGGNet-16稍加改进就是VGGNet-19


参考资料:
常用的几种卷积神经网络介绍
TF.VARIABLE、TF.GET_VARIABLE、TF.VARIABLE_SCOPE以及TF.NAME_SCOPE关系
张平:《图解深度学习与神经网络》

 
4 months ago

一、网络简介

该网络2012年提出,是为了解决彩色图片的分类问题:输入为224*224*3的彩色图片,输出为1000
该网络总共8层,包括5个卷积层和3个全连接层,如下图所示


二、代码实操

# 1.第一层卷积:96个11*11*3,步长为4的卷积核;加偏置;使用激活函数
#   输入:224*224*3,输出:224*224*96
input=tf.placeholder(tf.float32,[None,224,224,3])
k1=tf.Variable(tf.random_normal((11,11,3,96)))
conv1=tf.nn.conv2d(input,k1,(1,4,4,1),"SAME")
b1=tf.Variable(tf.random_normal((96,)))
l1=conv1+b1
act1=tf.nn.relu(l1)
# 最大池化:3*3,步长为2
p1=tf.nn.max_pool(act1,(1,3,3,1),(1,2,2,1),"SAME")

# 2.第二层卷积:256个5*5*96,步长为1的卷积核;加偏置;使用激活函数
#   输入:28*28*96,输出:28*28*256
k2=tf.Variable(tf.random_normal((5,5,96,256)))
conv2=tf.nn.conv2d(p1,k2,(1,1,1,1),"SAME")
b2=tf.Variable(tf.random_normal((256,)))
l2=conv2+b2
act2=tf.nn.relu(l2)
# 最大池化:3*3,步长为2
p2=tf.nn.max_pool(act2,(1,3,3,1),(1,2,2,1),"SAME")

# 3.第三层卷积:384个3*3*256,步长为1的卷积核;加偏置;使用激活函数
#   输入:14*14*256,输出:14*14*384
k3=tf.Variable(tf.random_normal((3,3,256,384)))
conv3=tf.nn.conv2d(p2,k3,(1,1,1,1),"SAME")
b3=tf.Variable(tf.random_normal((384,)))
l3=conv3+b3
act3=tf.nn.relu(l3)

# 4.第四层卷积:384个3*3*384,步长为1的卷积核;加偏置;使用激活函数
#   输入:14*14*384,输出:14*14*384
k4=tf.Variable(tf.random_normal((3,3,384,384)))
conv4=tf.nn.conv2d(act3,k4,(1,1,1,1),"SAME")
b4=tf.Variable(tf.random_normal((384,)))
l4=conv4+b4
act4=tf.nn.relu(l4)

# 5.第五层卷积:256个3*3*384,步长为1的卷积核;加偏置;使用激活函数
#   输入:14*14*384,输出:14*14*256
k5=tf.Variable(tf.random_normal((3,3,384,256)))
conv5=tf.nn.conv2d(act4,k5,(1,1,1,1),"SAME")
b5=tf.Variable(tf.random_normal((256,)))
l5=conv5+b5
act5=tf.nn.relu(l5)
# 最大池化:3*3,步长为2
p3=tf.nn.max_pool(act2,(1,3,3,1),(1,2,2,1),"SAME")
# 构建全连接神经网络输入数据
num=p3.shape[1].value * p3.shape[2].value * p3.shape[3].value
fc_input=tf.reshape(p3,[-1,num])

# 6.第一层fc:4096个神经元
w1=tf.Variable(tf.random_normal((num,4096)))
bw1=tf.Variable(tf.random_normal((4096,)))
fc_l1=tf.matmul(fc_input,w1)+bw1
fc_con1=tf.nn.relu(fc_l1)
# 引入dropout
fc_con1=tf.nn.dropout(fc_con1,keep_prob)

# 7.第二层fc:4096个神经元
w2=tf.Variable(tf.random_normal((4096,4096)))
bw2=tf.Variable(tf.random_normal((4096,)))
fc_l2=tf.matmul(fc_con1,w2)+bw2
fc_con2=tf.nn.relu(fc_l2)
# 引入dropout
fc_con1=tf.nn.dropout(fc_con1,keep_prob)

# 8.第三层fc:1000个神经元
w3=tf.Variable(tf.random_normal((4096,1000)))
bw3=tf.Variable(tf.random_normal((1000,)))
fc_l3=tf.matmul(fc_con2,w3)+bw3
fc_con3=tf.nn.relu(fc_l3)

# 创建会话
keep_prob=tf.placeholder(tf.float32)
sess=tf.Session()
sess.run(tf.global_variables_initializer())
result=sess.run(fc_con3,{input:np.ones((2,224,224,3)),keep_prob:0.5})
print(result.shape)
sess.close()

运行结果:(2, 1000)

四、小结

AlexNet比LeNet具备更深的网络结构,卷积核深度更深,全连接神经元个数更多
有两个非常重要的改进:
1.提出RELU激活函数
2.提出dropout:在训练过程中随机扔掉一些神经元,有效防止网络的过拟合
一般用在全连接神经网络或者全连接层的隐藏层

tf.nn.dropout(x, keep_prob, noise_shape=None, seed=None,name=None)
x:输入张量
keep_prob:属于区间(0,1],代表x中的每一个值以概率keep_prob变为原来的 1/keep_prob,以概率 1-keep_prob变为0

从1998年LeNet的提出到2012年的AlexNet
中间的十几年,其他机器学习方法,如支持向量机(SVM)的表现超出了神经网络和卷积网络
随后大数据、硬件(GPU、TPU)、算法(如RELU激活函数的提出),使得卷积神经网络焕发了生机,拉开了深度学习技术发展的序幕
AlexNet取得巨大成功后,网络结构设计引发广泛关注,开始出现各种网络结构
接下来学习另外三种经典的网络结构 VGGNet、GoogleNet、RestNet

参考资料:
张平:《图解深度学习与神经网络》

 
4 months ago

一、网络简介

LeNet是第一个成熟的卷积神经网络,是专门为处理 MNIST 数字字符集的分类问题而设立的网络结构(1998年创建)
该网络输入值是32行32列1深度的三维张量(灰度图像)




整个网络包括5层结构(2个卷积层和3个全连接层),所以也常称作LeNet5

二、直接开撸代码,边撸边解释

import tensorflow as tf
import numpy as np

# 1.使用6个5x5x1的卷积核进行valid卷积,卷积结果每一深度上加偏置,然后激活
# 输入:32x32x1
# 输出:28x28x6
x=tf.placeholder(tf.float32,[None,32,32,1]) #tf.placeholder构建输入
# 6个5x5x1的卷积核
k1=tf.Variable(tf.random_normal((5,5,1,6),0,1,tf.float32),tf.float32)
conv1=tf.nn.conv2d(x,k1,[1,1,1,1],"VALID")
# 长度为6的偏置(因为卷积结果的输出深度为6)
b1=tf.Variable(tf.random_normal((6,),0,1,tf.float32),tf.float32)
# 线性操作后求和
l1=tf.add(conv1,b1)
act1=tf.nn.relu(l1)

# 2.进行大小2x2,步长为2的VALID最大池化
# 输入:28x28x6
# 输出:14x14x6
pool1=tf.nn.max_pool(act1,(1,2,2,1),(1,2,2,1),"VALID")

# 3.与16个5x5x6的卷积核进行VALID卷积,卷积结果每一深度上加偏置,激活
# 输入:14x14x6
# 输出:10x10x16
k2=tf.Variable(tf.random_normal((5,5,6,16),0,1,tf.float32),tf.float32) #16个5x5x6的卷积核
con2=tf.nn.conv2d(pool1,k2,(1,1,1,1),"VALID")
# 长度为16的偏置(因为卷积输出的深度为16)
b2=tf.Variable(tf.random_normal((16,),0,1,tf.float32),tf.float32)
# 线性操作后激活
l2=tf.add(con2,b2)
act2=tf.nn.relu(l2)

# 4.最大化VALID池化操作,大小2x2,步长2
# 输入:10x10x16
# 输出:5x5x16
pool2=tf.nn.max_pool(act2,(1,2,2,1),(1,2,2,1),"VALID")

# 5.将以上输出拉伸为1维向量,然后构建一个全连接神经网络
# 一个输入层:长度为5x5x16=400的一维向量
# 第一个隐藏层:120个神经元
# 第二个隐藏层:10个神经元
# 输出层:10个神经元(因为要处理十个类别)
# 5.1 拉伸为1维变量,构建全连接神经网络输入层
num = pool2.shape[1].value * pool2.shape[2].value * pool2.shape[3].value
flatten=tf.reshape(pool2,(-1,num))
# 5.2 第一个隐藏层:120个神经元
w1=tf.Variable(tf.random_normal((400,120)),tf.float32)
bw1=tf.Variable(tf.random_normal((120,),0,1,tf.float32),tf.float32)
h1=tf.matmul(flatten,w1)+bw1
activate1=tf.nn.relu(h1)
# 5.3 第二个隐藏层:84个神经元
w2=tf.Variable(tf.random_normal((120,84),0,1,tf.float32),tf.float32)
bw2=tf.Variable(tf.random_normal((84,),0,1,tf.float32),tf.float32)
h2=tf.matmul(activate1,w2)+bw2
activate2=tf.nn.relu(h2)
# 5.4 输出层:10个神经元(因为要处理十个类别)
w3=tf.Variable(tf.random_normal((84,10),0,1,tf.float32),tf.float32)
bw3=tf.Variable(tf.random_normal((10,),0,1,tf.float32),tf.float32)
h3=tf.matmul(activate2,w3)+bw3
output=tf.nn.softmax(h3)

三、运行网络

1.简单验证整个网络是否可以跑通

sess=tf.Session()
sess.run(tf.global_variables_initializer())
sess.run(output,{x:np.random.normal(0,1,(2,32,32,1))})
sess.close()

2.正式训练:采用梯度下降法

labels=tf.placeholder(tf.float32,(None,10))
loss=tf.reduce_sum(tf.nn.softmax_cross_entropy_with_logits(labels=labels,logits=h3))
opti=tf.train.GradientDescentOptimizer(0.001).minimize(loss)
for i in range(10):
# 这里先使用假数据,真实训练需要倒入真实数据
    sess.run(opti,{x:np.random.normal(0,1,(2,32,32,1)),labels:np.random.normal(0,1,(2,10))})
    sess.run(loss,{x:np.random.normal(0,1,(2,32,32,1)),labels:np.random.normal(0,1,(2,10))})
sess.close()

四、小结

这个最简单的卷积神经网络说到底,终究是起到一个分类器的作用
卷积层负责提取特征,采样层负责特征选择,全连接层负责分类
备注:其实人工智能领域有七成以上都是分类问题
卷积核就是权重,相比全连接神经网络,已经大大降低训练参数个数;
池化层也叫向下采样,主要作用是降低计算量,随着计算速度越来越快,现在有神经网络已经趋向不使用池化层

参考资料:
卷积神经网络 LeNet-5各层参数详解
卷积:如何成为一个很厉害的神经网络
CNN入门讲解:卷积层是如何提取特征的?
陈平:《图解深度学习与神经网络》

 
4 months ago

Tensorflow保存计算模型,可以简单的理解为保存程序中的Variable变量

1.保存模型:tf.train.Saver().save

import tensorflow as tf

# 1.定义两个Variable变量,初始化为不同的值
v1=tf.Variable(tf.constant([1,2,3],tf.float32),tf.float32,name="v1")
v2=tf.Variable(tf.constant([4,5,6],tf.float32),tf.float32,name="v2")
# 2.声明tf.train.Saver一个类对象,调用其save方法,将变量保存到当前文件夹下的model.ckpt文件
saver=tf.train.Saver()
sess=tf.Session()
sess.run(tf.global_variables_initializer())
saver.save(sess,"./model.ckpt") #这里一定要填写路径,否则会报错
sess.close()

程序生成并保存四个文件

checkpoint /文本文件,记录了模型文件的路径信息列表/
model.ckpt.data-00000-of-00001 /网络权重信息/
model.ckpt.index /.data和.index这两个文件是二进制文件,保存了模型中的变量参数(权重)信息/
model.ckpt.meta /二进制文件,保存了模型的计算图结构信息/

关于tf.train.Saver().save()的参数介绍,可以参照参考资料

2.加载模型:tf.train.Saver().restore()

import tensorflow as tf

# 1.首先定义两个Variable变量,形状与之前保存的变量形状相同
v1=tf.Variable(tf.constant([11,21,31],tf.float32),tf.float32,name="v1")
v2=tf.Variable(tf.constant([42,52,62],tf.float32),tf.float32,name="v2")
# 2.声明tf.train.Saver一个类对象,调用其save方法,将变量保存到当前文件夹下的model.ckpt文件
saver=tf.train.Saver()
sess=tf.Session()
sess.run(tf.global_variables_initializer())
saver.restore(sess,"./model.ckpt")
print(sess.run(v1))
print(sess.run(v2))
sess.close()

如果你在同一个文件中前后执行保存和加载模型,会遇到一个错误

NotFoundError (see above for traceback): Key v1_1 not found in checkpoint

这个主要是因为你在上下午中给不同的变量起了相同的名字,系统内部会自动处理:
前面的变量系统标记为v1, v2,后面的变量系统标记为v1_1, v2_1
当执行save方法时,系统保存变量v1, v2到checkpoint
当执行restore方法时,系统到checkpoint里面寻找变量v1_1, v2_1,自然是找不到的,因此报错

解决这个问题的办法也很简单:
1)在不同的文件中分别执行保存和加载 这个看似愚蠢,但应该是正常使用场景
2)重置全局默认图:清除默认图的堆栈
在加载模型代码块第一行写下这句代码tf.reset_default_graph()就OK了
当然,这行代码是有强烈后遗症的:会话中使用任何以前创建的tf.Operation或tf.Tensor对象将导致未定义的行为
这一招就是传说中的关机重启,因此不建议在中途使用该方法,一般都是用在每个程序文件的第一行

好了,好了,程序正常运行后给大家看一下结果:

INFO:tensorflow:Restoring parameters from ./model.ckpt
[1. 2. 3.]
[4. 5.]

3.加载模型升级

上面加载个模型废了半天功夫,看起来很高大上的样子,但是缺点确实很明确的:我加载个模型还得知道里面的变量名称和形状
显然这种操作一点儿也不科学不友好,差评
下面介绍一个更加科学的办法:

import tensorflow as tf
from tensorflow.python import pywrap_tensorflow

#ckpt=tf.train.get_checkpoint_state('./')
#"获取在当前文件夹下(./)的ckpt文件,具体根据ckpt保存的位置设置"
ckpt=tf.train.latest_checkpoint('./')
#"打印获取的ckpt文件"
print('获取的ckpt文件:'+ckpt)
#"创建NewCheckpointReader类,读取ckpt文件中的变量名称及其对应的值"
reader=pywrap_tensorflow.NewCheckpointReader(ckpt)
var_map=reader.get_variable_to_shape_map()
for i in read:
    print(i,":",reader.get_tensor(i))

运行结果如下:

v2 : [4. 5.]
v1 : [1. 2. 3.]

参考资料:
浅谈Tensorflow模型的保存与恢复加载
Tensorflow模型保存与加载
张平:《图解深度学习与神经网络》
Tensorflow学习笔记--模型保存与调取
解决tensorflow中报错NotFoundError (see above for traceback): Key v1_1 not found in checkpoint问题
tf.reset_default_graph()