47.2_softmax函数

47.2 softmax函数

当使用神经网络进行多分类时,我们可以直接使用之前在线性回归中使用的神经网络。此前在MLP类中实现了神经网络,这意味着我们可以直接使用它。例如,对于输入数据的维度是2,需要将数据分为3类的问题,我们可以写出以下代码。

steps/step47.py
fromdezero.modelsimportMLP model  $=$  MLP((10,3))

上面的代码通过MLP((10, 3))创建了一个2层的全连接网络。第1个全连接层的输出大小为10,第2个全连接层的输出大小为3。由此得到的model会把输入数据变换为三维向量(有3个元素的向量)。输入一组数据后,代码如下所示。

steps/step47.py
$\mathbf{x} = \mathbf{np}$  .array([0.2,-0.4])   
y  $=$  model(x)   
print(y)

运行结果

variable([[-0.6150578 -0.42790162 0.31733288]])

上面代码中的 xx 的形状是 (1, 2)。该形状表示 1 个样本数据中有 2 个元素 (=二维向量)。神经网络的输出的形状是 (1, 3),它意味着 1 个样本数据会变换为 3 个元素 (=三维向量)。这个三维向量的每个元素都对应一个类别,元素值最大的索引就是模型分类的类别。在上面的例子中,(在第 0 个、第 1 个和第 2 个元素中) 第 2 个元素的值最大,为 0.31733288,这表示模型将数据分到了第 2 个类中。

虽然上面的示例代码中只有一个输入数据,但模型也支持一次性处理多个数据。如果 x=np.array([0.2,0.4],[0.3,0.5],[1.3,3.2],[2.1,0.3])x = \text{np.array}([0.2, -0.4], [0.3, 0.5], [1.3, -3.2], [2.1, 0.3]) ,那么4个输入数据将被合并为一组数据, y=model(x)y = \text{model}(x) 会一次性处理4个输入数据。具体来说,y.shape为(4,3),其中第i个输入数据是 x[i]x[i] ,相应的输出是 y[i](i=0,1,2,3)y[i](i=0,1,2,3)

这里展示的代码示例中,神经网络的输出是数值。这个数值也能够转换为概率。进行这种转换的是softmax函数。softmax函数的式子如下所示。

pk=exp(yk)i=1nexp(yi)(47.1)p _ {k} = \frac {\exp (y _ {k})}{\sum_ {i = 1} ^ {n} \exp (y _ {i})} \tag {47.1}

假设向softmax函数输入的数据 yky_{k} 共有 nn 个(这个 nn 是类的数量)。式子47.1是求第 kk 个输出 pkp_k 的式子。softmax函数的分子是输入 yky_{k} 的指数函数,分母是所有输入的指数函数之和,因此有 0pi10\leqslant p_i\leqslant 1p1+p2++pn=1p_1 + p_2 + \dots +p_n = 1 换言之, (p1,p2,pn)(p_{1},p_{2},\dots p_{n}) 可以解释为一种概率。

下面在DeZero中实现softmax函数。首先实现输入数据只有一个(一个样本数据)时的softmax函数。代码如下所示。

steps/step47.py

fromdezeroimportVariable,as_variable   
importdezero-functionsasF   
defsoftmaxld(x):  $\mathrm{x} =$  as_variable(x)  $\mathbf{y} = \mathbf{F}\cdot \exp (\mathbf{x})$  sum_y  $=$  F-sum(y) return y/sum_y

函数内部的实现仅仅是使用DeZero函数对式子47.1进行编码(假定DeZero的Exp类和exp函数已经添加到functions.py中)。第一行的 x=as_variable(x)x = \text{as\_variable}(x) 确保ndarray实例x被转换为Variable实例。

在上面代码y/sum_y的计算中,由于y和sum_y的形状不同,所以通过广播使二者形状匹配。我们已经在DeZero中支持了广播。因此,在使用广播的情况下,反向传播也会正确进行。

下面我们来实际使用一下softmax1d函数。

steps/step47.py
$\mathbf{x} =$  Variable(np.array([[0.2, -0.4]]))  
y  $=$  model(x)  
p  $=$  softmax1d(y)  
print(y)  
print(p)

运行结果

variable([[-0.61505778 -0.42790161 0.31733289]])  
variable([[0.21068638 0.25404893 0.53526469]])

结果 pp 的每个元素都是 0 和 1 之间的值,它们的总和是 1。这样就成功地将神经网络的输出变换为概率了。

由于softmax函数的计算中有指数函数的计算,其值容易变大(或变小),所以,在实现softmax函数时,我们通常会采取防止溢出的措施。本书不对这部分内容进行介绍。关于如何更好地实现softmax函数,可以参阅《深度学习入门:基于Python的理论与实现》的3.5.2节。

下面对softmax函数进行扩展,使其能够批量处理数据,比如图47-2中的将softmax函数应用于每个样本数据的情况。

[-0.615,-0.427,0.317]  
[-0.763,-0.249,0.185]  
[-0.520,-0.962,0.578]  
[-0.942,-0.503,0.175]


图47-2 对二维数据应用softmax函数的例子

[ \begin{bmatrix} [0.210, 0.254, 0.535] \\ [0.190, 0.318, 0.491] \\ [0.215, 0.138, 0.646] \\ [0.178, 0.276, 0.545] \end{bmatrix} ]

我们要实现上图这种能应用于批量数据的softmax函数,其实现如下所示①。

dezero/functions.py

def softmaxsimple(x,axis=1): x = as_variable(x) y  $=$  exp(x) sum_y  $=$  sum(y,axis  $\equiv$  axis,keepdims=True) return y / sum_y

假设参数 xx 为二维数据,参数axis指定了softmax函数应用于哪个轴。如果 axis=1\text{axis} = 1 ,则像图47-2那样使用softmax函数。求和时的参数keepdims=True,这意味着对每一行都执行式子47.1的除法运算。

这里实现的softmax.simple函数只使用了DeZero函数。虽然它能输出正确的结果,但也有可改进的地方。更好的实现方式是实现继承Function类的Softmax类,然后实现softmax函数,这里不再赘述。代码在dezero/functions.py中,感兴趣的读者可以参考。