【TensorFlow实战】5.损失函数(1)

分类问题和回归问题是监督学习的两大类。分类问题希望解决的是将不同的样本分到事先定义好的类别中,比如判断一个零件是否合格就是一个二分类问题。在这个问题中需要将样本(也就是零件)分到合格或者是不合格两个类别中。手写数字体识别问题可以看作是十分类问题,也就是将一张包含了手写数字的图片分类到0~9这十个数字中。

在解决零件是否合格的二分类问题中,定义一个有单个输出节点的神经网络。当这个节点的输出越接近0时,这个样本越有可能是不合格的;反之如果输出越接近于1,则样本越有可能是合格的。为了给出具体的分类结果,可以取0.5作为阈值,凡是输出大于0.5的都认为是合格的,小于0.5则是不合格的。然而这样的做法并不容易直接推广到多分类问题。虽然设置多个阈值在理论上时可行的,但在解决实际问题中一般不这么处理。

通过神经网络解决多分类问题最常用的方法是设置n个输出节点,其中n为类别的个数。对于每一个样例,神经网络可以得到一个n维数组作为输出结果。数组中的每一个维度(也就是每一个输出节点)对应一个类别。在理想情况下,如果一个样本属于类别k,那么这个类别所对应的输出节点的输出值应该为1,而其他节点的输出为0。以识别数字1为例,神经网络模型的输出结果越接近于[0,1,0,0,0,0,0,0,0,0]越好。那么如何判断一个输出向量和期望的向量有多接近呢?交叉熵(cross entropy)是常用的判断方法之一。交叉熵刻画了两个概率分布之间的距离,他是分类问题中使用比较广的一种损失函数。

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

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

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

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

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

                                           

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

从交叉熵的公式可以看到交叉熵函数不是对称的(H(p,q)≠H(q,p)),它刻画的是通过概率分布q来表达概率分布p的困难程度。因为正确答案是希望得到的结果,所以当交叉熵作为神经网络的损失函数时,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.3

如果另一个模型的预测是(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.1

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

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

其中y_代表了正确结果,y代表了预测结果。这一行代码包含了四个不同的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())
#输出的是 1 2 3的对数

第三个运算是乘法,在实现交叉熵的代码中直接将两个矩阵通过“*”操作相乘。这个操作不是矩阵乘法,而是元素之间直接相乘。矩阵乘法需要使用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.]]

通过上面这三个运算完成了对于每一个样例中的每一个类别交叉熵p(x)logq(x)的计算。这三个计算得到的结果是一个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())

因为交叉熵一般会与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)。它的定义如下:

                          

其中yiWie一个batch中第i个数据的正确答案,而yi`为神经网络给出的预测值。下面展示了如何通过TensorFlow实现均方误差损失函数:

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

其中y代表了神经网络的输出答案,y_代表了标准答案。这里的减法运算“-”也是对两个矩阵中对应元素的减法。

猜你喜欢

转载自blog.csdn.net/poulang5786/article/details/81090033