神经网络模型的效果及优化的目标是通过损失函数来定义的。

1、经典损失函数

分类问题和回归问题是监督学习的两大种类。

分类问题

常用方法:交叉熵(cross_entropy),它描述了两个概率分布之间的距离,当交叉熵越小说明二者之间越接近。它是分类问题中使用比较广的一种损失函数。
给定两个概率分布p和q,通过q来表示p的交叉熵为:

H(p,q)=xp(x)logq(x)H(p,q)=−∑xp(x)log⁡q(x)
<script type="math/tex; mode=display" id="MathJax-Element-1">H(p,q)=-\sum_x p(x)\log q(x) \qquad</script>

交叉熵刻画的是两个概率分布之间的距离,但是神经网络的输出却不一定是一个概率分布。概率分布刻画了不同事件发生的概率。当事件总数是有限的情况下,概率分布函数p(X=x)满足:

x   p(X=x)[0,1]xp(X=x)=1∀x   p(X=x)∈[0,1]且∑xp(X=x)=1
<script type="math/tex; mode=display" id="MathJax-Element-2">\forall x \ \ \ p(X=x)\in [0,1] 且 \sum_x p(X=x) =1 </script>

如何将神经网络前向传播得到的结果变成概率分布,Softmax回归就是一个非常常用的办法。
Softmax回归本身可以作为一个学习算法来优化分类结果,但在TensorFlow中,Softmax回归的参数被去掉了,它只是一层额外的处理层,将神经网络的输出变成一个概率分布。下图展示了加上Softmax回归的神经网络结构图。

这里写图片描述

交叉熵作为神经网络的损失函数时,p代表的是正确答案,q代表的是预测值。交叉熵刻画的是两个概率分布的距离,交叉熵值越小,两个概率分布越接近。

案例:
有个三分类问题,样例正确答案(1,0,0)。
某模型经过Softmax回归之后的预测答案是(0.5,0.4,0.1),那么这个预测和正确答案之间的交叉熵为:

H((1,0,0),(0.5,0.4,0.1))=(1×log0.5+0×log0.4+0×log0.1)0.3H((1,0,0),(0.5,0.4,0.1))=−(1×log0.5+0×log0.4+0×log0.1)≈0.3
<script type="math/tex; mode=display" id="MathJax-Element-3">H((1,0,0),(0.5,0.4,0.1)) = -(1\times log0.5 + 0\times log0.4 + 0 \times log0.1)\approx 0.3 </script>
另一个模型的预测是(0.8,0.1,0.1),那么这个预测和真实值之间的交叉熵是:
H((1,0,0),(0.8,0.1,0.1))=(1×log0.8+0×log0.1+0×log0.1)0.1H((1,0,0),(0.8,0.1,0.1))=−(1×log0.8+0×log0.1+0×log0.1)≈0.1
<script type="math/tex; mode=display" id="MathJax-Element-4">H((1,0,0),(0.8,0.1,0.1)) = -(1\times log0.8 + 0\times log0.1 + 0 \times log0.1)\approx 0.1 </script>
从直观上可以很容易地知道第二个预测答案要优于第一个。通过交叉熵计算得到的结果也是一致的(第二个交叉熵的值更小)。

TensorFlow实现交叉熵,代码如下:

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

tf.clip_by_value函数可以将一个张量中的数值限制在一个范围内,这样就避免了一些运算错误(比如log0是无效的)。

y_:正确结果
y :预测结果

TensorFlow对交叉熵和softmax回归进行了统一封装,我们可以直接使用如下代码实现使用softmax回归后的交叉熵损失函数:

cross_entropy = tf.nn.softmax_cross_entropy_with_logits(y,y_)
回归问题

回归问题解决的是对具体数值的预测。比如房价预测、销量预测等都是回归问题。这些问题需要预测的不是一个事先定义好的类别,而是一个任意实数。解决回顾问题的神经网络一般只有一个输出节点,这个节点的输出值就是预测值。对于回归问题,最常用的损失函数是均方误差(MSE,mean squared error )。它的定义如下:

MSE(y,y)=ni=1(yiyi)2nMSE(y,y′)=∑i=1n(yi−yi′)2n
<script type="math/tex; mode=display" id="MathJax-Element-5">MSE(y,y') = \frac{\sum _{i=1}^{n} (y_i - y_{i}^{'})^2}{n}</script>

