深入理解softmax

前言

本文代码基于Pytorch实现。

一、softmax的定义及代码实现

1.1 定义

s o f t m a x ( x i ) = e x p ( x i ) ∑ j n e x p ( x j ) softmax(x_i) = \frac{exp(x_i)}{\sum_j^nexp(x_j)} softmax(xi)=jnexp(xj)exp(xi)

1.2 代码实现

def softmax(X):
    '''
    实现softmax
    输入X的形状为[样本个数,输出向量维度]
    '''
    return torch.exp(X) / torch.sum(torch.exp(X), dim=1).reshape(-1, 1)

>>> X = torch.randn(5, 5)
>>> y = softmax(X)
>>> torch.sum(y, dim=1)
tensor([1.0000, 1.0000, 1.0000, 1.0000])

二、softmax的作用


softmax可以对线性层的输出做规范化校准:保证输出为非负且总和为1。
因为如果直接将未规范化的输出看作概率,会存在2点问题:

  1. 线性层的输出并没有限制每个神经元输出数字的总和为1;
  2. 根据输入的不同,线性层的输出可能为负值。
    这2点违反了概率的基本公理。

三、softmax的上溢出(overflow)与下溢出(underflow)

3.1 上溢出

x i x_i xi的取值过大时,指数运算的取值过大,若超出精度表示范围,则上溢出。

>>> torch.exp(torch.tensor([1000]))
tensor([inf])

3.2 下溢出

当向量 x \boldsymbol x x的每个元素 x i x_i xi的取值均为绝对值很大的负数时,则 e x p ( x i ) exp(x_i) exp(xi)的数值很小超出了精度范围向下取0,分母 ∑ j e x p ( j ) \sum_jexp(j) jexp(j)的取值为0。

>>> X = torch.ones(1, 3) * (-1000)
>>> softmax(X)
tensor([[nan, nan, nan]])

3.3 避免溢出

参考1中的技巧:

  1. 找到向量 x \boldsymbol x x中的最大值:
    c = m a x ( x ) c=max(\boldsymbol x) c=max(x)
  2. s o f t m a x softmax softmax的分子、分母同时除以 c c c
    s o f t m a x ( x i − c ) = e x p ( x i − c ) ∑ j n e x p ( x j − c ) = e x p ( x i ) e x p ( − c ) ∑ j n e x p ( x i ) e x p ( − c ) = s o f t m a x ( x i ) softmax(x_i - c) = \frac{exp(x_i-c)}{\sum_j^nexp(x_j-c)}=\frac{exp(x_i)exp(-c)}{\sum_j^nexp(x_i)exp(-c)}=softmax(x_i) softmax(xic)=jnexp(xjc)exp(xic)=jnexp(xi)exp(c)exp(xi)exp(c)=softmax(xi)
    经过上述变换,分子的最大取值变为了 e x p ( 0 ) = 1 exp(0)=1 exp(0)=1,避免了上溢出;
    分母中至少会 + 1 +1 +1,避免了分母为0造成下溢出。
    ∑ j n e x p ( x j − c ) = e x p ( x i − c ) + e x p ( x 2 − c ) + . . . + e x p ( x m a x − c ) = e x p ( x 1 − c ) + e x p ( x 2 − c ) + . . . + 1 \sum_j^nexp(x_j-c) =exp(x_i-c)+exp(x_2-c)+...+exp(x_{max}-c)\\ =exp(x_1-c) + exp(x_2-c)+...+1 jnexp(xjc)=exp(xic)+exp(x2c)+...+exp(xmaxc)=exp(x1c)+exp(x2c)+...+1
def softmax_trick(X):
    c, _ = torch.max(X, dim=1, keepdim=True)
    return torch.exp(X - c) / torch.sum(torch.exp(X - c), dim=1).reshape(-1, 1)
>>> X = torch.tensor([[-1000, 1000, -1000]])
>>> softmax_trick(X)
tensor([0., 1., 0.])
>>> softmax(X)
tensor([[0., nan, 0.]])

