夜间模式暗黑模式
字体
阴影
滤镜
圆角
主题色
TensorFlow学习笔记2 – 神经网络的激活函数与损失函数及优化

深度学习和深层神经网络

深度学习:一种通过多层非线性变换对高复杂性数据建模算法的合集

特点: 多层和非线性

激活函数和偏置项

$$ A_1 = [a_{11}, a_{12}, a_{12}] = f(xW^{(1)} + b) $$ 新的公式增加了偏置项(bias), 并且每个节点取值不再是单纯的加权和, 每个节点的输出在加权和的基础上还做了一个非线性变换

常见的几种非线性激活函数的函数图像:

ReLU函数: $f(x)=  max(x, 0)$

sigmoid函数: $f(x) = \frac{1}{1+e^{-x}}$

tanh函数: $f(x) = \frac{1-e^{-2x}}{1+e^{-2x}}$

多层网络

感知机可以简单理解为单层神经网络, 无法模拟亦或运算, 加入隐藏层后, 亦或问题就可以很好的解决.

损失函数

交叉熵

通过神经网络解决多分类问题最常用的方法是设置n个输出节点, 其中n为类别的个数. 对于每个样例, 神经网络可以得到一个n维的数组作为输出结果. 数组每个元素属于一个类别. 在理想情况下, k类别的样本对应类别的输出节点为1

, 其它都为0

交叉熵可以用来刻画两个概率分布的距离

给定两个概率分布p和q, 通过q来表示p的交叉熵为: $$ H(p, q) = -\sum_{x}p(x)\log{q(x)} $$ 它刻画的是通过概率分布q来表达概率分布p的困难程度

Softmax回归

Softmax回归可以将神经网络的输出变成一个概率分布 $$ softmax(y) _i = y’ _i = \frac{e^{yi}}{\sum^{n} _{j=1}e^{yj}} $$

代码实现

cross_entropy = -tf.reduce_mean(
    y_*tf.log(tf.clip_by_value(y, 1e-10, 1.0))
)

tf.clip_by_value()可以将一个张量中的数值限制在一个范围, 超出这个范围的将被替换为最近的边界值

将两个矩阵通过*相乘不是矩阵乘法, 而是元素之间一一直接相乘

