第1关:实现常见损失函数的前向传播
任务描述
本关任务:实现常见损失函数的前向传播。
相关知识
为了完成本关任务,你需要掌握:常见损失函数的定义。
本实训内容可参考《深度学习入门——基于 Python 的理论与实现》一书中第4.1−4.2章节的内容。
神经网络的训练
神经网络作为上世纪90年代就已经出现的技术,为什么忽然在2012年以后重新进入人们的视野,并在计算机视觉、自然语言处理领域等领域展现了前所未有的统治力呢?答案包括两个因素,第一是计算机硬件的高速发展,为深度学习提供了强大的算力支撑;第二就是数据,随着云计算等技术的高速发展,互联网为深度学习提供了海量的数据。可以说,数据是深度学习的命脉,深度学习是由数据驱动的,数据质量也是决定神经网络模型性能的最关键的因素。
那么有了数据,怎么来获得一个能够解决问题的模型呢?这个过程就是模型的训练。训练的目的是使模型识别出数据存在的模式(pattern),可以理解为数据背后隐含的有一定共性的特征。在训练模型时,我们需要设定一个目标,使得模型的输出向着我们希望的结果靠近。用来衡量模型的输出与我们期望结果的接近程度的就是损失函数。在不同类型的任务中,我们会使用不同的损失函数。下面,我们对常用的损失函数进行介绍。
常见损失函数的定义
1. Cross Entropy
在分类任务中,网络通常会对输入样本属于每个类的概率进行预测,而我们的目标则是期望正确的类的概率最大。这就是最大似然概率的思想。基于这个思想,我们可以得到交叉熵(Cross Entropy)损失函数。其函数表达式为:
E=−∑i=1Cqilog(pi)
其中qi是标签类别的 one-hot 编码,当样本属于第i个类别时,qi=1,否则qi=0;C表示类别的个数;pi是预测的每个类别的概率,通常是 softmax 的输出:
pi=exi/∑j=1Cexj
其中xi是网络模型的输出。softmax 可以将网络的输出转化为和为1的若干个正实数,xi越大对应的pi越大,因此可以看做是多个类别的概率。
在实现交叉熵时,为了反向传播时求导的简单,通常与 softmax 一起实现,即对于模型的输出,先做一次 softmax,再计算交叉熵损失。这种做法对应的反向传播的优点会在下一关中介绍。
2. Mean Squared Error
回归问题相比于分类问题要更加简单。在回归问题中,我们的目标是使得网络模型的输出与目标尽可能的接近。为了实现这一目标,最直接的想法就是使用均方误差(Mean Squared Error)。均方误差是回归问题中常用的损失函数,其核心思想是使得模型的预测结果与目标的差的平方最小。其函数表达式为:
E=0.5∑i=1N(yi−ti)2
其中yi是网络模型的输出,ti是我们期望的目标,N是输出的个数。
常见损失函数前向传播的实现
对于交叉熵损失函数,实训已经预先定义了一个SoftmaxWithLoss
类,该类是 softmax 和交叉熵的复合实现。你需要实现该类的前向传播函数forward(x, t)
。forward(x, t)
函数的输入x
是一个维度等于2的numpy.ndarray
,形状为(B,C),其中B是 batch size,C是类别的个数;t
是 batch 中每个样本所属的类别,是一个形状为(B,)的int
类型的numpy.ndarray
。首先,你需要对x
沿着第二个维度进行 softmax 操作。实训已经提供了一个 softmax 函数的实现,你可以直接使用。之后,你需要实现交叉熵损失函数。最后的输出是 batch 中所有样本的损失的平均值。
对于均方误差损失函数,实训已经预先定义了一个MeanSquaredError
类。你需要实现该类的前向传播函数forward(y, t)
。forward()
函数的输入y
是一个维度等于2的numpy.ndarray
,形状为(B,N),其中B是 batch size,N是输出的个数;t
也是一个形状为(B,N)的numpy.ndarray
,代表每个输出的期望值。你需要实现均方误差损失函数,最后的输出是 batch 中所有样本的损失的和。
对于上面两个损失函数,你都需要将最后的损失值记录在self.loss
中。
编程要求
根据提示,在右侧编辑器 Begin 和 End 之间补充代码,实现上述损失函数。
测试说明
平台会对你编写的代码进行测试,测试方法为:平台会随机产生输入x
/y
和目标t
,然后根据你的实现代码,创建一个SoftmaxWithLoss
/MeanSquaredError
类的实例,然后利用该实例进行前向传播计算。你的答案将并与标准答案进行比较。因为浮点数的计算可能会有误差,因此只要你的答案与标准答案之间的误差不超过10−5即可。
开始你的任务吧,祝你成功!
第一关任务代码
import numpy as np
def softmax(x):
x = x - np.max(x, axis=1, keepdims=True)
return np.exp(x) / np.sum(np.exp(x), axis=1, keepdims=True)
class SoftmaxWithLoss:
def __init__(self):
self.loss = None
def forward(self, x, t):
r'''
SoftMax + Cross Entropy的前向传播
Parameter:
- x: numpy.array, (B, C)
- t: numpy.array, (B)
Return:
- loss: float
'''
########## Begin ##########
y = softmax(x)
batch_size = y.shape[0]
# 为了避免log计算的时候y太小,所以加一个1e-7来避免算数错误
loss = -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
self.loss = loss
return loss
########## End ##########
class MeanSquaredError:
def __init__(self):
self.loss = None
def forward(self, y, t):
r'''
Mean Squared Error的前向传播
Parameter:
- y: numpy.array, (B, N)
- t: numpy.array, (B, N)
Return:
- loss: float
'''
########## Begin ##########
loss = 0.5 * np.sum((y - t) ** 2)
self.loss = loss
return loss
########## End ##########
第2关:实现常见损失函数的反向传播
任务描述
本关任务:实现常见损失函数的反向传播。
相关知识
为了完成本关任务,你需要掌握:常见损失函数的定义。
本实训内容可参考《深度学习入门——基于 Python 的理论与实现》一书中第4.1−4.2章节的内容。
梯度下降法
通过上一关的学习,我们知道损失函数的作用是衡量模型的输出与我们的期望值之间的差距。那么,损失函数值越小,也就代表着我们更有可能获得一个具有更好性能的模型。那么,怎么改变模型中参数的值,才能令损失函数值不断变小呢?这里我们引入梯度的概念。梯度是函数值上升最快的参数变化方向,通常来说,这也是函数值下降最快的参数变化方向的负方向。如果我们能够求得每个参数的梯度∂l/∂w,那么我们就可以令所有的参数沿着其负梯度方向前进一小步,得到一组新的参数。这就是梯度下降法的基本思想,这一小步的距离叫做学习率η。参数更新的过程可以用下面公式表示:
wi′=wi−η⋅∂wi∂l
那么,现在问题来了,怎样才能对每个参数求出它们的梯度呢?这一过程称为神经网络的反向传播(backprop)。对于神经网络来说,因为其堆叠的结构,使得求梯度这件事变成稍微有些复杂。但也因为这种堆叠结构,使得这个过程也可以根据堆叠的顺序一步一步求解。反向传播的核心思想是求导的链式法则,即:
∂x∂l=∂f(x)∂l⋅∂x∂f(x)
这样,神经网络的求梯度过程就变成了对每一层求梯度的过程。在本实训中,我们聚焦在上面公式的第一项,即损失函数自身的反向传播。
常见损失函数的反向传播
1. Cross Entropy
在上一关中,我们介绍了交叉熵损失函数的函数表达式为:
E=−∑i=1Ctilog(yi)
如果合并 softmax,那么则有:
yiE=∑j=1Cexiexi=−i=1∑Ctilog(yi)
上面公式对应的xi的梯度的计算为:
∂xi∂l=yi−ti
当t为 one-hot 编码时,则可以进一步简化为:
∂xk∂l=yk−1
其中k是正确类别对应的编号。上面公式的推导过程略微繁琐,感兴趣的同学可以参考这篇文章。
2. Mean Squared Error
相比于交叉熵,均方误差的反向传播较为简单。回顾均方误差的前向传播公式:
E=0.5∑i=1N(yi−ti)2
我们可以直接写出其反向传播的公式:
∂yi∂l=yi−ti
其中yi是网络模型的输出,ti是我们期望的目标,N是输出的个数。
常见损失函数前向传播的实现
对于交叉熵损失函数,实训拓展了上一关中定义的SoftmaxWithLoss
类,该类是 softmax 和交叉熵的复合实现。实训已经给出了forward(x, t)
的实现,并针对反向传播的需要对其进行了一定的修改。你需要实现该类的反向传播函数backward()
。backward()
函数不需要任何的输入,你需要根据forward(x, t)
函数调用时记录的self.t
和self.y
的值,来计算forward(x, t)
输入x
的梯度,并作为backward()
的返回值返回。
对于均方误差损失函数,实训拓展了上一关中定义的MeanSquaredError
类,该类是 softmax 和交叉熵的复合实现。实训已经给出了forward(y, t)
的实现,并针对反向传播的需要对其进行了一定的修改。你需要实现该类的反向传播函数backward()
。backward()
函数不需要任何的输入,你需要根据forward(y, t)
函数调用时记录的self.t
和self.y
的值,来计算forward(y, t)
输入y
的梯度,并作为backward()
的返回值返回。
编程要求
根据提示,在右侧编辑器 Begin 和 End 之间补充代码,实现上述损失函数。
测试说明
平台会对你编写的代码进行测试,测试方法为:平台会随机产生输入x
/y
和目标t
,然后根据你的实现代码,创建一个SoftmaxWithLoss
/MeanSquaredError
类的实例,然后利用该实例先进行前向传播计算,在进行反向传播计算。你的答案将并与标准答案进行比较。因为浮点数的计算可能会有误差,因此只要你的答案与标准答案之间的误差不超过10−5即可。
开始你的任务吧,祝你成功!
第二关任务代码
import numpy as np
def softmax(x):
x = x - np.max(x, axis=1, keepdims=True)
return np.exp(x) / np.sum(np.exp(x), axis=1, keepdims=True)
class SoftmaxWithLoss:
def __init__(self):
self.loss = None
self.y = None
self.t = None
def forward(self, x, t):
r'''
SoftMax + Cross Entropy的前向传播
Parameter:
- x: numpy.array, (B, C)
- t: numpy.array, (B)
Return:
- loss: float
'''
y = softmax(x)
batch_size = y.shape[0]
loss = -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
self.loss = loss
self.y = y
self.t = t
return loss
def backward(self):
r'''
SoftMax + Cross Entropy的反向传播
Return:
- dx: numpy.array, (B, C)
'''
########## Begin ##########
batch_size = self.t.shape[0]
dx = self.y.copy()
dx[np.arange(batch_size), self.t] -= 1
dx = dx / batch_size
return dx
########## End ##########
class MeanSquaredError:
def __init__(self):
self.loss = None
self.y = None
self.t = None
def forward(self, y, t):
r'''
Mean Squared Error的前向传播
Parameter:
- y: numpy.array, (B, N)
- t: numpy.array, (B, N)
Return:
- loss: float
'''
loss = 0.5 * np.sum((y - t) ** 2)
self.loss = loss
self.y = y
self.t = t
return loss
def backward(self):
r'''
Mean Squared Error的反向传播
Return:
- dy: numpy.array, (B, N)
'''
########## Begin ##########
y_grad = self.y - self.t
return y_grad
########## End ##########