[ch03-02] 交叉熵损失函数

系列博客,原文在笔者所维护的github上:https://aka.ms/beginnerAI
点击star加星不要吝啬,星越多笔者越努力。

3.2 交叉熵损失函数

交叉熵(Cross Entropy)是Shannon信息论中一个重要概念,主要用于度量两个概率分布间的差异性信息。在信息论中,交叉熵是表示两个概率分布 \(p,q\) 的差异,其中 \(p\) 表示真实分布,\(q\) 表示非真实分布,那么\(H(p,q)\)就称为交叉熵:

\[H(p,q)=\sum_i p_i \cdot \ln {1 \over q_i} = - \sum_i p_i \ln q_i \tag{1}\]

交叉熵可在神经网络中作为损失函数,\(p\) 表示真实标记的分布,\(q\) 则为训练后的模型的预测标记分布,交叉熵损失函数可以衡量 \(p\)\(q\) 的相似性。

交叉熵函数常用于逻辑回归(logistic regression),也就是分类(classification)。

3.2.1 交叉熵的由来

信息量

信息论中,信息量的表示方式:

\[I(x_j) = -\ln (p(x_j)) \tag{2}\]

\(x_j\):表示一个事件

\(p(x_j)\):表示\(x_j\)发生的概率

\(I(x_j)\):信息量,\(x_j\)越不可能发生时,它一旦发生后的信息量就越大

假设对于学习神经网络原理课程,我们有三种可能的情况发生,如表3-2所示。

表3-2 三种事件的概论和信息量

事件编号事件概率 \(p\)信息量 \(I\)
\(x_1\)优秀\(p=0.7\)\(I=-\ln(0.7)=0.36\)
\(x_2\)及格\(p=0.2\)\(I=-\ln(0.2)=1.61\)
\(x_3\)不及格\(p=0.1\)\(I=-\ln(0.1)=2.30\)

WoW,某某同学不及格!好大的信息量!相比较来说,“优秀”事件的信息量反而小了很多。

\[H(p) = - \sum_j^n p(x_j) \ln (p(x_j)) \tag{3}\]

则上面的问题的熵是:

\[ \begin{aligned} H(p)&=-[p(x_1) \ln p(x_1) + p(x_2) \ln p(x_2) + p(x_3) \ln p(x_3)] \\ &=0.7 \times 0.36 + 0.2 \times 1.61 + 0.1 \times 2.30 \\ &=0.804 \end{aligned} \]

相对熵(KL散度)

相对熵又称KL散度,如果我们对于同一个随机变量 \(x\) 有两个单独的概率分布 \(P(x)\)\(Q(x)\),我们可以使用 KL 散度(Kullback-Leibler (KL) divergence)来衡量这两个分布的差异,这个相当于信息论范畴的均方差。

KL散度的计算公式:

\[D_{KL}(p||q)=\sum_{j=1}^n p(x_j) \ln{p(x_j) \over q(x_j)} \tag{4}\]

\(n\) 为事件的所有可能性。\(D\) 的值越小,表示 \(q\) 分布和 \(p\) 分布越接近。

交叉熵

把上述公式变形:

\[ \begin{aligned} D_{KL}(p||q)&=\sum_{j=1}^n p(x_j) \ln{p(x_j)} - \sum_{j=1}^n p(x_j) \ln q(x_j) \\ &=- H(p(x)) + H(p,q) \end{aligned} \tag{5} \]

等式的前一部分恰巧就是p的熵,等式的后一部分,就是交叉熵:

\[H(p,q) =- \sum_{j=1}^n p(x_j) \ln q(x_j) \tag{6}\]

在机器学习中,我们需要评估label和predicts之间的差距,使用KL散度刚刚好,即\(D_{KL}(y||a)\),由于KL散度中的前一部分\(H(y)\)不变,故在优化过程中,只需要关注交叉熵就可以了。所以一般在机器学习中直接用交叉熵做损失函数来评估模型。

\[loss =- \sum_{j=1}^n y_j \ln a_j \tag{7}\]

其中,\(n\) 并不是样本个数,而是分类个数。所以,对于批量样本的交叉熵计算公式是:

