前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >tensorflow中损失函数的用法

tensorflow中损失函数的用法

作者头像
狼啸风云
修改2022-09-04 22:07:02
3.7K1
修改2022-09-04 22:07:02
举报
文章被收录于专栏:计算机视觉理论及其实现

1、经典损失函数:

分类问题和回归问题是监督学习的两大种类。这一节将分别介绍分类问题和回归问题中使用到的经典损失函数。分类问题希望解决的是将不同的样本分到事先定义到的经典损失函数。分类问题希望解决的将不同的样本分到事先定义好的类别中。

通过神经网络解决多分类问题最常用的方法是设置n个输出节点,其中n为类别的个数。对于每一个样例,神经网络可以得到的一个n为数组作为输出结果。数组中的每一个维度(也就是每一个输出节点)对饮一个类别。在理想情况下,如果一个样本属于类别k,那么这个类别所对应的输出节点的输出值应该为1,而其他节点的输出都为0。交叉熵是常用的判别方法之一。交叉熵刻画了两个概率分布之间的距离,它是分类问题中试用版比较广的一种损失函数。

交叉熵是一个信息论中的概念,它原本是用来估计平均编码长度的。给定两个给吕分布p和q,通过q来表示p的交叉熵为:

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

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

\forall p(X=x) \in[0,1], \sum_{x} p(X=x)=1

 也就是说,任意事件发生的概率在0到1之间,且总有某一事件发生(概率和为1)。如果将分类问题中“一个样例属于某一类别”看成一个概率事件,那么训练数据的正确答案就符合一个额概率分布。因为事件“一个样例不属于正确的类别”的概率为0,而“一个样例属于正确的类别”的概率为1.如何将神经网络的前向传播得到的结果也变成概率分布呢?Softmax回顾就是一个非常有用的方法。

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

假设原始的神经网络输出为 y_1y_2y_3,...,y_n,那么经过Softmax回归处理之后的输出为: 

\operatorname{softmax}(y)_{i}=y_{i}^{\prime}=\frac{e^{y_{i}}}{\sum_{j=1}^{n} e^{y_{i}}}

从以上公式中可以看出,原始神经网络的输出使用北至新都来生成新的输出,而新的输出满足概率分布的所有要求。这个新的输出可以理解为经过神经网络的推导,一个样例为不同类别的概率分别是多大。这样就把神经网络的输出也变成了一个概率分布,从而可以通过交叉熵来计算预测的概率分布和真实答案之间的距离了。

从交叉熵的公式可以看到交叉熵函数时不对称的(H(p,q)不等于H(q,p)),它刻画的是通过来绿分布q来表达概率分布p的困难程度。因为正确答案是希望得到的结果,所以当交叉熵作为神经网络的损失函数时,p代表的是正确答案,q代表的是预测值。交叉熵刻画的是两个概率分布的距离,也就是说交叉熵值越小,两个概率分布越接近。下面将给出两个具体样例来直观地说明通过交叉熵可以判断与对策答案和真实答案之间的距离。假设有一个三分类问题,某个样例正确的答案是(1,0,0)。某模型经过sigmoid回归之后的预测值答案是(0.5, 0.4, 0.1),那么这个预测和真实答案之间的交叉熵为:

H((1,0,0),(0.5,0.4,0.1))=-(1 \times \log 0.5+0 \times \log 0.4+0 \times \log 0.1) \approx 0.3
H((1,0,0),(0.8,0.1,0.1))=-(1 \times \log 0.8+0 \times \log 0.1+0 \times \log 0.1) \approx 0.1

从直观上可以很容易地知道第二个预测答案要优于第一个。通过交叉熵计算得到的结果也是一致的(第二个交叉熵的值更小) 。tensorflow实现交叉熵,其代码实现如下:

代码语言:javascript
复制
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的简单样例。

代码语言:javascript
复制
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函数,这个函数完成了对张量所有元素依次求对数的功能。以下代码中给出一个简单的样例。

代码语言:javascript
复制
v = tf.constant([1.0, 2.0, 3.0])
print tf.log.eval( )
# 输出[0. 0.69314718 1.09861231]

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

