深层神经网络之二:损失函数定义

1.经典损失函数

监督学习所要解决的两大类问题为分类问题和回归问题。针对分类问题和回归问题有各自不同的经典损失函数。
通过神经网络解决多分类问题最常用的方法是设置n个输出节点,其中n为类别的个数。对于每一个样例,神经网络可以得到一个n维数组作为输出结果。数组中每个维度(也就是每一个输出节点)对应一个类别。在理想情况下,如果一个样本属于类别k,那么这个类别所对应的输出节点的输出值应该为1,而其他节点的输出都为0。那么问题就转化为如何判断输出向量和期望的向量之间有多接近的问题。交叉熵(cross entropy)是常用的评判方法之一。交叉熵刻画了两个概率分布之间的距离,它是分类问题中使用比较广的一种损失函数。
给定两个概率p和q,通过q来表示p的交叉熵为:
在这里插入图片描述
从交叉熵公式可以看出,交叉熵函数不是对称的(即H(p,q)≠H(q,p)),它刻画的是通过概率分布q来表达概率分布p的困难程度。因为正确答案是希望得到的结果,所以当交叉熵作为神经网络的损失函数时,p代表的是正确答案,q代表的是预测值。交叉熵刻画的是两个概率分布的距离,也就是说交叉熵值越小,两个概率分布越接近。
交叉熵刻画的是两个概率分布之间的距离,但是神经网络的输出却不一定是一个概率分布。这便将印出了如何将神经网络前向传播得到的结果也编程概率分布的问题,而Softmax回归便是解决这一问题常用的方法。
Softmax回归本身可以作为一个学习算法来优化分类结果,但是在TensorFlow中,Softmax回归的参数被去掉了,它只是一个额外的处理层,将神经网络的输出变成一个概率分布。下图展示了Softmax回归的神经网络结构图。
在这里插入图片描述
假设原始的神经网络输出为y1,y2,…yn,那么经过Softmax回归处理之后的输出为:
在这里插入图片描述
通过以上公式,原始神经网络的输出被用作置信度来生成新的输出,而新的输出满足概率分布的所有要求。这个新的输出可以理解为经过神经网络的推导,一个样例为不同类别的概率分别是多大。这样就把神经网络的输出也变成了一个概率分布,从而可以通过交叉熵来计算预测的概率分布和真实答案的概率分布之间的距离了。
通过TensorFlow实现交叉熵的代码如下:

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

其中y_代表正确结果,y代表预测结果。这行代码包含了4个不同的TensorFlow运算。通过tf.clip_by_value函数可以将一个张量中的数限制在一个范围之内,这样可以避免一些运算错误(比如log0是无效的)。下面给出了使用tf.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.clip_by_value函数可以保证在进行log运算时,不会出现log0这样的错误或者大于1的概率。第二个运算时tf.log函数,这个函数完成了对张量中所有元素依次求对数的功能。以下代码中给出一个简单样例。

v=tf.constant([1.0,2.0,3.0])
print(tf.log(v).eval())
#输出[0. 0.69314718 1.09861231]

第三个运算是乘法,在实现交叉熵的代码中直接将两个矩阵通过“*”操作相乘。这个操作不是矩阵乘法,而是元素之间直接相乘。矩阵乘法需要使用tf.matmul函数来完成。下面给出了这两个操作的区别。

v1=tf.constant([[1.0,2.0],[3.0,4.0]])
v2=tf.constant([[5.0,6.0],[7.0,8.0]])
print((v1*v2).eval())
#输出[[5. 12.][21. 32.]]
print(tf.matmul(v1,v2).eval())
#输出[[19. 22.][43. 50.]]

通过上面三个运算,完成了对于每一个样例中每一个类别交叉熵的计算。这三步计算得到的结果是一个n×m的二维矩阵,其中n 为一个batch中样例的数量,m为分类的类别数量。根据交叉熵的公式,应该将每行中m个结果相加得到所有样例的交叉熵,然后再对这n行取平均得到一个batch的平均交叉熵。但因为分类问题的类别数量是不变的,所以可以直接对整个矩阵做平均而并不改变计算结果的意义。这样的方式可以使整个程序更加简洁。以下代码简单展示了tf.reduce_mean函数的使用方法。

v=tf.constant([[1.0,2.0,3.0],[4.0,5.0,6.0]])
print(tf.reduce_mean(v).eval())
#输出3.5

