Tensoflow基础概念和编程(第三讲)

前向传播算法简介

不同的神经网络的前向传播的方式也不一样,这里介绍的是最简单的全连接网络结构的前向传播算法;一个最简单的神经元结构的输出就是所有输入的加权和,而不同输入的权重就是神经元的参数,神经元的优化过程就是优化神经元中的参数的过程

 

计算前向传播的结果需要三部分信息:

  • 神经网络的输入
  • 神经网络的连接结构
  • 神经元的参数

上图的运算过程假设权重已知;

这样前向传播算法就可以通过矩阵乘法的形式表示出来了,在Tensorflow中实现上图所示的前向传播过程:

a = tf.matmul(x, w1)
y = tf.matmul(a, w2)

以上就是前向传播算法的全部内容;

神经网络参数与tensorflow变量

在tensorflow中,变量的作用就是保存和更新神经网络中的参数,看代码:

# 生成一个2行3列的矩阵,均值为0,标准差为2
weights = tf.Variable(tf.random_normal([2,3], stddev=2))
# 生成一个初始值全部为0且长度为3的变量
biases = tf.Variable(tf.zeros([3]))

# 除了使用随机数或者常数,tensorflow也支持通过其他变量的初始值来初始化新的变量
w2 = tf.Variable(weights.initialized_value())
w3 = tf.Variable(weights.initialized_value() * 2.0)
# 在上述代码中,w2被设置成了与weights变量相同,w3被设置成了weights初始值的两倍

总结一些常用的生成函数:

随机数生成数函数创建的是矩阵,常数生成数创建的是数组

注意:类似张量,维度和类型也是变量最重要的两个属性,和大部分程序语言类似,变量的类型是不可改变的;一个变量在创建之后,它的类型就不能被改变了;维度是变量另一个重要的属性,和类型不太一样的是,维度在程序运行中是有可能改变的,但是需要通过设置参数validate_shape=False;给出一段代码:

通过tensorflow训练神经网络模型

这一块简单介绍一下使用监督学习的方式来更合理的设置参数取值;看代码:

import tensorflow as tf
import numpy as np
from numpy.random import RandomState

# 造数据
# np.random.seed(1)
# X = np.random.rand(128,2)
rdm = RandomState(1)
X = rdm.rand(128, 2)
Y = [[int((x1+x2)<1)] for (x1,x2) in X]
n_samples = X.shape[0]

# 常用参数设定
learning_rate = 0.001
batch = 8
training_epochs = 5000

# 构建一些参数
w1 = tf.Variable(tf.random_normal([2,3], stddev=1, seed=1))
w2 = tf.Variable(tf.random_normal([3,1], stddev=1, seed=1))
x = tf.placeholder(tf.float32,shape=[None, 2])
y = tf.placeholder(tf.float32, shape=[None, 1])

# d定义前向传播
a = tf.matmul(x, w1)
pred = tf.matmul(a, w2)
# 定义损失函数
# 定义反向传播算法来优化神经网络中的参数
cross_entropy = -tf.reduce_mean(y * tf.log(tf.clip_by_value(pred, 1e-10, 1.0)))
training_step = tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)


init = tf.global_variables_initializer()

# 进行训练
with tf.Session() as sess:
    sess.run(init)
    for epoch in range(training_epochs):
        start = (epoch * batch) % n_samples
        end = min(start+batch-1, n_samples)
        sess.run(training_step, feed_dict={x:X[start:end], y:Y[start:end]})
        if epoch % 1000 == 0:
            # 损失传进去的一定是整个数据集,在进行优化的时候可以一个batch一个batch进行传输
            print('w1:',sess.run(w1),'w2:',sess.run(w2),'cross_entropy:',sess.run(cross_entropy, feed_dict={x:X, y:Y}))

运行结果:

w1: [[-0.81231821  1.4855988   0.06632923]
 [-2.44370413  0.1002484   0.59222424]] w2: [[-0.81231821]
 [ 1.4855988 ]
 [ 0.06632937]] cross_entropy: 0.0674925
