全连接神经网络(fully connected neural network),顾名思义,就是相邻两层之间任意两个节点之间都有连接。全连接神经网络是最为普通的一种模型(比如和CNN相比),由于是全连接,所以会有更多的权重值和连接,因此也意味着占用更多的内存和计算。
本文将分为两部分,第1部分介绍了一个完整的全连接神经网络的样例程序,并对程序中的函数进行了详细的注解。第2部分结合tensorflow 游乐场,利用全连接神经网络解决一个二分类问题。
1. 全连接神经网络样例程序bxcvzz
下面的代码定义了一个两层的全连接神经网络,如下图1所示。其工作原理如图2所示。
#coding:utf-8
#由于python只接受ASCII character,那么在需要输入中文时,需要加入上面这一句,支持utf-8编码
#导入tensorflow
import tensorflow as tf
#这里是导入RandomState,后面利用其生成随机数,模拟一个数据集
from numpy.random import RandomState
#batch_size表示把输入数据分成若干组,每组数据为batch_size个,每次喂一组给NN
batch_size=8
#tf.random_normal原型为tf.random_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
#表示生成正态分布随机数,seed=1,表示种子初始化为1,对于同一个种子,每次生成的随机数相同
#这里到w1和w2对应图中的w1和w2
w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1))
w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1))
#TensorFlow provides a placeholder operation that must be fed with data on execution.
#原型:tf.placeholder(dtype,shape=None,name=None)
#placeholder进行占位(还没有具体的数据),因此在执行计算之前需要先喂入数据
#在sess.run()函数中用feed_dict喂数据
#shape=(None,2),这里指定了shape为2列,但是行数没有指定,这样可以方便使用不同的batch_size
#x为输入数据,y_为输入数据的标签值
x=tf.placeholder(tf.float32,shape=(None,2),name='x-input')
y_=tf.placeholder(tf.float32,shape=(None,1),name='y-input')
#定义NN的前向传播过程
a=tf.matmul(x,w1)
y=tf.matmul(a,w2)
#定义激活函数,将y转换为0~1之间的数值,转换后y代表预测是正样本的概率,1-y代表是负样本的概率
y=tf.sigmoid(y)
#定义损失函数来刻画预测值与真实值的差距,这里选择的是交叉熵损失函数
#tf.clip_by_value()将y限定在1e-10~1.0之间,包含1e-10和1.0
#tf.reduce_mean()默认对括号中的所有值取平均值,其原型为tf.reduce_mean(input_tensor,reduction_indices=None,keep_dims=False,name=None)
#当reduction_indices=1,则对括号中到张量以行为单位计算平均值,若等于0,则以裂为单位计算平均值
cross_entropy=-tf.reduce_mean(y_*tf.log(tf.clip_by_value(y,1e-10,1.0))+(1-y)*tf.log(tf.clip_by_value(1-y,1e-10,1.0)))
#定义反向传播算法来优化NN的参数,这里选择的是AdamOptimizer,其中learning_rate=0.001
train_step=tf.train.AdamOptimizer(0.001).minimize(cross_entropy)
#通过随机数生成一个模拟数据集
rdm=RandomState(1)
dataset_size=128
#RandomState.rand():create an array of the given shape and populate it with
#random samples from a uniform distribution over [0, 1)
#X为模拟的输入数据集
X=rdm.rand(dataset_size,2)
#定义规则来给出样本的标签。x1+x2<1的样例认为是正样本,否则为负样本
#这里0表示负样本,1表示正样本
Y=[[int(x1+x2<1)] for (x1,x2) in X]
#创建一个会话来运行TensorFlow
with tf.Session() as sess:
#利用global_variables_initializer汇总所有需要优化的变量,添加用于初始化变量的节点
init_op=tf.global_variables_initializer()
# 运行初始化操作
sess.run(init_op)
#输出w1和w2的值
print("w1:")
print(sess.run(w1))
print("w2:")
print(sess.run(w2))
#设定训练的轮数
STEPS=5000
for i in range(STEPS):
#每次选取batch_size个样本进行训练
start=(i*batch_size)%dataset_size
end=min(start+batch_size,dataset_size)
#通过选取的样本训练神经网络并更新数据
#通过feed_dict对x&y_进行赋值,其中x为样例内容,y_为x对应的标签
sess.run(train_step,feed_dict={x:X[start:end],y_:Y[start:end]})
#每隔1000轮计算在所有数据上的的交叉熵并输出
if i%1000==0:
total_cross_entropy=sess.run(cross_entropy,feed_dict={x:X,y_:Y})
print("After %d training step(s),cross entropy on all data is %g" %(i,total_cross_entropy))
2. 全连接神经网络求解二分类问题
在tensorflow playground中提供了几个二分类的例子,我们将用代码实现下面这个二分类问题。
假设根据零件的重量和长度来判断零件是否合格,也就是输入的x1,x2,用这两个数据作为一个点在二维平面上的x, y坐标,那么零件就可以表示为二维平面上的一个点,图中蓝色表示合格,红色表示不合格。通过最后输出的数值和一个事先设定的阈值做比较就可以得到最后的分类结果。
#coding:utf-8
import tensorflow as tf
#导入numpy模块用于生成数据集
import numpy as np
#导入plt模块用于绘制图形
import matplotlib.pyplot as plt
#定义神经网络的输入、参数和输出,定义前向传播过程
#######################################################################################
def get_weight(shape, regularizer):
w = tf.Variable(tf.random_normal(shape), dtype=tf.float32)
#下面的代码有两个函数,tf.contrib.layers.l2_regularizer(regularizer)(w),其中第一个参数regularizer由形参传入,
#表示正则化的权重,第二个参赛为需要计算正则化损失的参数,这里选择的l2正则化,更多正则化的讲解请参考注解1
#tf.add_to_collection(name, value),将value加入name列表中
tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(regularizer)(w))
return w
def get_bias(shape):
b = tf.Variable(tf.constant(0.01, shape=shape))
return b
def forward(x, regularizer):
w1 = get_weight([2,8], regularizer)
b1 = get_bias([8])
y1 = tf.nn.relu(tf.matmul(x, w1) + b1)
w2 = get_weight([8,1], regularizer)
b2 = get_bias([1])
y = tf.matmul(y1, w2) + b2
return y
#######################################################################################
#生成数据集
#######################################################################################
seed = 1
def generateds():
#基于seed产生随机数
rdm = np.random.RandomState(seed)
#随机数返回300行2列的矩阵,表示300组坐标点(x0,x1)作为输入数据集,randn()生成服从标准正态分布到数据(ave=0,stdev=1)
X = rdm.randn(300,2)
#从X这个300行2列的矩阵中取出一行,判断如果两个坐标的平方和小于2,给Y赋值1,其余赋值0
#作为输入数据集的标签(正确答案)
Y_ = [int(x0*x0 + x1*x1 <2) for (x0,x1) in X]
#遍历Y中的每个元素,1赋值'red'其余赋值'blue',这样可视化显示时人可以直观区分
Y_c = [['red' if y else 'blue'] for y in Y_]
#对数据集X和标签Y进行形状整理,第一个元素为-1表示跟随第二列计算,第二个元素表示多少列,可见X为两列,Y为1列
#这里X可以不用整理形状,原因是它已经是n*2列
X = np.vstack(X).reshape(-1,2)
Y_ = np.vstack(Y_).reshape(-1,1)
return X, Y_, Y_c
#print X
#print Y_
#print Y_c
#用plt.scatter画出数据集X各行中第0列元素和第1列元素的点即各行的(x0,x1),用各行Y_c对应的值表示颜色(c是color的缩写)
#squeeze从数组的shape中将1的纬度删除
#plt.scatter(X[:,0], X[:,1], c=np.squeeze(Y_c))
#plt.show()
#######################################################################################
#定义反向传播过程
#######################################################################################
STEPS = 40000 #训练轮数
BATCH_SIZE = 30
LEARNING_RATE_BASE = 0.001 #最初学习率
LEARNING_RATE_DECAY = 0.999 #学习率衰减率
REGULARIZER = 0.01 #正则化权重
def backward():
x = tf.placeholder(tf.float32, shape=(None, 2))
y_ = tf.placeholder(tf.float32, shape=(None, 1))
X, Y_, Y_c = generateds()
y = forward(x, REGULARIZER)
#global_step用来记录当前的训练轮数,下面到代码初始化global_step为0,并将其标记为超参数(不训练)
global_step = tf.Variable(0,trainable=False)
#通过exponential_decay函数生成学习率,其会指数级的减小学习率,参考注释3
learning_rate = tf.train.exponential_decay(
LEARNING_RATE_BASE,
global_step,
300/BATCH_SIZE,
LEARNING_RATE_DECAY,
staircase=True)
#定义损失函数,这里选择的是均方误差损失函数,并加上正则化项
#tf.add_n计算列表元素的和
loss_mse = tf.reduce_mean(tf.square(y-y_))
loss_total = loss_mse + tf.add_n(tf.get_collection('losses'))
#定义反向传播方法:包含正则化
#这里选择AdampOptimizer
train_step = tf.train.AdamOptimizer(learning_rate).minimize(loss_total)
with tf.Session() as sess:
# 添加用于初始化变量的节点
init_op = tf.global_variables_initializer()
# 运行初始化操作
sess.run(init_op)
for i in range(STEPS):
start = (i*BATCH_SIZE) % 300
end = start + BATCH_SIZE
sess.run(train_step, feed_dict={x: X[start:end], y_:Y_[start:end]})
if i % 2000 == 0:
loss_v = sess.run(loss_total, feed_dict={x:X,y_:Y_})
print("After %d steps, loss is: %f" %(i, loss_v))
#这部分下面到代码为寻找分类的分界线
#xx, yy = np.mgrid[起:止:步长, 起:止:步长]
#xx,yy在-3到3之间以步长为0.01,生成二维网格坐标点
xx, yy = np.mgrid[-3:3:.01, -3:3:.01]
#np.c_收集规定区域内所有的网格坐标点
#np.ravel将多维数组降为一维
#这里是将xx,yy拉直,并合并成一个2列的矩阵,得到一个网格坐标点的集合
grid = np.c_[xx.ravel(), yy.ravel()]
#将网格坐标点喂入神经网络,probs为输出
probs = sess.run(y, feed_dict={x:grid})
#调整probs的形状
probs = probs.reshape(xx.shape)
#plt.scatter():利用指定颜色实现点(x,y)的可视化, plt.scatter (x 坐标, y 坐标, c=”颜色”)
plt.scatter(X[:,0], X[:,1], c=np.squeeze(Y_c))
#plt.contour()函数:告知 x、y 坐标和各点高度,用 levels 指定高度的点描上颜色
#plt.contour (x 轴坐标值, y 轴坐标值, 该点的高度, levels=[等高线的高度])
#这里是给所有probs=0.5的点上色,也就是寻找分界线
plt.contour(xx, yy, probs, levels=[.5])
plt.show()
#######################################################################################
if __name__=='__main__':
backward()
输出结果如下:
注释:
1.正则化
正则化是一种非常常用的避免过拟合的方法。过拟合就是模型过于专注于训练数据的细节,而不是去学习通用的特性,这样模型会过于复杂,最后表现出来的结果就是在训练数据上的表现非常好,而在测试数据上的表现不及训练数据。而正则化的思想就是在损失函数中加入刻画模型复杂度到指标,以控制其模型的复杂度。而一般说来模型的复杂度只由权重w决定,因此常用的模型复杂度函数R(w)有L1和L2两种。
2. np.squeeze(a, axis=None)
Selects a subset of the single-dimensional entries in the shape. If an axis is selected with shape entry greater than one, an error is raised.
squeeze 的过程就是常说的展平的过程。默认axis=None,在此情况下,a的所有单一纬度将会被删除,当然也可以通过axis指定删除某一个单一纬度,如果指定的axis上到纬度大于1将会出错。这个指令主要是为了最简化a的纬度。
示例如下:
import numpy as np
x = np.array([[[0], [1], [2]]])
print x
#[[[0]
# [1]
# [2]]]
print x.shape #(1, 3, 1)
y=np.squeeze(x)
print y.shape #(3,)
print y
#[0 1 2]
3. exponential_decay(learning_rate, global_step, decay_steps, decay_rate, staircase=True/False)。其中:
learning_rate为事先设定的初始学习率;
tf.train.global_step为训练迭代轮数;
decay_steps为衰减速度;
decay_rate为衰减率;
当staircase=True时,global_step/decay_steps的结果会被转化为整数,那就表明每隔decay_steps次计算学习速率变化,更新原始学习速率,如果是False,那就是每一步都更新学习速率。
该函数的计算如下:
decayed_learning_rate=learning_rate*decay_rate^(global_step/decay_step)