前言
上篇笔记我们利用 MNIST 数据集训练了一个手写数字识别的模型, 但是准确率非常的低, 维持在 91% 左右, 我们可以尝试着将准确率提高到 96% 以上, 在实验之前我们需要先了解一些基本的概念, 本篇文章可能会有些枯燥, 因为大多都是理论知识.
本文重点
激活函数
代价函数
拟合
什么是激活函数? 激活函数是干嘛的?
想了解什么是激活函数, 就要先了解神经网络的基本模型, 下图所示为一单一人工神经网络的基本模型图:
神经网络中的每个神经元节点接受上一层神经元的输出值作为本神经元的输入值, 并将输入值传递给下一层, 输入层神经元节点会将输入属性值直接传递给下一层(隐藏层或输出层). 在多层神经网络中, 上层节点的输出和下层节点的输入之间具有一个函数关系, 这个函数称为激活函数(又称激励函数).
如果我们不运用激活函数的话, 则输出信号将仅仅是一个简单的线性函数. 线性函数一个一级多项式. 现如今, 线性方程是很容易解决的, 但是它们的复杂性有限, 并且从数据中学习复杂函数映射的能力更小. 一个没有激活函数的神经网络将只不过是一个线性回归模型 (Linear regression Model) 罢了, 它功率有限, 并且大多数情况下执行得并不好. 我们希望我们的神经网络不仅仅可以学习和计算线性函数, 而且还要比这复杂得多. 同样是因为没有激活函数, 我们的神经网络将无法学习和模拟其他复杂类型的数据, 例如图像, 视频, 音频, 语音等. 这就是为什么我们要使用人工神经网络技术, 诸如深度学习(Deep learning), 来理解一些复杂的事情, 一些相互之间具有很多隐藏层的非线性问题, 而这也可以帮助我们了解复杂的数据.
那么为什么我们需要非线性函数?
非线性函数是那些一级以上的函数, 而且当绘制非线性函数时它们具有曲率. 现在我们需要一个可以学习和表示几乎任何东西的神经网络模型, 以及可以将输入映射到输出的任意复杂函数. 神经网络被认为是通用函数近似器(Universal Function Approximators). 这意味着他们可以计算和学习任何函数. 几乎我们可以想到的任何过程都可以表示为神经网络中的函数计算.
而这一切都归结于这一点, 我们需要应用激活函数 f(x), 以便使网络更加强大, 增加它的能力, 使它可以学习复杂的事物, 复杂的表单数据, 以及表示输入输出之间非线性的复杂的任意函数映射. 因此, 使用非线性激活函数, 我们便能够从输入输出之间生成非线性映射.
激活函数的另一个重要特征是: 它应该是可以区分的. 我们需要这样做, 以便在网络中向后推进以计算相对于权重的误差 (丢失) 梯度时执行反向优化策略, 然后相应地使用梯度下降或任何其他优化技术优化权重以减少误差.
二次代价函数
二次代价函数的公式如下:
其中, C 表示代价, x 表示样本, y 表示实际值, a 表示输出值, n 表示样本的总数. 为简单起见, 以一个样本为例进行说明, 此时二次代价函数为:
其中
, 是激活函数
加入我们使用梯度下降法来调整权值参数的大小, 权值 w 和偏置 b 的梯度推导如下:
其中, z 表示神经元的输入,表示激活函数. 从以上公式可以看出, w 和 b 的梯度跟激活函数的梯度成正比, 激活函数的梯度越大,和 的大小调整得越快, 训练收敛得就越快. 而神经网络常用的激活函数为 sigmoid 函数, 该函数的曲线如下所示:
所以在这种情况下, 权值和偏置的变化就会出现如下异常:
假设我们目标是收敛到 1.A 点为 0.82 离目标比较远, 梯度比较大, 权值调整比较大. B 点为 0.98 离目标比较近, 梯度比较小, 权值调整比较小. 调整方案合理. 假如我们目标是收敛到 0. A 点为 0.82 离目标比较近, 梯度比较大, 权值调整比较大. B 点为 0.98 离目标比较远, 梯度比较小, 权值调整比较小. 调整方案不合理.
那么可能有人就会说, 如果我们想要解决上述问题, 选择一个梯度不变化或变化不明显的激活函数不就解决问题了吗? 图样图森破, 那样虽然简单粗暴地解决了这个问题, 但可能会引起其他更多更麻烦的问题. 而且, 类似 sigmoid 这样的函数 (比如 tanh 函数) 有很多优点, 非常适合用来做激活函数, 具体请自行 google 之.
在这里我们不改变激活函数, 选择将代价函数改为交叉熵代价函数.
交叉熵代价函数
先放公式:$$C=-\frac{1}{n}\sum_{x}^{ }[ylna+(1-y)ln(1-a)]$$ 其中, C 表示代价, x 表示样本, y 表示实际值, a 表示输出值, n 表示样本的总数. 那么, 重新计算参数 w 的梯度:
其中:$${\sigma }'(z)=\sigma (z)(1-\sigma (z))$$ 因此, w 的梯度公式中原来的 被消掉了; 另外, 该梯度公式中的 表示输出值与实际值之间的误差. 所以, 当误差越大, 梯度就越大, 参数 w 调整得越快, 训练速度也就越快. 同理可得, b 的梯度为:
实际情况证明, 交叉熵代价函数带来的训练效果往往比二次代价函数要好.
权值和偏置值的调整与 无关, 另外, 梯度公式中的 表示输出值与实际值的误差. 所以当误差越大时, 梯度就越大, 参数 w 和 b 的调整就越快, 训练的速度也就越快.
如果输出神经元是线性的, 那么二次代价函数就是一种合适的选择. 如果输出神经元是 S 型函数, 那么比较适合用交叉熵代价函数.
对数释然代价函数(log-likelihood cost)
对数释然函数常用来作为 softmax 回归的代价函数, 然后输出层神经元是 sigmoid 函数, 可以采用交叉熵代价函数. 而深度学习中更普遍的做法是将 softmax 作为最后一层, 此时常用的代价函数是对数释然代价函数.
对数似然代价函数与 softmax 的组合和交叉熵与 sigmoid 函数的组合非常相似. 对数释然代价函数在二分类时可以化简为交叉熵代价函数的形式. 在 tensorflow 中用:
tf.nn.sigmoid_cross_entropy_with_logits()来表示跟 sigmoid 搭配使用的交叉熵.
tf.nn.softmax_cross_entropy_with_logits()来表示跟 softmax 搭配使用的交叉熵.
使用 TensorFlow 比较两种代价函数的效果
以上一篇文章手写数字识别的模型为例子, 在这给出采用交叉熵函数的模型的代码:
- import datetime
- # 4.1 交叉熵代价函数
- import tensorflow as tf
- from tensorflow.examples.tutorials.mnist import input_data
- start = datetime.datetime.now()
- # 载入数据
- mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
- # 每个批次的大小
- batch_size = 50
- # 计算一共有多少个批次
- n_batch = mnist.train.num_examples // batch_size
- # 定义两个 placeholder
- x = tf.placeholder(tf.float32, [None, 784])
- y = tf.placeholder(tf.float32, [None, 10])
- # 创建一个简单的神经网络
- W = tf.Variable(tf.zeros([784, 10]))
- b = tf.Variable(tf.zeros([10]))
- prediction = tf.nn.softmax(tf.matmul(x, W)+b)
- # 二次代价函数
- # loss = tf.reduce_mean(tf.square(y-prediction))
- # 交叉熵代价函数
- loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(
- labels=y, logits=prediction))
- # 使用梯度下降法
- train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
- # 初始化变量
- init = tf.global_variables_initializer()
- # 结果存放在一个布尔型列表中
- # argmax 返回一维张量中最大的值所在的位置
- correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(prediction, 1))
- # 求准确率
- accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
- with tf.Session() as sess:
- sess.run(init)
- for epoch in range(30):
- for batch in range(n_batch):
- batch_xs, batch_ys = mnist.train.next_batch(batch_size)
- sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys})
- acc = sess.run(accuracy, feed_dict={
- x: mnist.test.images, y: mnist.test.labels})
- print("Iter"+str(epoch)+",Testing Accuracy"+str(acc))
- end = datetime.datetime.now()
- print((end-start).seconds)
在这里我们将二次代价函数更改为了交叉熵代价函数:
- # 二次代价函数
- # loss = tf.reduce_mean(tf.square(y-prediction))
- # 交叉熵代价函数
- loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(
- labels=y, logits=prediction))
接下来我们来对比下训练的结果:
由上图可知, 使用二次代价函数训练第 10 次的精确度为 0.9063, 而使用交叉熵代价函数训练到第 2 次的精确度就已经超过 0.9 了, 结果显而易见.
拟合
拟合分为三种: 1. 欠拟合(underfitting);2. 正确拟合(just right);3. 过拟合(overfitting); 如下图所示:
其中每个 x 表示的是样本, 每条曲线代表的是模型. 下图是分类问题中的拟合情况, 和上述情况类似.
在这里介绍过拟合, 下面是 wikipedia 对于 overfitting 的解释. 在统计学和机器学习中, overfitting 一般在描述统计学模型随机误差或噪音时用到. 它通常发生在模型过于复杂的情况下, 如参数过多等. overfitting 会使得模型的预测性能变弱, 并且增加数据的波动性.
发生 overfitting 是因为评判训练模型的标准不适用于作为评判该模型好坏的标准, 模型通常会增强模型在训练模型的预测性能. 但是模型的性能并不是由模型在训练集的表现好坏而决定, 它是由模型在未知数据集上的表现确定的. 当模型开始 "memorize" 训练数据而不是从训练数据中 "learning" 时, overfitting 就出现了. 比如, 如果模型的 parameters 大于或等于观测值的个数, 这种模型会显得过于简单, 虽然模型在训练时的效果可以表现的很完美, 基本上记住了数据的全部特点, 但这种模型在未知数据的表现能力会大减折扣, 因为简单的模型泛化能力通常都是很弱的.
上面这个图, 是通过线性函数和多项式函数来拟合这些数据点, 显然多项式函数拟合效果很完美, 包含了所有的点, 而线性函数丢失了大部分点. 但实际上, 线性函数有一个很好的泛化能力, 如果用这些点来做一个回归线, 多项式函数过拟合的情况更糟糕.
过拟合不仅和参数的个数以及数据有关, 也和数据形状模型结构的一致性有关.
为了避免过拟合, 有必要使用一些额外的技术(如交叉验证, 正则化, early stopping, 贝斯信息量准则, 赤池信息量准则或 model comparison), 以指出何时会有更多训练而没有导致更好的一般化.
Overfitting 的概念在机器学习中很重要. 通常一个学习算法是借由训练样本来训练的, 在训练时会伴随着训练误差. 当把该模型用到未知数据的测试时, 就会相应的带来一个 validation error. 下面通过训练误差和验证误差来详细分析一下 overfitting. 如下图:
在上图总, 蓝色表示训练误差 training error, 红色表示 validation error. 当训练误差达到中间的那条垂直线的点时, 模型应该是最优的, 如果继续减少模型的训练误差, 这时就会发生过拟合.
其实你可以这样来理解 overfitting: 数据集中信息分为两部分, 一部分是和预测未来数据有关的数据, 另一部分是无关的, 两者地位是平等的. 用来作为预测的评判标准越不精确, 表明噪声数据就越多, 需要忽略掉的数据也就越多, 而关键就是究竟那一部分应该忽略掉. 所以我们把一个学习算法对噪声的削减能力就叫做它的鲁棒性. 我们需要的就是鲁棒性很强的学习算法
举一个简单的例子, 一个零售购物的数据库包括购买项, 购买人, 日期, 和购买时间. 根据这个数据可以很容易的建立一个模型, 并且在训练集上的拟合效果也会很好, 通过使用日期, 购买时间来预测其它属性列的值, 但是这个模型对于新数据的泛化能力很弱, 因为这些过去的数据不会再次发生.
防止过拟合的几种方式
- import datetime
- # 4.2 Dropout
- import tensorflow as tf
- from tensorflow.examples.tutorials.mnist import input_data
- start = datetime.datetime.now()
- # 载入数据
- mnist = input_data.read_data_sets("MNIST_data", one_hot=True)
- # 每个批次的大小
- batch_size = 50
- # 计算一共有多少个批次
- n_batch = mnist.train.num_examples // batch_size
- # 定义两个 placeholder
- x = tf.placeholder(tf.float32, [None, 784])
- y = tf.placeholder(tf.float32, [None, 10])
- keep_prob = tf.placeholder(tf.float32)
- # 创建一个神经网络
- W1 = tf.Variable(tf.truncated_normal([784, 2000], stddev=0.1))
- b1 = tf.Variable(tf.zeros([2000])+0.1)
- L1 = tf.nn.tanh(tf.matmul(x, W1)+b1)
- L1_drop = tf.nn.dropout(L1, keep_prob)
- W2 = tf.Variable(tf.truncated_normal([2000, 2000], stddev=0.1))
- b2 = tf.Variable(tf.zeros([2000])+0.1)
- L2 = tf.nn.tanh(tf.matmul(L1_drop, W2)+b2)
- L2_drop = tf.nn.dropout(L2, keep_prob)
- W3 = tf.Variable(tf.truncated_normal([2000, 1000], stddev=0.1))
- b3 = tf.Variable(tf.zeros([1000])+0.1)
- L3 = tf.nn.tanh(tf.matmul(L2_drop, W3)+b3)
- L3_drop = tf.nn.dropout(L3, keep_prob)
- W4 = tf.Variable(tf.truncated_normal([1000, 10], stddev=0.1))
- b4 = tf.Variable(tf.zeros([10])+0.1)
- prediction = tf.nn.softmax(tf.matmul(L3_drop, W4)+b4)
- # 二次代价函数
- # loss = tf.reduce_mean(tf.square(y-prediction))
- # 交叉熵代价函数
- loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(
- labels=y, logits=prediction))
- # 使用梯度下降法
- train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
- # 初始化变量
- init = tf.global_variables_initializer()
- # 结果存放在一个布尔型列表中
- # argmax 返回一维张量中最大的值所在的位置
- correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(prediction, 1))
- # 求准确率
- accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
- with tf.Session() as sess:
- sess.run(init)
- for epoch in range(20):
- for batch in range(n_batch):
- batch_xs, batch_ys = mnist.train.next_batch(batch_size)
- sess.run(train_step, feed_dict={
- x: batch_xs, y: batch_ys, keep_prob: 1.0})
- test_acc = sess.run(accuracy, feed_dict={
- x: mnist.test.images, y: mnist.test.labels, keep_prob: 1.0})
- train_acc = sess.run(accuracy, feed_dict={
- x: mnist.train.images, y: mnist.train.labels, keep_prob: 1.0})
- print("Iter"+str(epoch)+",Testing Accuracy" +
- str(test_acc)+",Train Accuracy"+str(train_acc))
- end = datetime.datetime.now()
- print((end-start).seconds)
- W1 = tf.Variable(tf.truncated_normal([784, 2000], stddev=0.1))
- b1 = tf.Variable(tf.zeros([2000])+0.1)
- L1 = tf.nn.tanh(tf.matmul(x, W1)+b1)
- L1_drop = tf.nn.dropout(L1, keep_prob)
- W2 = tf.Variable(tf.truncated_normal([2000, 2000], stddev=0.1))
- b2 = tf.Variable(tf.zeros([2000])+0.1)
- L2 = tf.nn.tanh(tf.matmul(L1_drop, W2)+b2)
- L2_drop = tf.nn.dropout(L2, keep_prob)
- W3 = tf.Variable(tf.truncated_normal([2000, 1000], stddev=0.1))
- b3 = tf.Variable(tf.zeros([1000])+0.1)
- L3 = tf.nn.tanh(tf.matmul(L2_drop, W3)+b3)
- L3_drop = tf.nn.dropout(L3, keep_prob)
- W4 = tf.Variable(tf.truncated_normal([1000, 10], stddev=0.1))
- b4 = tf.Variable(tf.zeros([10])+0.1)
- prediction = tf.nn.softmax(tf.matmul(L3_drop, W4)+b4)
- for epoch in range(10):
- for batch in range(n_batch):
- batch_xs, batch_ys = mnist.train.next_batch(batch_size)
- sess.run(train_step, feed_dict={
- x: batch_xs, y: batch_ys, keep_prob: 1.0})
- test_acc = sess.run(accuracy, feed_dict={
- x: mnist.test.images, y: mnist.test.labels, keep_prob: 1.0})
- train_acc = sess.run(accuracy, feed_dict={
- x: mnist.train.images, y: mnist.train.labels, keep_prob: 1.0})
- print("Iter"+str(epoch)+",Testing Accuracy" +
- str(test_acc)+",Train Accuracy"+str(train_acc))
来源: https://juejin.im/post/5c0fe343f265da6147701186