w1: [[-1.24812543  1.90805066  0.66588837]
 [-2.78695393  0.43289173  1.07395673]] w2: [[-1.1758728 ]
 [ 1.92894673]
 [ 0.4995679 ]] cross_entropy: 0.0184845
w1: [[-1.50642836  2.15626526  1.04335308]
 [-2.99071741  0.62583178  1.42270398]] w2: [[-1.38642049]
 [ 2.19844174]
 [ 0.81061947]] cross_entropy: 0.00942738
w1: [[-1.65557456  2.29861379  1.25609362]
 [-3.12099385  0.74799085  1.63670993]] w2: [[-1.51482654]
 [ 2.35542345]
 [ 1.00087345]] cross_entropy: 0.00724648
w1: [[-1.79234862  2.42781615  1.44464421]
 [-3.27053308  0.88764274  1.85585523]] w2: [[-1.65091252]
 [ 2.50544453]
 [ 1.18902004]] cross_entropy: 0.00587285

 

深层神经网络

深度学习在某种程度上可以认为等同于深层神经网络;深度学习在维基百科的定义是“一类通过多层非线性变换对高复杂性数据建模算法的合集”;从维基百科中可以看到,深度学习有两个非常重要的特性----多层和非线性,那么为什么要着重强调这两个特性呢?

多层:

首先,深度学习需要处理的都是大量数据,经过实践证明,多层的神经网络在处理数据方面虽然对计算能力要求比较高,但是多层神经网络在大量样本的情况下效果会更好

非线性:

在线性模型中,模型的输出为输入的加权和,假设一个模型的输出y和输入x满足以下关系,那么这个模型就是一个线性模型:

y = \sum_{i}^{m} w_{i}x_{i}+b 

被称为线性模型是因为当模型的输入只有一个的时候,x和y形成了二维坐标系上的一条直线;类似的,当模型有n个输入的时候,x和y形成了n+1维空间中的一个平面。而一个线性模型中通过输入得到输出的函数被称为一个线性变换。线性模式最大的一个特点就是任意线性模式的组合仍然是线性模型,前面前向传播算法就是一个标准的线性模型;只通过线性变换,任意层的全连接神经网络和单层神经网络模型的表达能力没有任何区别,而且他们都是线性模型,线性模型可以很好的处理线性可分的问题,但是对于非线性的问题,线性模型无法做出很好的预测,这就是线性模型最大的局限性

通过激活函数实现去线性化

上面提到线性模型无法处理复杂问题的缺陷,如果将每一个神经元(也就是神经网络的节点)的输出通过一个非线性函数,那么整个神经网络的模型就不再是线性的了,下面是神经网络结构加上激活函数和偏置项后的前向传播算法的数学定义:

tensorflow定义了七种非线性激活函数,当然,tensorflow中也支持自己定义激活函数,代码如下:

a = tf.nn.relu(tf.matmul(x, w1) + biased)
y = tf.nn.relu(tf.matmul(a, w2) + biased)

损失函数的定义

经典损失函数

分类

通过神经网络解决多分类问题最常用的方法就是设置N个输出点,数组中的每一个维度对应一个类别;在理想情况下,如果一个样本属于类别K,那么类别对应的输出节点的输出值就应该是等于1,而其他节点输出0;但是一般来说输出都是有误差的,那么怎么判断输出的向量和期望的向量有多接近呢?交叉熵是常用的评判方法之一,交叉熵刻画了两个概率分布之间的距离,他是分类问题中应用比较广的一种损失函数,公式如下:

H\left ( p \right ,q ) = -\sum_{x}^{ } p\left ( x \right )log\left ( q\left ( x \right ) \right )

其中p(x)是期望值,q(x)是实际得到的值

交叉熵描述的是输出值和期望值之间的距离,然而神经网络的输出并不一定是个概率分布。为了将前向传播的结果也变成一个概率分布吗,我们一般在输出层之后加上softmax层,softmax回归的参数被去掉了,它只是一个额外的处理层,将神经网络的输出变为一个概率分布;