因为交叉熵通常会和softmax一起使用, 所以tf对这两个功能提供了统一封装函数tf.nn.softmax_cross_entropy_with_logits()(已经废弃, 使用cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2()代替

cross_entropy = tf.nn.softmax_cross_entropy_with_logits(y, y_)
# y代表神经网络输出结果, y_代表标准答案
# 如果只有一个正确答案, 可以使用sparse开头的函数加快过程
tf.nn.sparse_softmax_cross_entropy_with_logits()

均方误差(MSE)

回归问题需要解决的是对具体数值的预测, 解决回归问题的神经网络一般只有一个输出节点(预测值), 这类问题常用的损失函数是均方误差(MSE, mean squared error)(均方误差也是分类问题的常用损失函数)

公式: $$ MSE(y, y’) = \frac{\sum^{n}_{i-1}{(y_i-y_i’)}^2}{n} $$ 代码:

mse = tf.reduce_mean(tf.square(y_-y))

自定义损失函数

可以自定义损失函数, 使结果更接近实际问题的需求, 比如使得神经网络更偏向向偏多的方向, 可以给偏多和偏少不同的损失系数

tf.select(tf.greater(v1, v2), (v1-v2)*a, (v2-v1)*b)

tf.select()类似c中的三目运算符

神经网络优化算法

[反向传播算法解析](<https://www.cnblogs.com/charlotte77/p/5629865.html)

各种优化算法:https://zhuanlan.zhihu.com/p/27449596

https://juejin.im/entry/5b922cddf265da0aa664a02e

梯度下降算法(gradient decent)主要用于优化单个参数的取值, 而反向传播算法(backpropagation)给出了一个高效的方式在所有参数啥时候使用梯度下降算法.

反向传播算法是训练神经网络的核心算法, 它可以根据定义好的损失函数优化神经网络中的参数的取值, 从而使神经网络模型在训练集上的损失函数达到一个较小值

梯度下降算法

假设用$\theta$表示神经网络中的参数, $J(\theta)$表示在给定参数取值下, 训练数据集上损失函数的大小, 整个优化过程可以抽象为寻找一个$\theta$, 使得$J(\theta)$最小

目前并没有通用的方法可以对任意损失函数求解最佳值, 实践中, 梯度下降算法是最常用的神经网络优化算法.

参数的梯度通过求偏导的方式计算, 对于参数$\theta$, 其梯度为$\frac{\delta}{\delta \theta}J(\theta)$

学习率$\eta$可以认为是每次参数移动的幅度

参数更新的公式为: $$ \theta_{n+1} = \theta_n – \eta \frac{\delta}{\delta \theta}J(\theta_n) $$ 缺点:

  • 梯度下降算法并不能保证被优化的函数达到全局最优解, 只有当损失函数为凸函数的时候才能保证达到全局最优解, 参数的初始值会很大程度影响最后的结果
  • 计算时间太长, 需要计算每一轮迭代中全部训练数据上的损失函数

随机梯度下降算法

每一轮迭代中, 只随机优化某一条数据上的损失函数

速度加快但是问题也很明显, 某一条数据可能无法代表全部数据, 于是随机算法优化得到的参数甚至无法都达到局部最优

折中

实际上一般采用上面两种算法的折中, 每次计算一小部分训练数据的损失函数

各种优化算法:https://zhuanlan.zhihu.com/p/27449596

https://juejin.im/entry/5b922cddf265da0aa664a02e

进一步优化

学习率的设置 – 指数衰减法

学习率(learnig rate)用于控制参数更新的速度, 决定了参数每次更新的幅度

过小会导致优化速度, 过大会导致无法收敛

为此TF提供了一种灵活的学习率设置方法, 指数衰减法:

tf.train.exponential_decay(
    learning_rate=0.1, global_step=global_step,
    decay_steps=100, decay_rate=0.96, staircase=True)

函数实现了以下代码的功能:

decayed_learning_rate = 
	learning_rate * decay_rate ^ (global_step / decay_steps)
# 相当于每decay_steps轮迭代学习率*decay_rate

其中learning_rate为初始学习率, decay_rate为衰减系数, decay_steps为衰减速度(通常为完整使用一边训练数据所需要的迭代轮数, 也就是dataset_size/batch_size), global_step为当前迭代轮数

staircase=True时, global_step / decay_steps会被转换成整数, 也就是说只有迭代完一轮全部数据后, 学习率才会变化, 使得数据集中所有数据对模型训练有相同的作用(不然后面的数据影响能力更小)

其他学习率控制方式

过拟合问题 – 正则化

模型在训练数据上的表现并不一定代表它在未知数据上的表现

过拟合指的是, 当一个模型过于复杂后, 它可以很好的"记忆"每一个训练数据中随机噪声的的部分而没有很好的"学习"训练数据中通用的趋势

为了避免过拟合问题, 一个常用的方法是正则化(regularization), 其思想是在损失函数中增加刻画模型复杂度的指标

损失函数为$J(\theta)$, $R(w)$刻画模型复杂程度, 优化时不是直接优化$J(\theta)$而是优化$J(\theta) + \lambda R(w)$, $\lambda$表示模型复杂损失占总损失的权重, $\theta$表示神经网络中所有参数, 包括权重$w$和偏置项$b$, 一般模型复杂度只由权重$w$决定

常用的刻画模型复杂度的函数$R(w)$有两种, 一种是L1正则化: $$ R(w) = \|w\| _1 = \sum _{i}{|w _i|} $$

L2正则化: $$ R(w) = \|w\|^2 _2 = \sum _{i}{w _i^2} $$ (两竖加一个下表x代表向量的x-范数, 1-范数为所有元素绝对值之和, 2-范数代表所有元素平方和再开方)

两种正则化有很大的区别, 首先L1会让参数变得稀疏(更多参数变为0), 这样可以达到特征选取的功能, 而L2不会, 因为L2有一个平方, 但参数变得很小的时候, 它的平方会变得更小而基本可以忽略, 于是模型不会将其进一步调整为0

其次L1计算公式不可导, 而L2可导, 优化是需要计算损失函数的偏导, 所以对L2优化要更加简洁

在实践中, 也可以将L1和L2正则化同时使用: $$ R(w) = \sum_{i}\alpha|w_i|+(1-\alpha)w_i^2 $$ 代码:

loss = tf.reduce_mean(tf.square(y_-y)) + tf.contrib.layers.l2_regularizer(0.5)(w)

当神经网络参数增多后, 这样的定义方式会很麻烦, 为此可以利用TF中提供的集合(collection), 将损失函数所有项都放入集合(tf.add_to_colletion), 最后使用tf.add_n获取所有项的和

import tensorflow as tf

# 将权重的L2正则化损失加入名为'losses'的collection中
def get_weight(shape, lam):
    var = tf.Variable(tf.random_normal(shape), dtype=tf.float32)
    tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(lam)(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(1, n_layers):
    # 下一层节点个数
    out_dimension = layer_dimension[i]
    # 当前层权重变量 并将其L2正则化损失加入集合
    weight = get_weight([in_dimension, out_dimension], 0.001)
    bias = tf.Variable(tf.constant(0.1, shape=[out_dimension]))
    
    # 使用ReLU激活函数
    cur_layer = tf.nn.relu(tf.matmul(cur_layer, weight) + bias)
    in_dimension = layer_dimension[i]

# mse均方差, 刻画模型在训练数据上的损失函数
mse_loss = tf.reduce_mean(tf.square(y_-cur_layer))

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

# get_collection返回一个列表包含集合所有元素, 在这里就是损失函数的所有项
loss = tf.add_n(tf.get_collection('losses'))

滑动平均模型

理解滑动平均(exponential moving average)

滑动平均模型可以使模型在测试数据上更健壮

tf中提供了tf.train.ExponentialMovingAverage来实现滑动平均模型, 初始化ema(ExponentialMovingAverage)时需要提供一个衰减率(decay)用于控制模型更新速率, ema对每一个变量会维护一个影子变量(shadow_variable), 其初始值是相应变量的初始值, 每次变量更新时, 影子变量更新为:

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

影子变量可以当作变量variable的最近$\frac{1}{1-decay}$次取值的平均值, 例如decay=0.99, 那么影子变量就相当于变量的最近100次取值的均值

测试时, 使用影子变量代替原本的变量可以使模型更健壮, 因为影子变量更新更平滑, 最后的$\frac{1}{1-decay}$训练中, 模型训练早已完成, 处于抖动阶段, 影子变量相当于最后那几次抖动的平均

ema还提供了num_updates参数来动态设置decay大小, 使用此参数后, 每次使用的衰减率为: $$ \min{\left\{ decay, \frac{1+num\_updates}{10+num\_updates} \right\}} $$ 代码:

# 测试ema
import tensorflow as tf

# 定义了一个变量用于计算滑动平均
v1 = tf.Variable(0, dtype=tf.float32)
# step模拟迭代轮数, 用于动态控制衰减率
step = tf.Variable(0, trainable=False)
ema = tf.train.ExponentialMovingAverage(decay=0.99, num_updates=step)

# 定义了一个更新滑动平均的操作
# 这里需要给定一个列表, 每次执行这个操作时, 列表中的变量都会更新
maintain_averages_op = ema.apply([v1])

with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
    print(sess.run([v1, ema.average(v1)]))

    # 更新v1为5
    sess.run(tf.assign(v1, 5))
    sess.run(maintain_averages_op)
    print(sess.run([v1, ema.average(v1)]))

    # 更新step为10000
    sess.run(tf.assign(step, 10000))
    # 更新v1为10
    sess.run(tf.assign(v1, 10))
    sess.run(maintain_averages_op)
    print(sess.run([v1, ema.average(v1)]))
    
    sess.run(maintain_averages_op)
    print(sess.run([v1, ema.average(v1)]))
暂无评论

发送评论 编辑评论


				
上一篇
下一篇