其中yiyi<script type="math/tex" id="MathJax-Element-6">y_i</script>为一个batch中第i个数据的正确答案,而yiyi′<script type="math/tex" id="MathJax-Element-7">y_{i}{'}</script>为神经网络给出的预测值。
如下代码展示了如何通过TensorFlow实现均方误差损失函数:

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

其中y代表了神经网络的输出答案,y_代表了标准答案。

2、自定义损失函数

例:如果一个商品的成本是1元,但是利润是10元,那么少预测一个就少赚10元,而多预测一个少赚1元。
为了最大化预期利润,需要将损失函数和利润直接联系起来。下面公式给出了一个当预测多于真实值和预测少于真实值时有不同损失系数的损失函数:

Loss(y,y)=i=1nf(yi,yi),f(x,y)={a(xy)x>yb(yx)xyLoss(y,y′)=∑i=1nf(yi,yi′),f(x,y)={a(x−y)x>yb(y−x)x≤y
<script type="math/tex; mode=display" id="MathJax-Element-8">Loss(y,y^{'}) =\sum _{i=1}^{n} f(y_i , y_{i}^{'}) ,\quad f(x,y)=\begin{cases}a(x-y) \quad x>y\\b(y-x)\quad x\leq y\end{cases}</script>

yiyi<script type="math/tex" id="MathJax-Element-9">y_i</script>为一个batch中第i个数据的正确答案,yiyi′<script type="math/tex" id="MathJax-Element-10">y_{i}^{'}</script>为神经网络得到的预测值,

a(xy)x>ya(x−y)x>y<script type="math/tex" id="MathJax-Element-11">a(x-y) \quad x>y</script>表示正确答案多于预测答案的情况
b(yx)xyb(y−x)x≤y<script type="math/tex" id="MathJax-Element-12">b(y-x)\quad x\leq y</script>表示正确答案少于预测答案的情况

TensorFlow实现这个损失函数:

a= 10
b= 1
loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * a, (y_ - y) * b))

(tf.select已被舍弃,使用tf.where替代)

再看一个tf.where和tf.greater使用的例子

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])

sess = tf.InteractiveSession()
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

1.定义神经网络的相关参数和变量。

batch_size = 8
#两个输入节点
x = tf.placeholder(tf.float32, shape=(None, 2), name="x-input")
#回归问题一般只有一个输出节点
y_ = tf.placeholder(tf.float32, shape=(None, 1), name='y-input')
#定义了一个单层的神经网络前向传播过程,这里就是简单加权和
w1= tf.Variable(tf.random_normal([2, 1], stddev=1, seed=1))
y = tf.matmul(x, w1)

2.设置自定义的损失函数。

#定义损失函数使得预测少了的损失大,于是模型应该偏向多的方向预测。
loss_less = 10
loss_more = 1
loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * loss_more, (y_ - y) * loss_less))
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)

3.生成模拟数据集。

#通过随机数生成一个模拟数据集
rdm = RandomState(1)
X = rdm.rand(128,2)
#设置回归的正确值为两个输入的和加上一个随机量。之所以要加上一个随机量是为了加入不可预测的噪音,否则不同#损失函数的意义就不大了,因为不同损失函数都会在能完全预测正确的时候最低。一般来说噪音为一个均值为0的小#量,所以这里的噪音设置为-0.05 ~ 0.05的随机数
Y = [[x1+x2+(rdm.rand()/10.0-0.05)] for (x1, x2) in X]

4.训练模型。

with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
    STEPS = 5000
    for i in range(STEPS):
        start = (i*batch_size) % 128
        end = (i*batch_size) % 128 + batch_size
        sess.run(train_step, feed_dict={x: X[start:end], y_: Y[start:end]})
        if i % 1000 == 0:
            print("After %d training step(s), w1 is: " % (i))
            print(sess.run(w1), "\n")
    print("Final w1 is: \n", sess.run(w1))

输出:

Final w1 is: 
 [[ 1.01934695]
 [ 1.04280889]]

5.重新定义损失函数,使得预测多了的损失大,于是模型应该偏向少的方向预测。

loss_less = 1
loss_more = 10
loss = tf.reduce_sum(tf.where(tf.greater(y, y_), (y - y_) * loss_more, (y_ - y) * loss_less))
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)

with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
    STEPS = 5000
    for i in range(STEPS):
        start = (i*batch_size) % 128
        end = (i*batch_size) % 128 + batch_size
        sess.run(train_step, feed_dict={x: X[start:end], y_: Y[start:end]})
        if i % 1000 == 0:
            print("After %d training step(s), w1 is: " % (i))
            print(sess.run(w1), "\n")
    print("Final w1 is: \n", sess.run(w1))

输出:

Final w1 is: 
 [[ 0.95525807]
 [ 0.9813394 ]]

6.定义损失函数为MSE。

loss = tf.losses.mean_squared_error(y, y_)
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)

with tf.Session() as sess:
    init_op = tf.global_variables_initializer()
    sess.run(init_op)
    STEPS = 5000
    for i in range(STEPS):
        start = (i*batch_size) % 128
        end = (i*batch_size) % 128 + batch_size
        sess.run(train_step, feed_dict={x: X[start:end], y_: Y[start:end]})
        if i % 1000 == 0:
            print("After %d training step(s), w1 is: " % (i))
            print( sess.run(w1), "\n")
    print ("Final w1 is: \n", sess.run(w1))

输出:

Final w1 is: 
 [[ 0.97437561]
 [ 1.0243336 ]]

从上面三种不同的运行结果可看出,对于相同的神经网络,不同的损失函数会对训练得到的模型产生重要影响。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