经过softmax层产生的新的概率分布,然后在用交叉熵计算预测值和真实值的距离。

tensorflow代码如下:

# 交叉熵
cross_entropy = -tf.reduce_mean(y * tf.log(tf.clip_by_value(pred, 1e-10, 1.0)))
# y表示实际结果,pred表示预测结果
# tf.clip_by_value()函数可以将一个张量中的数值限定在一个范围之内这样可以避免一些运算错误

# 下面是clip_by_value函数的简单样例
v = tf.constant([[1.0,2.0,3.0],[4.0,5.0,6.0]])
print(tf.clip_by_value(v, 2.5, 4.5).eval())

# 输出结果
# [[2.5,2.5,3.],[4.,4.5,4.5]]
# 小于2.5的都被替换成了2.5,大于4.5的也被替换成了4.5

注意:

  • 代码中是直接通过‘*’实现矩阵的乘法运算的,但是 这里并不是矩阵乘法,而是元素之间的相乘,矩阵乘法需要通过tf.matmul函数来完成
  • 因为交叉熵一般会和softmax一起使用,所以tensorflow对这两个功能进行了一并封装,并提供了tf.nn.softmax_cross_entropy_with_logits(pred, y)这个函数来实现使用了softmax回归之后的交叉熵损失函数

回归

与分类问题不一样的是,回归问题解决的是对具体的数值的预测,这些问题需要预测的不是一个事先定义好的类别,而是一个任意实数;解决回顾问题的神经网络一般只有一个输出节点,这个节点的输出就是预测值;对于回归问题,最常用的损失函数是均方误差:

 

自定义损失函数:

tensorflow不仅支持经典的损失函数,还可以优化任意的自定义损失函数。

loss = tf.reduce_sum(tf.where(tf.greater(v1,v2), (v1-v2)*a, (v2-v1)*b))
  • tf.greater()传入两个张量,此函数会比较这两个输入张量中每一个元素的大小,并返回比较结果
  • tf.where()函数有三个参数,第一个作为选择条件,当选择条件为True的时候,tf.where会选择第二个参数中的值,否则会返回第三个参数中的值
  • 代码展示:
import tensorflow as tf
v1 = tf.constant([1.0, 2.0, 3.0, 4.0])
v2 = tf.constant([4.0, 3.0, 2.0, 1.0])

# 这里边是比较了Tensor中的每一个元素
with tf.Session() as sess:
    print(tf.greater(v1, v2).eval())
    print(tf.where(tf.greater(v1, v2), v1, v2).eval())

运行结果:

[False False  True  True]
[ 4.  3.  3.  4.]

下面通过一个实例来看看损失函数的作用:

import tensorflow as tf
from numpy.random import RandomState
import numpy as np

# 创造数据
rdm = RandomState(1)
#随机生成一个数据集
X = rdm.rand(128, 2)
# X = rdm.rand(128, 2) + np.random.randint(50,100,[128, 2])
Y = [[x1+x2+rdm.rand()/10.0-0.05] for (x1, x2) in X]
# print(X)
# print(Y)

# 定义张量
n_samples = X.shape[0]
training_epochs = 50000
batch = 10
w1 = tf.Variable(tf.random_normal([2,1], stddev=1, seed=1))
x = tf.placeholder(tf.float32, shape=[None,2])
y = tf.placeholder(tf.float32, shape=[None,1])
pred = tf.matmul(x, w1)

loss_less  = 1
loss_more = 10
# 学习率越小,轮数越多,那么就更容易找到全局最优
loss = tf.reduce_sum(tf.where(tf.greater(y,pred),(y-pred)*loss_less, (pred-y)*loss_more))
optimizer = tf.train.AdamOptimizer(0.001).minimize(loss)