代码语言:javascript
复制
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.]]

v1\times v2的结果是每个位置上对应元素的乘积。比如(1,1)这个元素的值是:

                                       v1[1,1]\times v2[1,1] = 1\times 5 =5

(1,2)这个元素的值是:

v1[1,2]\times v2[1,2] = 2\times 6 =12

以此类推。而tf.matmul函数完成的是矩阵乘法运算,所以(1,1)这个元素的值是:

                                    v1[1,1]\times v2[1,1]+v1[1,2]\times v2[2,1] = 1\times 5+2\times 7=19

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

代码语言:javascript
复制
v = tf.constant([1.0, 2.0, 3.0], [4.0, 5.0, 6.0])
print tf.reduce_mean(v).eval( )

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

代码语言:javascript
复制
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(label = y_, logits = y)

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

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

\operatorname{MSE}\left(y, y^{2}\right)=\frac{\sum_{i=1}^{n}\left(y_{i}-y_{i}^{\prime}\right)^{2}}{n}

 其中yi为一个batch中第i个数据的正确答案,而yi'为神经网络给出的预测值。以下代码展示了如何通过tensorflow实现均方误差函数。

代码语言:javascript
复制
mse = tf.reduce_mean(tf.square(y_ - y))

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

2、自定义损失函数:

tensorflow不仅支持经典的损失函数。还可以优化任意的自定义损失函数。下面介绍如何通过自定义损失函数的方法,使得神经网络优化的结果更加接近实际问题的需求。

在预测商品销量时,如果预测多了(预测值比真实值大),商家损失的是生产商品的成本;而如果预测少了(预测值比真实销量少),损失的则是商品的利润。因为一般商品的成本和商品的利润不会严格相等,所以使用前文介绍的均方误差损失函数就不能够很好的最大化销售利润。比如如果一个商品的成本价是1元,但利润是10元,那么少预测一个就少挣10元;而多预测一个才少挣1元。=如果神经网络模型最小化的是均方误差,那么很有可能此模型就无法最大化预期的利润。为了最大化预期利润,需要将损失函数和利润直接联系起来。注意损失函数定义的是损失,所以要将利润最大化,定义的损失函数应该和客户啊成本或者代价。以下公式给出了一个当预测多于真实值和预测少于真实值时由不同损失系数的损失函数:

\operatorname{Loss}\left(y, y^{\prime}\right)=\sum_{i=1}^{n} f\left(y_{i}, y_{i}^{\prime}\right), f(x, y)=a(x-y), x>y, b(y-x) x<y
代码语言:javascript
复制
loss = tf.reduce_sum(tf.where(tf.greater(v1, v2),(v1- v2)*a,(v2 -v1)*b))

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

代码语言:javascript
复制
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( )

# 输出[False False True True]

print tf.where(tf.greater(v1, v2), v1, v2).eval( )
# 输出[4. 3. 3. 4. ]
sess.close( )

在定义了损失函数之后,下面通过一个简单的神经网络程序来讲解损失函数对模型训练结果的影响。在下面程序中实现一个拥有两个输入节点、一个输出节点,没有隐藏层的神经网络。

代码语言:javascript
复制
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], stdddev = 1, seed = 1 ))
y = tf.matmul(x, w1)

# 定义预测多了和预测少了的成本
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)

# 通过随机数生成一个模拟数据集
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.global_variables_initializer( )
   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.0193495, 1.04280889],也就是说得到的预测函数时1.02x1+1.04x2,这要比x1+x2大,因为在损失函数中指定预测少了的损失更大(loss_less>loss_more)。如果将loss_less的值调整为1,loss_more的值调整为10,那么w1的值将会是[0.95525807,][0.95525807, 0.9813394]。也就是说,在这样的设置下,模型会更加偏向于预测少一点。而如果使用军方误差作为损失函数,那么w1将会是[0.97437561, 1.0243336]。使用这个损失函数会尽量让预测值离标准打哪更近。通过这个样例可以感受到,对于相同的神经网络,不同的损失函数会对训练得到的模型产生重要影响。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年01月07日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、经典损失函数:
  • 2、自定义损失函数:
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档