pytorch的实现中已经做过了防止溢出的处理,所以,其运行结果与softmax_trick一致。

import pytorch.nn.functional as F
>>> X = torch.tensor([[-1000., 1000., -1000.]])
>>> F.softmax(X, dim=1)
tensor([[0., 1., 0.]])

3.4 Log-Sum_Exp Trick2(取log操作)

1. 避免下溢出
对数运算可以将相乘变为相加,即: l o g ( x 1 x 2 ) = l o g ( x 1 ) + l o g ( x 2 ) log(x_1x_2) = log(x_1) + log(x_2) log(x1x2)=log(x1)+log(x2)。 当两个很小的数 x 1 、 x 2 x_1、x_2 x1x2相乘时,其乘积会变得更小,超出精度则下溢出;而对数操作将乘积变为相加,降低了下溢出的风险。
2. 避免上溢出
l o g − s o f t m a x log-softmax logsoftmax的定义:
l o g − s o f t m a x = l o g [ s o f t m a x ( x i ) ] = l o g ( e x p ( x i ) ∑ j n e x p ( x j ) ) = x i − l o g [ ∑ j n e x p ( x j ) ] \begin{aligned} log-softmax &=log[softmax(x_i)] \\ &= log(\frac{exp(x_i)}{\sum_j^nexp(x_j)}) \\ &=x_i - log[\sum_j^nexp(x_j)] \end{aligned} logsoftmax=log[softmax(xi)]=log(jnexp(xj)exp(xi))=xilog[jnexp(xj)]
y = l o g ∑ j n e x p ( x j ) y=log\sum_j^nexp(x_j) y=logjnexp(xj),当 x j x_j xj的取值过大时, y y y存在上溢出的风险,因此,采用与3.3中同样的Trick:
y = l o g ∑ j n e x p ( x j ) = l o g ∑ j n e x p ( x j − c ) e x p ( c ) = c + l o g ∑ j n e x p ( x j − c ) \begin{aligned} y &= log\sum_j^nexp(x_j) \\ & = log\sum_j^nexp(x_j-c)exp(c) \\ & = c +log\sum_j^nexp(x_j-c) \end{aligned} y=logjnexp(xj)=logjnexp(xjc)exp(c)=c+logjnexp(xjc)
c = m a x ( x ) c=max(\boldsymbol x) c=max(x)时,可避免上溢出。
此时, l o g − s o f t m a x log-softmax logsoftmax的计算公式变为:(其实等价于直接对3.3节的Trick取对数
l o g − s o f t m a x = ( x i − c ) − l o g ∑ j n e x p ( x j − c ) log-softmax = (x_i-c)-log\sum_j^nexp(x_j-c) logsoftmax=(xic)logjnexp(xjc)
代码实现:

def log_softmax(X):
	c, _ = torch.max(X, dim=1, keepdim=True)
	return X - c - torch.log(torch.sum(torch.exp(X-c), dim=1, keepdim=True))
>>> X = torch.tensor([[-1000., 1000., -1000.]])
>>> torch.exp(log_softmax(X))
tensor([[0., 1., 0.]])
# pytorch API实现
>>> torch.exp(F.log_softmax(X, dim=1))
tensor([[0., 1., 0.]])

3.5 log-softmax与softmax的区别3

结合3.3节的Trick及我自己的理解:

  1. 在pytorch的实现中,softmax的运算结果等价于对log_softmax的结果作指数运算
>>> X = torch.tensor([[-1000., 1000., -1000.]])
>>> torch.exp(F.log_softmax(X, dim=1)) == F.softmax(X)
tensor([[True, True, True]])
  1. 使用 l o g log log运算之后求导更方便,可以加快反向传播的速度4
    ∂ ∂ x i l o g s o f t m a x = ∂ ∂ x i [ x i − l o g ∑ j n e x p ( x j ) ] = 1 − s o f t m a x ( x i ) \begin{aligned} \frac{\partial}{\partial x_i}logsoftmax&=\frac{\partial}{\partial x_i} [{x_i - log\sum_j^nexp(x_j)]} \\ &= 1 - softmax(x_i) \end{aligned} xilogsoftmax=xi[xilogjnexp(xj)]=1softmax(xi)


  1. BTTB你不知道的softmax ↩︎

  2. The Log-Sum_Exp Trick ↩︎

  3. log-softmax与softmax的区别 ↩︎

  4. 动手学深度学习:softmax回归 ↩︎

  • 13
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,关于注意力机制的代码,我们先来了解一下图注意力机制(Graph Attention Network,简称GAT)。 GAT是一种基于图结构的注意力机制,它在图结构中进行节点分类、图分类等任务时,能够自适应地学习每个节点在不同邻居节点上所占的重要性,从而更准确地进行分类。下面是一个简单的GAT实现: ```python import torch import torch.nn as nn import torch.nn.functional as F class GraphAttentionLayer(nn.Module): def __init__(self, in_features, out_features, dropout, alpha, concat=True): super(GraphAttentionLayer, self).__init__() self.in_features = in_features self.out_features = out_features self.dropout = dropout self.alpha = alpha self.concat = concat self.W = nn.Parameter(torch.zeros(size=(in_features, out_features))) nn.init.xavier_uniform_(self.W.data, gain=1.414) self.a = nn.Parameter(torch.zeros(size=(2*out_features, 1))) nn.init.xavier_uniform_(self.a.data, gain=1.414) self.leakyrelu = nn.LeakyReLU(self.alpha) def forward(self, h, adj): Wh = torch.mm(h, self.W) a_input = self._prepare_attentional_mechanism_input(Wh) e = self.leakyrelu(torch.matmul(a_input, self.a).squeeze(2)) zero_vec = -9e15*torch.ones_like(e) attention = torch.where(adj > 0, e, zero_vec) attention = F.softmax(attention, dim=1) attention = F.dropout(attention, self.dropout, training=self.training) h_prime = torch.matmul(attention, Wh) if self.concat: return F.elu(h_prime) else: return h_prime def _prepare_attentional_mechanism_input(self, Wh): N = Wh.size()[0] Wh_repeated_in_chunks = Wh.repeat_interleave(N, dim=0) Wh_repeated_alternating = Wh.repeat(N, 1) all_combinations_matrix = torch.cat([Wh_repeated_in_chunks, Wh_repeated_alternating], dim=1) return all_combinations_matrix.view(N, N, 2 * self.out_features) ``` 在这个代码中,我们定义了一个名为GraphAttentionLayer的类,它继承于nn.Module类。在它的__init__方法中,我们定义了一些必要的参数,包括输入特征维度、输出特征维度、dropout率、LeakyReLU函数的负斜率系数以及是否将节点特征与注意力机制的输出进行拼接。W和a是需要学习的参数,其中W是线性变换的权重矩阵,a是注意力机制的权重矩阵。我们使用xavier_uniform_方法对这两个参数进行初始化。 在forward方法中,我们首先将节点特征矩阵h与权重矩阵W相乘,得到Wh。然后,我们通过_prepare_attentional_mechanism_input方法将Wh转换为用于注意力计算的输入矩阵a_input。接着,我们将a_input与注意力权重矩阵a相乘,得到每个节点与其邻居之间的注意力系数e。我们使用LeakyReLU函数将e中的负值裁剪掉。然后,我们对每个节点的邻居节点计算softmax,得到它们之间的注意力权重。我们使用dropout对注意力权重进行随机失活,以防止过拟合。最后,我们将注意力权重与Wh相乘,得到每个节点的新特征h_prime。如果concat参数为True,我们将h_prime与原始节点特征进行拼接并使用ELU函数作为输出;否则,我们直接返回h_prime作为输出。 需要注意的是,这个代码中的实现是基于PyTorch框架的,如果你使用其他的深度学习框架,可能需要做一些调整。同时,这个代码只是GAT的一个简单实现,如果你想深入学习GAT,还需要阅读相关论文并了解更多细节。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值