# 执行循环
with tf.Session() as sess:
    init = tf.global_variables_initializer()
    sess.run(init)
    for i in range(training_epochs):
        start = (i * batch)  % n_samples
        end = min(start + batch - 1, n_samples)
        sess.run(optimizer, feed_dict={x:X[start:end], y:Y[start:end]})
        if i % 10000 == 0:
            print('loss:',sess.run(loss, feed_dict={x:X, y:Y}))
            print('w1:',sess.run(w1))

运行结果:

loss: 124.128
w1: [[-0.81231821]
 [ 1.48359871]]
loss: 6.56385
w1: [[ 0.95486355]
 [ 0.98035139]]
loss: 6.56008
w1: [[ 0.95606709]
 [ 0.98013008]]
loss: 6.57012
w1: [[ 0.95563829]
 [ 0.98271739]]
loss: 6.5633
w1: [[ 0.95543128]
 [ 0.98225933]]

实际的结果应该是x1+x2,但是最后预测的结果偏小,是因为代码中设定多预测一个亏损10,少预测一个亏损1,也就是说,在这样的设置下,模型会更加偏向预测少一点;如果把代码中的设定相反,那么最后预测的结果会是比1大一点。通过这个例子可以感受到,对于相同的神经网络,不同的损失函数会对训练得到的模型产生重要的影响

神经网络的优化算法

梯度下降和反向传播算法是常用的优化算法,梯度下降主要用于优化单个参数的取值,而反向传播算法给出一种高效的方式在所有参数上使用梯度下降算法,从而使神经网络模型在训练数据上的损失函数尽量小;目前来说没有一个通用的方法可以对任意损失函数直接求解最佳的参数取值,所以在实践中,梯度下降算法是最常用的神经网络优化算法,梯度下降算法会迭代更新参数,不断沿着梯度的反方向让参数朝着损失更小的方向更新;

神经网络的优化可以分为两个阶段,第一阶段是先通过前向传播算法计算得到的预测值,并将预测值和真实值进行比较并得出二者的差距;然后在第二阶段通过反向传播算法计算损失函数对每一个参数的梯度,再根据梯度和学习率使用梯度下降算法更新每一个参数

(个人总结:反向传播算法只是介绍一种思想,主要进行优化操作的还是梯度下降或者其他优化算法)仅代表个人拙见

需要注意的是,梯度下降算法并不能一定得到全局最优解,除非损失函数是一个严格的凸函数,参数的初始值在很大程度上决定了能不能找到全局最优;除了不一定能找到全局最优外,梯度下降算法的另外一个问题就是计算时间太长,因为要在全部训练数据上找到最小化损失,在海量的数据情况下,这是非常浪费时间的,为了加速训练,可以使用随机梯度下降算法,这个算法优化不是在全部的训练数据上,而是在每一轮的迭代中,这个算法的问题也是十分明显,就是在某一条数据上损失函数更小并不代表在全部数据上损失函数更小,于是使用随机梯度下降算法优化得到的神经网络甚至无法得到局部最优解;

为了综合梯度下降算法和随机梯度下降算法的优缺点,我们可以采用部分随机梯度,也就是每一轮选择一部分的数据进行迭代,

神经网络的进一步优化

学习率的设置(指数衰减法)

学习率就是梯度下降的步长,过大过小都不合适,tensorflow提供了一种更加灵活的学习率设置方式---指数衰减法,通过这个函数可以先使用较大的学习率来快速得到一个比较优的解,然后随着迭代的继续逐步减少学习率,使得模型在训练后更加稳定,代码如下:

decayed_learning_rate = learning_rate * decay_rate^(global_step/decay_steps)
# decay_rate为衰减系数
# decay_steps为衰减速度

通过tf.train_exponential_decay函数可以通过设置参数staircase选择不同的衰减模式;staircase的默认值为False,这是学习率随迭代轮数变化的趋势为一逐渐变小的平滑的曲线;当staircase为True的时候,global_step/decay_steps会被转化为整数,这使得学习率为一阶梯函数:

global_step = tf.Variable(0)

# 通过exponential_decay函数生成学习率
learning_rate = tf.train.exponential_decay(0.1, global_step, 100, 0.96, staircase=True)