\[J =- \sum_{i=1}^m \sum_{j=1}^n y_{ij} \ln a_{ij} \tag{8}\]

\(m\) 是样本数,\(n\) 是分类数。

有一类特殊问题,就是事件只有两种情况发生的可能,比如“学会了”和“没学会”,称为\(0/1\)分布或二分类。对于这类问题,由于\(n=2\),所以交叉熵可以简化为:

\[loss =-[y \ln a + (1-y) \ln (1-a)] \tag{9}\]

二分类对于批量样本的交叉熵计算公式是:

\[J= - \sum_{i=1}^m [y_i \ln a_i + (1-y_i) \ln (1-a_i)] \tag{10}\]

3.2.2 二分类问题交叉熵

把公式10分解开两种情况,当\(y=1\)时,即标签值是1,是个正例,加号后面的项为0:

\[loss = -\ln(a) \tag{11}\]

横坐标是预测输出,纵坐标是损失函数值。y=1意味着当前样本标签值是1,当预测输出越接近1时,损失函数值越小,训练结果越准确。当预测输出越接近0时,损失函数值越大,训练结果越糟糕。

当y=0时,即标签值是0,是个反例,加号前面的项为0:

\[loss = -\ln (1-a) \tag{12}\]

此时,损失函数值如图3-10。

图3-10 二分类交叉熵损失函数图

假设学会了课程的标签值为1,没有学会的标签值为0。我们想建立一个预测器,对于一个特定的学员,根据出勤率、课堂表现、作业情况、学习能力等等来预测其学会课程的概率。

对于学员甲,预测其学会的概率为0.6,而实际上该学员通过了考试,真实值为1。所以,学员甲的交叉熵损失函数值是:

\[ loss_1 = -(1 \times \ln 0.6 + (1-1) \times \ln (1-0.6)) = 0.51 \]

对于学员乙,预测其学会的概率为0.7,而实际上该学员也通过了考试。所以,学员乙的交叉熵损失函数值是:

\[ loss_2 = -(1 \times \ln 0.7 + (1-1) \times \ln (1-0.7)) = 0.36 \]

由于0.7比0.6更接近1,是相对准确的值,所以 \(loss2\) 要比 \(loss1\) 小,反向传播的力度也会小。

3.2.3 多分类问题交叉熵

当标签值不是非0即1的情况时,就是多分类了。假设期末考试有三种情况:

  1. 优秀,标签值OneHot编码为\([1,0,0]\)
  2. 及格,标签值OneHot编码为\([0,1,0]\)
  3. 不及格,标签值OneHot编码为\([0,0,1]\)

假设我们预测学员丙的成绩为优秀、及格、不及格的概率为:\([0.2,0.5,0.3]\),而真实情况是该学员不及格,则得到的交叉熵是:

\[ loss_1 = -(0 \times \ln 0.2 + 0 \times \ln 0.5 + 1 \times \ln 0.3) = 1.2 \]

假设我们预测学员丁的成绩为优秀、及格、不及格的概率为:\([0.2,0.2,0.6]\),而真实情况是该学员不及格,则得到的交叉熵是:

\[ loss_2 = -(0 \times \ln 0.2 + 0 \times \ln 0.2 + 1 \times \ln 0.6) = 0.51 \]

可以看到,0.51比1.2的损失值小很多,这说明预测值越接近真实标签值(0.6 vs 0.3),交叉熵损失函数值越小,反向传播的力度越小。

