当面试一些刚进入机器学习领域的学生时,我都会问这两个问题:
(1) 逻辑回归的损失函数是?
(2) 逻辑回归为何不用平方差损失函数?
对于第一个问题,大多数人都知道是交叉熵损失函数;对于第二个问题,却有不少人回答不出来或者回答不是那么准确。有的回答说:因为逻辑回归的预测值在[0,1]范围内,用交叉熵损失函数能方便转成概率问题进行计算,这种回答显然没有回答出我想要的理想答案。
其实当初我也被这个问题困惑过:为何线性回归用的是平方差损失函数,到了逻辑回归就改成交叉熵损失函数。这次就说说我对于这个问题的理解。
为何损失函数
损失函数是用来评价模型优劣的一个函数,也称为目标函数、代价函数。那模型的优劣如何来评价呢,一个最直观的做法就是看预测值与真实值的差距,若差距为0,则说明预测正确;若差值越大,则说明模型表现的越糟糕。这也就引出了平方差损失函数的形式,如下。
其中数据集的样本数量。
为何交叉熵损失函数
交叉熵是衡量两个概率分布的差异,也称对数损失函数。交叉熵越大,两个分布之间差异越大,说明预测的结果越糟糕;反之,交叉熵越小,两个分布很接近,说明预测的结果越完美。
在逻辑回归上,交叉熵损失函数表现形式如下:
为何逻辑回归选择交叉熵损失函数
其实这个主要问题在于逻辑回归是在线性回归的基础上加了非线性激活函数sigmoid,在反向传播用梯度下降的方式求解的时候,若采用平方差损失函数,导致参数的导数带了sigmoid激活函数,而sigmoid在值很大或很小的两端会出现梯度消失的情况;而若采用交叉熵损失函数,参数的导数跟sigmoid激活函数无关,这样在参数更新的时候更容易求到最优解。下面展示下公式的推导过程。
假设,其中, 为sigmoid激活函数,在考虑单条样本下:
若采用平方差损失函数,在使用梯度下降方法进行和更新时,需要损失函数对它们进行求导:
然后在的学习率下更新和时:
因为sigmoid函数的特性(如下图),当过大或过小的时候,函数呈平坦趋势,导致很小,甚至为0,也就出现梯度消失的情况,也造成和更新非常慢,使得平方损失函数难以求的最优解的概率加大。
若采用交叉熵损失函数,情况就有好很多。对应参数的导数如下(若对详细推导过程感兴趣,建议在纸上自己推导下):
在的学习率下更新和时:
可以看出来,参数更新中没有,只受, 而这相当于预测误差,当误差大的时候,权重更新快;当误差小的时候,权重更新慢。这一特性更适合损失函数能更快求到最优解。
以上也就能足够说明逻辑回归选择交叉熵而不选择平方差作为损失函数的原因了。
在实际情况下两个损失函数有多大差别
为了验证两个损失函数的差异,我选择MNIST数据集进行实验:只识别数字3,这样将数据集的label转变为[0,1],0代表是数字3,1为其他数字。模型最后一层选择sigmod作为激活函数进行回归预测,然后选择这两个不同的损失函数,看看训练情况如何。下面为对应的代码。
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np
# load dataset
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train = x_train.reshape(60000, 784).astype('float32') /255
x_test = x_test.reshape(10000, 784).astype('float32') /255
y_train=np.array([0 if d==2 else 1 for d in y_train])
y_test=np.array([0 if d==2 else 1 for d in y_test])
#build model
inputs = keras.Input(shape=(784,), name='mnist_input')
h1 = layers.Dense(64, activation='relu')(inputs)
outputs = layers.Dense(1, activation='sigmoid')(h1)
model = tf.keras.Model(inputs, outputs)
#以平方差损失函数来编译模型进行训练
model.compile(optimizer=keras.optimizers.RMSprop(),
loss=tf.keras.losses.MeanSquaredError(),
metrics=['accuracy'])
#以交叉熵函数来编译模型进行训练
model.compile(optimizer=keras.optimizers.RMSprop(),
loss=keras.losses.BinaryCrossentropy(),
metrics=['accuracy'])
#training
history = model.fit(x_train, y_train, batch_size=64, epochs=5,
validation_data=(x_test, y_test))
训练的结果对应如下:
从结果可以看出,不论在训练集上,还是验证集上,交叉熵损失函数都是明显优于平方差损失函数。此外,也能看到在平方差损失函数下,验证集上的准确率没发生变化,说明模型的参数没更新过,跟上述公式推导结论也是吻合的。所以,在训练模型时候,选择一个合适的损失函数也是至关重要的。
更多文章可关注笔者公众号:自然语言处理算法与实践