# 使用指数衰减的学习率,在minimize函数中传入global_step将自动更新
# global_step参数,从而使得学习率也得到相应程度的更新
learning_rate = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss, global_step=global_step)

上述代码中设定了初始学习率是0.1,因为指定了staircase=True,所以每训练100轮后学习率乘以0.96;一般来说初始学习率、衰减速度和衰减系数都是根据经验设定的,而且损失函数下降的速度和迭代结束之后总损失的大小没有必然的联系

过拟合

过拟合的目的就是防止模型过度拟合训练数据上的随机噪音

为了避免过拟合问题,一种常用的方法是正则化,正则化形式有两种:

无论哪一种正则化方法,基本的思想就是希望通过限制权重的大小,使得模型不能任意拟合训练数据中的随机噪音;但是这两种正则化方法有很大的区别,首先L1正则化会让参数变得稀疏,L2正则化不会,所谓参数变得稀疏是指会有更多的参数变为0

tensorflow中支持正则化,代码实现:

loss = tf.reduce_mean(tf.square(y_-y) + tf.contrib.layers.l2_regularizer(lambda)(w))
# lambda表示了正则化的权重,w表示需要计算正则化损失的参数
# tf.contrib.layers.l2_regularizer函数返回一个函数,这个函数可以计算给定参数的L2正则化的值

但是当神经网络很复杂的时候,使用上述的定义方法可能在计算上会有很大的问题,tensorflow提供了集合的概念:

下面第一段代码出自tensorflow的一本书中,第二段代码是自己通过不同的方式(原理是一样的)创造数据集简单实现了一下

def get_weights(shape, lambda):
    # 生成一个变量
    var = tf.Variable(tf.random_normal(shape), dtype=tf.float32)
    # add_to_collection函数将这个新生成变量的L2正则化损失加入集合
    # 这个函数的第一个参数是losses是集合的名字,第二个参数是要加入这个集合的内容
    tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(lambda)(var))
    return var

x = tf.placeholder(tf.float32, shape=(None, 2))
y = tf.placeholder(tf.float32, shape=(None, 1))
batch_size = 8
# 定义了每一层网络节点中的个数
layer_dimension = [2, 10, 10, 10, 1]
# 神经网络的层数
n_layers = len(layer_dimension)

# 这个变量维护前向传播时最深层的节点,开始的时候就是输入层
cur_layer = x
in_dimension = layer_dimension[0]

# 通过一个循环来生成五层全连接的神经网络
for i in range(n_layers):
    out_dimension = layer_dimension[i]
    # 生成当前层中权重的变量,并将这变量的L2正则化损失加入到计算图上的集合
    weight = get_weights([in_dimension, out_dimension], 0.001)
    bias = tf.Variable(tf.constant(.1, shape=[out_dimension]))
    
    # 使用relu激活函数
    cur_layer = tf.nn.relu(tf.matul(cur_layer, weight) + bias)
    # 进入下一层之前将下一层的节点数更新为当前层节点个数
    in_dimension = out_dimension
    
# 在定义神经网络前向传播的同时已经将所有的L2正则项算是加入到图上的集合
# 这里只需要计算刻画模型在训练数据上表现的损失函数
mse_loss = tf.reduce_mean(tf.square(y-cur_layer))

# 将均方误差损失函数加入损失集合
tf.add_to_collection('losses', mse_loss)