因为交叉熵一般会与softmax回归一起使用,所以TensorFlow对这两个功能进行了统一封装,并提供了tf.nn.softmax_cross_entropy_with_logits函数。比如可以直接通过下面的代码来实现使用了softmax回归之后的交叉熵损失函数。

cross_entropy=tf.nn.softmax_cross_entropy_with_logits(y,y_)

其中y代表了原始神经网络的输出结果,而y_给出了标准答案。这样通过一个命令就可以得到使用了Softmax回归之后的交叉熵。在只有一个正确答案的分类问题中,TensorFlow提供了tf.nn.sparse_softmax_cross_entropy_with_logits函数来进一步加速计算过程。

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

在这里插入图片描述
其中yi为一个batch中第i个数据的正确答案,而yi’为神经网络给出的预测值。以下代码展示了如何通过TensorFlow实现均方误差损失函数。

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

其中y代表了神经网络的输出答案,y_代表了标准答案。类似前文介绍的乘法操作,这里的加减法运算“-”也是两个矩阵中对应元素的减法。

2.自定义损失函数

TensorFlow不仅支持经典的损失函数,还可以优化任意的自定义损失函数以使神经网络优化的结果更加接近实际问题的需求。
以预测商品销量为例,如果预测值比真实销量大,商家损失的是生产商品的成本,如果预测值比真实销售量小,损失的则是商品的利润。因为一般商品的成本和商品的利润不会严格相等,所以如果用上文所述的军方误差函数就不能很好地最大化销售利润。比如如果一个商品成本是1元,但利润是10元,那么少预测一个就少赚10元,而多预测一个才少赚1元。如果深究网络模型最小化的是均方误差,那么很有可能此模型就无法最大化预期的利润。为了最大化预期利润,需要将损失函数和利润直接联系起来。注意损失函数定义的是损失,所以要将利润最大化,定义的损失函数应该刻画成本或者代价。下面的公式给出了一个当预测多于真实值和预测少于真实值时有不容损失系数的损失函数:
在这里插入图片描述
和均方误差公式类似,yi为一个batch中第i个数据的正确答案,yi’为神经网络得到的预测值,a和b是常量。比如在上面介绍的销量预测问题中,a就等于10(正确答案多于预测答案的代价),而b等于1(正确答案少于预测答案的代价)。通过对这个自定义损失函数的优化,模型提供的预测值更有可能最大化收益。在TensorFlow中,可以通过以下代码来实现这个损失函数。

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

上面代码用到了tf.greater和tf.select来实现选择操作。tf.greater的输入是两个张量,此函数会比较这两个输入张量中每一个元素的大小,并返回比较结果。当tf.greater的输入张量维度不一样时,TensorFlow会进行类似NumPy广播(broadcasting)操作的处理。tf.select函数有三个参数。第一个为选择条件根据,当选择条件为True时,tf.select函数会选择第二个参数中的值,否则使用第三个参数中的值。注意tf.select函数判断和选择都是在元素级别上进行,以下代码展示了tf.select函数和tf.greater函数的用法。

#coding=utf-8

import tensorflow as tf 
from numpy.random import RandomState

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)

#定义预测多了和预测少了的成本
loss_less=10
loss_more=1
#由于版本的问题,一些SPI的名称做了改变:将tf.select()改为tf.where()
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)

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

#训练神经网络
with tf.Session() as sess:
	init_op=tf.initialize_all_variables()
	sess.run(init_op)
	STEPS=5000
	for i in range(STEPS):
		start=(i*batch_size)%dataset_size
		end=min(start+batch_size,dataset_size)
		sess.run(train_step,feed_dict={x:X[start:end],y_:Y[start:end]})
		print(sess.run(w1))

运行上面代码得到w1的值为[1.01934695 ,1.04280889],也就是说得到的预测函数是x1+x2,这比1.02x1+1.04x2大,因为在损失函数中指定预测少了的损失更大(loss_less>loss_more)。如果将loss_less的值调整为1,loss_more的值调整为10,那么w1的值将会是[ 0.95525807, 0.9813394 ]。也就是说,在这样的设置下,模型会更加偏向于预测少一点。而如果使用均方误差作为损失函数,那么w1会是[0.97437561,1.0243336]。使用这个损失函数会尽量让预测值离标准答案更近。通过这个例子可以看到,不同的损失函数会对训练得到的模型产生重要影响。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值