有两种方式可以推导出二分类交叉熵损失函数,一个是通过极大似然估计法,另一个则是信息熵。
参考之前如何推导多元线性回归的损失函数,我们可以总结一下这个思想:那就是,一个预测模型的背后,一定都会假设预测结果服从某种分布,然后通过对该分布构建似然函数,紧接着使用极大似然估计,最后求解出线性模型中的参数。可不是嘛,多元线性回归假设数据的误差服从高斯分布,而逻辑回归假设预测的结果服从伯努利分布。
损失函数要做的,就是优化模型学习出内部的参数,使得模型在这个参数给出的预测值的前提下,预测结果和标签相同的概率最大。这个条件概率可以表示如下:
P ( x 1 , x 2 , x 3 , x 4 , … , x n ∣ f ( W , X ) ) P\left(x_{1}, x_{2}, x_{3}, x_{4}, \ldots, x_{n} \mid f(W,X)\right) P(x1,x2,x3,x4,…,xn∣f(W,X))
这里的W就是指模型的参数,f(W,X)就是表示模型的预测结果y_hat,即参数W和输入X的函数,x1~xn就是数据的真实标签。
对于多元线性回归而言,这里的f(W)就是θᵀx,并且我们假设这整个概率P(·)是服从高斯分布的,那么这个高斯分布哪来的呢,其实就源自于误差epsilon。所以损失函数要做的,就是使得这个条件概率最大化(预测结果最准确),说到这,这个概率P的含义就很清楚了,它实则是一个似然函数。这样一来,不就可以通过极大似然估计去求解了吗。我们也的确是这么做的,通过MLE,就推得了多元线性回归的损失函数MSE。
同理逻辑回归,我们假设预测的结果服从伯努利分布(0,1分布)
伯努利分布的概率计算: P(xi=1)=yi,P(xi=0)=1−yi,0<p<1
P ( x i = 1 ) = y i , P ( x i = 0 ) = 1 − y i , 0 < p < 1 \operatorname{P}(x_i=1)=y_i, \operatorname{P}(x_i=0)=1-y_i, 0
其中,xi表示每条样本的真值(标签0,1),yi表示模型对该样本的预测结果。
将上式整合成一个式子表达就是:
P ( x i ) = y i x i ( 1 − y i ) 1 − x i , x i ∈ { 0 , 1 } ; P(x_i)=y_{i}^{x_{i}}\left(1-y_{i}\right)^{1-x_{i}},\boldsymbol{x}_{i} \in\{\mathbf{0}, \mathbf{1}\}; P(xi)=yixi(1−yi)1−xi,xi∈{ 0,1};
此时似然函数就是:
∏ i = 1 n y i x i ( 1 − y i ) 1 − x i \prod_{i=1}^{n} y_{i}^{x_{i}}\left(1-y_{i}\right)^{1-x_{i}} i=1∏nyixi(1−yi)1−xi
转化为对数似然函数就是:
log ( ∏ i = 1 n y i x i ( 1 − y i ) 1 − x i ) = ∑ i = 1 n log ( y i x i ( 1 − y i ) 1 − x i ) = ∑ i = 1 n ( x i ⋅ log y i + ( 1 − x i ) ⋅ log ( 1 − y i ) ) \begin{aligned} &\log \left(\prod_{i=1}^{n} y_{i}^{x_{i}}\left(1-y_{i}\right)^{1-x_{i}}\right) \\ &=\sum_{i=1}^{n} \log \left(y_{i}^{x_{i}}\left(1-y_{i}\right)^{1-x_{i}}\right) \\ &=\sum_{i=1}^{n}\left(x_{i} \cdot \log y_{i}+\left(1-x_{i}\right) \cdot \log \left(1-y_{i}\right)\right) \end{aligned} log(i=1∏nyixi(1−yi)1−xi)=i=1∑nlog(yixi(1−yi)1−xi)=i=1∑n(xi⋅logyi+(1−xi)⋅log(1−yi))
所以最后我们要做的就是极大化这个对数似然函数:
arg max θ = ∑ i = 1 n ( x i ⋅ log y i + ( 1 − x i ) ⋅ log ( 1 − y i ) ) \underset{\theta}{\arg \max }\quad=\sum_{i=1}^{n}\left(x_{i} \cdot \log y_{i}+\left(1-x_{i}\right) \cdot \log \left(1-y_{i}\right)\right) θargmax=i=1∑n(xi⋅logyi+(1−xi)⋅log(1−yi))
但是损失函数一般都是来最小化的,小问题,对似然函数加个负号即可:
arg min θ = − ∑ i = 1 n ( x i ⋅ log y i + ( 1 − x i ) ⋅ log ( 1 − y i ) ) \underset{\theta}{\arg \min }\quad=-\sum_{i=1}^{n}\left(x_{i} \cdot \log y_{i}+\left(1-x_{i}\right) \cdot \log \left(1-y_{i}\right)\right) θargmin=−i=1∑n(xi⋅logyi+(1−xi)⋅log(1−yi))
所以,逻辑回归的损失函数就是:
l o s s = − ∑ i = 1 n ( x i ⋅ log y i + ( 1 − x i ) ⋅ log ( 1 − y i ) ) loss=-\sum_{i=1}^{n}\left(x_{i} \cdot \log y_{i}+\left(1-x_{i}\right) \cdot \log \left(1-y_{i}\right)\right) loss=−i=1∑n(xi⋅logyi+(1−xi)⋅log(1−yi))
这样,我们就用极大似然估计,推导出了逻辑回归的损失函数,但到这里其实还没有结束,因为,这个损失函数还有一个名称,那就是交叉熵。所以接下来,我将顺便以熵的角度,帮助大家理解一波这个损失函数的由来。
既然是交叉熵,那么公式的定义当中必然含有熵的概念,事实上,交叉熵里的熵,指的就是信息熵,因此我们必须先搞懂,信息熵是如何定义的:
我们知道,熵在热力学当中,表示一个体系的混乱程度。克劳德·香农将熵的概念引入了信息论中,作为信息熵的定义者,他给出了信息熵的三个性质:
可见,信息熵与事件发生的概率大小有关。因此我们可以先将信息熵定义为事件概率的某个函数f:
H ( x ) : = f ( p ( x ) ) H(x) := f(p(x)) H(x):=f(p(x))
再根据信息熵的累加性定义,两个事件发生的信息熵是这两个事件各自发生的信息熵之和。我们假设事件是独立的,则有
f ( p ( x ) p ( y ) ) = f ( p ( x ) ) + f ( p ( y ) ) f(p(x)p(y)) = f(p(x))+f(p(y)) f(p(x)p(y))=f(p(x))+f(p(y))
那么,有没有一种函数,能将连乘的形式转换为连加呢,有,就是对数函数,所以信息熵就可以表示成:
H ( x ) : = C l o g a ( p ( x ) ) H(x) := Clog_a(p(x)) H(x):=Cloga(p(x))
现在我们还不知道这个对数的系数和底数到底是多少,所以先暂且用C和a表示了
然后再根据定义中的非负性,即信息熵一定是非负的,但是我们定义式中的概率实则是在(0,1)之间的一个值,而对数在这个范围内是一个负值,因此我们将系数定义为-1:
H ( x ) : = − l o g a ( p ( x ) ) H(x) := -log_a(p(x)) H(x):=−loga(p(x))
(最后公式里的底数可以是e或者2.其实底数只会影响信息熵的单位,即导致量纲的不同,并不会影响计算信息熵的本质思想,因此这里就不再详细说明了)
这时候,我们便推得了体系中只有一个事件下的信息熵:
如果体系中包含多个事件,每个事件发生的概率不同,那么信息熵可以定义为系统中所有事件信息熵的期望值,即:
H ( X ) : = − ∑ x ∈ χ p ( x ) log a p ( x ) H(X):=-\sum_{x \in \chi} p(x) \log_a p(x) H(X):=−x∈χ∑p(x)logap(x)
H ( X ) : = E ( H ( x i ) ) H(X):=E(H(x_i)) H(X):=E(H(xi))
到此,信息熵的定义我们就已经知道了,可以看到,系统中一个事件的信息发生的概率越低,它的信息熵也就越高,或者说,一个事件的信息熵越高,该事件的不确定度就越高。这就表明,低概率事件将蕴含着巨大的信息量。举个例子吧,我们说太阳是从东边升起的,这是一个不争的事实,你今天告诉我了,也不会给我的人生带来多大变化。所以说这个事件的信息熵很低。但是如果有一天,你告诉我,太阳从西边升起了,那这其中包含的信息可就复杂了,可能是太阳系发生了巨变,或者是地球的自转转向了等等,因此这个事件的信息熵很高。总之就是这个意思。
我们这时候不妨思考一下,分类算法(逻辑回归属于分类算法的一种,在深度学习的分类网络也需要使用交叉熵作为损失函数)和信息熵有什么联系吗,为什么分类算法的损失函数能和信息论扯上关系呢?
你要这么问,那还真就有点关系。我们知道(上文有提过),分类算法其实是在优化一个条件概率,这个条件概率中包含一组随机变量(数据集),数据集包含两个分布,一个是真实分布,我们记为p(x),一个是非真实分布,我们记为q(x),真实分布好理解,就是自然界本来的样子,在机器学习中就是输入x和真实标签y的分布,而非真实分布,也好理解,就是输入x和模型预测y_hat的分布。
为什么说是两个分布呢,因为预测和真实情况总会有偏差嘛,因此它们之间的分布参数并不是完全相同的,那么损失函数这时就要干一件事,那就是优化非真实分布q(y_hat),使其尽可能接近真实分布p(y)。这时候,我们就可以使用相对熵这种方法来实现这一度量。
相对熵,顾名思义,即一定是一个相对的概念。在信息论中,相对熵等价于两个概率分布的信息熵的差值。在机器学习中,相对熵表示使用理论分布拟合真实分布时产生的信息损耗,因此当相对熵越小时,预测值越能够拟合真实样本。
假设我们以真实分布为度量基准,相对熵的定义如下:
D K L ( P ∥ Q ) : = ∑ i = 1 m p i ⋅ ( H Q ( q i ) − H P ( p i ) ) \begin{aligned}&\boldsymbol{D}_{K L}(\boldsymbol{P} \| \boldsymbol{Q}) :=\sum_{i=1}^{m} p_{i} \cdot\left(H_{Q}\left(q_{i}\right)-H_{P}\left(p_{i}\right)\right) \\ \end{aligned} DKL(P∥Q):=i=1∑mpi⋅(HQ(qi)−HP(pi))
可以看到,相对熵和信息熵的差别就在于,信息熵中的每一项只关于一个分布系统中每一个事件的信息熵,而相对熵则是两个系统对应事件熵的差值,并且前面的权重也只基于其中一个系统的系数,所以这也导致了相对熵不能作为距离度量,因为它是不对称的,即:
D K L ( P ∥ Q ) ≠ D K L ( Q ∥ P ) \boldsymbol{D}_{K L}(\boldsymbol{P} \| \boldsymbol{Q}) \neq \boldsymbol{D}_{K L}(\boldsymbol{Q} \| \boldsymbol{P}) DKL(P∥Q)=DKL(Q∥P)
将相对熵的定义继续展开:
D K L ( P ∥ Q ) = ∑ i = 1 m p i ⋅ ( ( − log q i ) − ( − log p i ) ) = ∑ i = 1 m p i ⋅ ( − log q i ) − ∑ i = 1 m p i ⋅ ( − log p i ) \begin{aligned} &\boldsymbol{D}_{K L}(\boldsymbol{P} \| \boldsymbol{Q}) \\ &=\sum_{i=1}^{m} p_{i} \cdot\left(\left(-\log q_{i}\right)-\left(-\log p_{i}\right)\right) \\ &=\sum_{i=1}^{m} p_{i} \cdot\left(-\log q_{i}\right)-\sum_{i=1}^{m} p_{i} \cdot\left(-\log p_{i}\right) \end{aligned} DKL(P∥Q)=i=1∑mpi⋅((−logqi)−(−logpi))=i=1∑mpi⋅(−logqi)−i=1∑mpi⋅(−logpi)
这时候等式右边其实就是真实分布的信息熵,在分类问题中,真实分布实则就是数据集+标签,因此它是一个定值。
所以相对熵也可以表示成:
D K L ( P ∥ Q ) = H ( p , q ) − H ( p ) \boldsymbol{D}_{K L}(\boldsymbol{P} \| \boldsymbol{Q})=H(p, q)-H(p) DKL(P∥Q)=H(p,q)−H(p)
既然我们知道了相对熵可以用来衡量两个分布系统的相似函数,那这不就说明我们可以用它来作为分类模型的损失函数吗,相对熵越小,说明两个系统的分布就越相似,这其实就可以等价于最小化损失函数为0。所以,我们其实就可以将相对熵作为损失函数。
但是到这里还没完,既然右边的H(p)是个定值,我们可否就拿左边的那部分来作为我们的损失呢?如果这么做,就需要一个前提,那就是要求相对熵必须恒为一个非负数,因为只有非负数,我们仅仅优化H(p,q)趋于0也可以保证整个相对熵最小化。如果相对熵可能出现负值,这时候仅仅优化H(p,q)趋于0就不能保证相对熵是最小了。
事实上,相对熵的确是非负的,这部分可以由吉布斯不等式证明。我这里就不深入了。
所以结论是,如果H§是定值,我们就可以仅通过优化H(p, q)来替代优化相对熵,那么H(p,q)又是什么呢。哈哈,它就是交叉熵呀
把相对熵中的第一项单独拿出来,就是交叉熵:
H ( p , q ) = − ∑ i = 1 m p i ⋅ ( log q i ) H(p,q)=-\sum_{i=1}^{m} p_{i} \cdot\left(\log q_{i}\right) H(p,q)=−i=1∑mpi⋅(logqi)
这时候,我们就可以把它和分类问题中的各个参数一一对应起来,m指的就是数据集数量;pi指的就是标签,如果是二分类,就是0或者1;qi指的就是模型预测出的某条样本对应的概率,范围是(0,1)。
但是仔细想想,感觉一些细节还有些问题。我们知道,第i条样本的交叉熵,应该对应第i个事件发生的概率,但是在二分类中,样本负例的标签为0,这显然不应该是负例样本发生的概率。同样的,逻辑回归模型预测出的结果也只包含它是正例的概率,即如果有一个样本预测为0.2,则应该表明它是负例的概率是0.8。这时候如果仅仅使用上述的交叉熵作为损失就是不完整的,应该再加上一项:
− ∑ i = 1 m ( 1 − p i ) ⋅ ( log ( 1 − q i ) ) -\sum_{i=1}^{m} (1-p_{i}) \cdot\left(\log (1-q_{i})\right) −i=1∑m(1−pi)⋅(log(1−qi))
所以,对于二分类,完整的交叉熵损失就是(哈哈,感觉还是有凑出来的成分):
H ( p , q ) = − ( ∑ i = 1 m p i ⋅ log q i + ∑ i = 1 m ( 1 − p i ) ⋅ log ( 1 − q i ) ) = − ∑ i = 1 n ( y i ⋅ log y ^ i + ( 1 − y i ) ⋅ log ( 1 − y ^ i ) ) \begin{aligned} H(p,q)=-(\sum_{i=1}^{m} p_{i} \cdot\log q_{i}+\sum_{i=1}^{m} (1-p_{i}) \cdot\log (1-q_{i})) \\ =-\sum_{i=1}^{n}\left(y_{i} \cdot \log \hat{y}_{i}+\left(1-y_{i}\right) \cdot \log\left(1-\hat{y}_{i}\right)\right) \end{aligned} H(p,q)=−(i=1∑mpi⋅logqi+i=1∑m(1−pi)⋅log(1−qi))=−i=1∑n(yi⋅logy^i+(1−yi)⋅log(1−y^i))
我们先来推导一下sigmoid函数的导函数,方便接下来直接使用:
g ( z ) = 1 1 + e − z g ′ ( z ) = d d z 1 1 + e − z = 1 ( 1 + e − z ) 2 ( e − z ) = 1 ( 1 + e − z ) ⋅ ( 1 − 1 ( 1 + e − z ) ) = g ( z ) ( 1 − g ( z ) ) \begin{aligned} &g(z)=\frac{1}{1+e^{-z}} \\\\\\ &g^{\prime}(z) =\frac{d}{d z} \frac{1}{1+e^{-z}} \\ &=\frac{1}{\left(1+e^{-z}\right)^{2}}\left(e^{-z}\right) \\ &=\frac{1}{\left(1+e^{-z}\right)} \cdot\left(1-\frac{1}{\left(1+e^{-z}\right)}\right) \\ &=g(z)(1-g(z)) \end{aligned} g(z)=1+e−z1g′(z)=dzd1+e−z1=(1+e−z)21(e−z)=(1+e−z)1⋅(1−(1+e−z)1)=g(z)(1−g(z))
把逻辑回归的交叉熵损失写成带参数的形式:
l o s s ( θ ) = − ∑ i = 1 n ( y i ⋅ log g ( θ T x i ) + ( 1 − y i ) ⋅ log ( 1 − g ( θ T x i ) ) ) \begin{aligned} loss(\theta)=-\sum_{i=1}^{n}\left(y_{i} \cdot \log g(\theta^Tx_i)+\left(1-y_{i}\right) \cdot \log\left(1-g(\theta^Tx_i)\right)\right) \end{aligned} loss(θ)=−i=1∑n(yi⋅logg(θTxi)+(1−yi)⋅log(1−g(θTxi)))
求导的过程我直接写纸上了(假设log以e为底):
所以损失函数关于参数θ的梯度就是:
g r a d . = 1 m ∑ i = 1 m ( g ( θ T x i ) − y i ) ) x i grad.=\frac{1}{m} \sum_{i=1}^{m}(g(\theta^Tx_i)-y_{i})) x_{i} grad.=m1i=1∑m(g(θTxi)−yi))xi
啊哈,是不是感觉又很眼熟,这不和多元线性回归损失的梯度一样嘛,只不过是y_hat换了个形式而已,毕竟都是它们都是属于广义线性回归的一种。
数据集简介:
The breast cancer dataset is a classic and very easy binary classification dataset.
================= ==============
类别数 2
每个类别下的样本数 212(M),357(B)
总样本数 569
每条样本的维度 30
================= ==============
(哈哈,直接在多元线性回归代码基础上改的,懒得重写了):
import sklearn.datasets as datasets # 数据集模块
import numpy as np
from sklearn.model_selection import train_test_split # 划分训练集和验证集
import sklearn.metrics # sklearn评估模块
from sklearn.preprocessing import StandardScaler # 标准归一化
from sklearn.metrics import accuracy_score
# 设置超参数
LR= 1e-5 # 学习率
EPOCH = 200000 # 最大迭代次数
BATCH_SIZE = 300 # 批大小
THRESHOLD = 1e-6 # 判断收敛条件
# 导入数据集
X, y = datasets.load_breast_cancer(return_X_y=True) # 乳腺癌二分类数据集
r= X.shape[0]
y = y.reshape(-1,1)
# 标准归一化
scaler = StandardScaler()
X = scaler.fit_transform(X)
# y = scaler.fit_transform(y)
X = np.concatenate((np.ones((r, 1)), X), axis=1)
# 划分训练集和验证集,使用sklearn中的方法
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33)
m, n = X_train.shape[0], X_train.shape[1]
# 每个epoch包含的批数
NUM_BATCH = m//BATCH_SIZE+1
# print(m,n)
# 1, 随机初始化W参数
W = np.random.rand(n, 1)
train_loss = []
test_loss = []
train_acc = []
test_acc = []
count = 0
def sigmoid(x):
return 1/(1 + np.exp(-x))
# 交叉熵损失
def cross_entropy(y_true, y_pred):
crossEntropy = -np.sum(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred)) / (1 + y_true.shape[0])
return crossEntropy
for i in range(EPOCH):
# 这部分代码打乱数据集,保证每次小批量迭代更新使用的数据集都有所不同
# 产生一个长度为m的顺序序列
index = np.arange(m)
# shuffle方法对这个序列进行随机打乱
np.random.shuffle(index)
# 打乱
X_train = X_train[index]
y_train =y_train[index]
pred_y_test = sigmoid(np.dot(X_test, W))
test_loss.append(cross_entropy(y_true=y_test, y_pred=pred_y_test))
test_acc.append(accuracy_score(y_true=y_test, y_pred=(pred_y_test > 0.5).astype(int)))
pred_y_train = sigmoid(np.dot(X_train, W))
train_loss.append(cross_entropy(y_true=y_train, y_pred=pred_y_train))
train_acc.append(accuracy_score(y_true=y_train, y_pred=(pred_y_train > 0.5).astype(int)))
if i % 100 == 0:
print("eopch: %d | train loss: %.6f | test loss: %.6f | train acc.:%.4f | test acc.:%.4f" % (i, train_loss[i], test_loss[i], train_acc[i], test_acc[i]))
for batch in range(NUM_BATCH-1):
# 切片操作获取对应批次训练数据(允许切片超过列表范围)
X_batch = X_train[batch*BATCH_SIZE: (batch+1)*BATCH_SIZE]
y_batch = y_train[batch*BATCH_SIZE: (batch+1)*BATCH_SIZE]
# 2, 求梯度,需要用上多元线性回归对应的损失函数对W求导的导函数
previous_y = sigmoid(np.dot(X_batch, W))
grad = np.dot(X_batch.T, previous_y - y_batch)
# print(X_batch.T.shape,previous_y.shape)
# 3, 更新参数,利用梯度下降法的公式
W = W - LR * grad
# 4, 判断收敛
current_y = sigmoid(np.dot(X_batch, W))
previous_loss = cross_entropy(y_true=y_batch, y_pred=previous_y)
current_loss = cross_entropy(y_true=y_batch, y_pred=current_y)
d_loss = previous_loss - current_loss
if 0 < d_loss < THRESHOLD:
count += 1
else:
count = 0
# 如果连续10次loss变化的幅度小于设定的阈值,让for循环退出
if count >= 10:
for loop in range(32):
print('===', end='')
print("\ntotal iteration is : {}".format(i))
break
if count >= 10:
break
# 打印最终结果
y_hat_train = sigmoid(np.dot(X_train, W))
loss_train = cross_entropy(y_true=y_train, y_pred=y_hat_train)
print("train loss:{}".format(loss_train))
y_hat_test = sigmoid(np.dot(X_test, W))
loss_test = cross_entropy(y_true=y_test, y_pred=y_hat_test)
print("test loss:{}".format(loss_test))
print("train acc.:{}".format(train_acc[-1]))
print("test acc.:{}".format(test_acc[-1]))
# 保存权重
# np.save("Weight.npy",W)
np.save("train_loss.npy",train_loss)
np.save("test_loss.npy",test_loss)
np.save("train_acc.npy",train_acc)
np.save("test_acc.npy",test_acc)
测试结果:
... ...
eopch: 18600 | train loss: 0.071966 | test loss: 0.059439 | train acc.:0.9843 | test acc.:0.9840
eopch: 18700 | train loss: 0.071909 | test loss: 0.059360 | train acc.:0.9843 | test acc.:0.9840
================================================================================================
total iteration is : 18717
train loss:0.07189857389459127
test loss:0.05934627841056932
train acc.:0.984251968503937
test acc.:0.9840425531914894
最终在测试集上的准确率达到了98%