# get_collection返回一个列表,这个列表是所以这个集合中的元素,在这个样例中,这些元素就是损失函数的不同部分,将他们加起来就得到了最终的损失函数
loss = tf.add_n(tf.get_collection('losses
# -*- coding: utf-8 -*-
"""
Created on Fri Mar  8 11:00:45 2019

@author: Happme
"""

import tensorflow as tf
from numpy.random import RandomState
import numpy as np

# 创建数据
rdm = RandomState(1)
X = rdm.randn(128, 2)
Y = [[int(x1+x2<1)] for (x1, x2) in X]
n_samples = X.shape[0]

training_epochs = 500
batch_size= 8
layer_dimension = [2, 10, 10, 10, 1]
n_layers = len(layer_dimension)
in_dimension = layer_dimension[0]
out_dimension = 0
global_step = 0
learning_rate = tf.train.exponential_decay(0.1, global_step, 100, 0.96, staircase=True)

x = tf.placeholder(tf.float32, shape=(None, 2))
y = tf.placeholder(tf.float32, shape=(None, 1))
pred = x
loss = 0.0
for i in range(1, n_layers):
    out_dimension = layer_dimension[i]
    shape=(in_dimension, out_dimension)
    weights = tf.Variable(tf.random_normal(shape, stddev=1, seed=1), dtype=tf.float32)
    biases = tf.Variable(tf.constant(0.1, shape=[out_dimension]), dtype=tf.float32)
    
    loss = tf.add(loss , tf.contrib.layers.l2_regularizer(0.001)(weights))
    pred = tf.nn.relu(tf.matmul(pred, weights) + biases)
    in_dimension = out_dimension
loss = loss + tf.reduce_mean(tf.square(y - pred))
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(loss)
init = tf.global_variables_initializer()

with tf.Session() as sess:
    sess.run(init)
    for epoch in range(training_epochs):
        start = (epoch * batch_size) % n_samples
        end = min(start + batch_size, n_samples)
        sess.run(optimizer, feed_dict={x:X[start:end], y:Y[start:end]})
        if epoch % 100 == 0:
            print(sess.run(loss, feed_dict={x:X, y:Y}))

滑动平均模型

在采用随机梯度下降算法训练神经网络的时候,使用滑动平均模型在很多应用上可以在一定程度上提高最终模型在测试数据上的表现;在tensorflow中提供了tf.train.ExpoentialMovingAverage来实现滑动平均模型;在初始化这个函数的时候需要提供一个衰减率,这个衰减率将用于控制模型更新的速度,ExpoentialMovingAverage对每一个变量会维护一个影子变量,这个影子变量的初始值是相应变量的初始值,而每次运行变量更新时,影子变量的值都会更新为:

shadoe_variable = decay * shadow_variable + (1-decay) * variable

其中shaow_decay为影子变量,variable为待更新的变量,decay为衰减率;从模型中可以看到,decay决定了更新的速度,decay越大模型越趋于稳定,在实际应用中,decay一般会设置成非常接近1的数;为了使模型在训练前期可以更新的更快,ExponentialMovingAverage还提供了num_updates参数来动态设置decay的大小;如果在初始化时提供了num_updates参数,那么每次使用的衰减率将会是:

min{decay, 1+num_updates/(10 + num_updates)}

下面一段代码来解释ExponentialMovingAverage是如何被使用的:

import tensorflow as tf

# 定义一个变量用来计算滑动平均,这个变量的初始值为0,注意这里手动指定了变量的类型,因为所有需要计算滑动平均的变量都必须是实数型
v1 = tf.Variable(0, dtype=tf.float32)
# 这里的step变量模拟神经网络中迭代的轮数,可以用于动态控制衰减率
step = tf.Variable(0, trainable=False)   # 表示为不可训练变量

# 定义一个滑动平均的类
ema = tf.train.ExponentialMovingAverage(0.99, step)
# 定义一个更新变量滑动平均的操作,这里需要给定一个列表,每次执行这个操作的时候,这个列表中的值都会被更新
maintain_average_op = ema.apply([v1])
with tf.Session() as sess:
    init = tf.global_variables_initializer()
    sess.run(init)
    
    # 通过ema.average(v1)获得滑动平均之后变量的取值、
    sess.run(maintain_average_op)
    print(sess.run([v1, ema.average(v1)]))
    sess.run(v1.assign(5))
    sess.run(maintain_average_op)
    print(sess.run([v1, ema.average(v1)]))

运行结果:

[0.0, 0.0]
[5.0, 4.5]

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值