3.2.4 为什么不能使用均方差做为分类问题的损失函数?

  1. 回归问题通常用均方差损失函数,可以保证损失函数是个凸函数,即可以得到最优解。而分类问题如果用均方差的话,损失函数的表现不是凸函数,就很难得到最优解。而交叉熵函数可以保证区间内单调。

  2. 分类问题的最后一层网络,需要分类函数,Sigmoid或者Softmax,如果再接均方差函数的话,其求导结果复杂,运算量比较大。用交叉熵函数的话,可以得到比较简单的计算结果,一个简单的减法就可以得到反向误差。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这是一个使用循环神经网络模型来处理拼音文本的任务。下面是一些参考代码和思路,供你参考: 1. 实现基本循环神经网络模型,循环单元为nn.RNN或GRU ```python import torch import torch.nn as nn class RNNModel(nn.Module): def __init__(self, input_size, hidden_size, output_size, num_layers, rnn_type='RNN'): super(RNNModel, self).__init__() self.input_size = input_size self.hidden_size = hidden_size self.output_size = output_size self.num_layers = num_layers self.rnn_type = rnn_type if rnn_type == 'RNN': self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True) elif rnn_type == 'GRU': self.rnn = nn.GRU(input_size, hidden_size, num_layers, batch_first=True) self.fc = nn.Linear(hidden_size, output_size) def forward(self, x, h=None): if h is None: h = torch.zeros(self.num_layers, x.size(0), self.hidden_size) out, h = self.rnn(x, h) out = self.fc(out) return out, h ``` 2. 输出层的全连接使用RNN所有时间步的输出 ```python # 在RNNModel中的forward函数中修改为 def forward(self, x, h=None): if h is None: h = torch.zeros(self.num_layers, x.size(0), self.hidden_size) out, h = self.rnn(x, h) out = out.contiguous().view(-1, self.hidden_size) out = self.fc(out) return out, h ``` 3. 隐状态初始值为0 ```python # 在RNNModel中的forward函数中修改为 def forward(self, x, h=None): if h is None: h = torch.zeros(self.num_layers, x.size(0), self.hidden_size) else: h = h.detach() out, h = self.rnn(x, h) out = out.contiguous().view(-1, self.hidden_size) out = self.fc(out) return out, h ``` 4. 测试前向传播 ```python # 假设已经加载了拼音文本数据,可以将拼音文本转化为对应的数值向量,然后进行前向传播 model.eval() h = None for i in range(len(input)): x = input[i].unsqueeze(0) # 将输入转化为batch_size=1的张量 out, h = model(x, h) # 然后可以对输出进行处理,例如计算损失或者进行预测 ``` 5. 采用顺序划分时,需要进行梯度截断 ```python model.train() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) criterion = nn.CrossEntropyLoss() for i in range(len(inputs)): optimizer.zero_grad() x = inputs[i] y = targets[i] h = None for j in range(len(x)): # 将输入和目标转化为张量 input_tensor = torch.tensor(x[j]).unsqueeze(0) target_tensor = torch.tensor(y[j]).unsqueeze(0) # 前向传播 output, h = model(input_tensor, h) # 计算损失 loss = criterion(output, target_tensor.view(-1)) # 反向传播 loss.backward() # 梯度截断 nn.utils.clip_grad_norm_(model.parameters(), max_norm=5) # 更新参数 optimizer.step() ``` 6. 训练时使用平均交叉熵作为损失函数 ```python # 在上面的代码中使用交叉熵损失函数 criterion = nn.CrossEntropyLoss() # 在每个batch训练结束后计算平均损失 total_loss = 0 for j in range(len(x)): # ... loss = criterion(output, target_tensor.view(-1)) total_loss += loss.item() average_loss = total_loss / len(x) ``` 7. 预测时可以给定一个前缀,进行单步预测和K步预测 ```python # 假设已经加载了拼音文本数据,可以将拼音文本转化为对应的数值向量,然后进行预测 model.eval() h = None prefix = ['b', 'a'] prefix_tensor = torch.tensor([vocab.stoi[ch] for ch in prefix]).unsqueeze(0) out, h = model(prefix_tensor, h) # 单步预测 _, topi = out[-1].topk(1) predicted_char = vocab.itos[topi.item()] # K步预测 k = 5 for i in range(k): input_tensor = torch.tensor(vocab.stoi[predicted_char]).unsqueeze(0).unsqueeze(0) out, h = model(input_tensor, h) _, topi = out[-1].topk(1) predicted_char = vocab.itos[topi.item()] print(predicted_char) ``` 8. 改变时间步数,测试对应的性能,并思考其原因 可以尝试改变时间步数,例如从单个字符预测改为预测两个字符,或者从预测两个字符改为预测三个字符等等。改变时间步数可能会对模型的性能产生影响。具体原因可能与模型的架构和数据集的特点有关,需要具体分析和实验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值