Pytorch 自定义激活函数前向与反向传播 sigmoid
文章目錄
- Sigmoid
- 公式
- 求導過程
- 優點:
- 缺點:
- 自定義Sigmoid
- 與Torch定義的比較
- 可視化
Sigmoid
公式
sigmoid(x)=σ(x)=11+e?x\text{sigmoid}(x)= \sigma(x) = \frac{1}{1+e^{-x}}sigmoid(x)=σ(x)=1+e?x1?
求導過程
σ′(x)=[(1+e?x)?1]′=(?1)(1+e?x)?2(?1)e?x=(1+e?x)?2e?x=e?x(1+e?x)2=1+e?x?1(1+e?x)2=1+e?x(1+e?x)2?1(1+e?x)2=1(1+e?x)(1?1(1+e?x))=σ(x)(1?σ(x))\begin{aligned} \sigma'(x) =&[(1+e^{-x})^{-1}]' \\ =&(-1)(1+e^{-x})^{-2}(-1)e^{-x}\\ =&(1+e^{-x})^{-2}e^{-x}\\ =&\frac{e^{-x}}{(1+e^{-x})^2} \\ =&\frac{1+e^{-x}-1}{(1+e^{-x})^2} \\ =&\frac{1+e^{-x}}{(1+e^{-x})^2} - \frac{1}{(1+e^{-x})^2} \\ =&\frac{1}{(1+e^{-x})}(1-\frac{1}{(1+e^{-x})}) \\ =&\sigma(x)(1-{\sigma(x)}) \end{aligned}σ′(x)========?[(1+e?x)?1]′(?1)(1+e?x)?2(?1)e?x(1+e?x)?2e?x(1+e?x)2e?x?(1+e?x)21+e?x?1?(1+e?x)21+e?x??(1+e?x)21?(1+e?x)1?(1?(1+e?x)1?)σ(x)(1?σ(x))?
用于隱層神經元輸出,取值范圍為(0,1),它可以將一個實數映射到(0,1)的區間,可以用來做二分類。在特征相差比較復雜或是相差不是特別大時效果比較好。Sigmoid作為激活函數有以下優缺點:
優點:
- 輸出范圍有限,數據在傳遞的過程中不容易發散。
- 輸出范圍為(0,1),所以可以用作輸出層,輸出表示概率。
- 抑制兩頭,對中間細微變化敏感,對分類有利。
- 在特征相差比較復雜或是相差不是特別大時效果比較好。
缺點:
- 梯度消失(Gradient Vanishing)會導致backpropagate時,w的系數太小,w更新很慢。所以對初始化時要特別注意,避免過大的初始值使神經元進入飽和區。
- 輸出不是zero-center 這會導致后層的神經元的輸入是非0均值的信號,這會對梯度產生影響:假設后層神經元的輸入都為正(e.g. x>0 elementwise in ),那么對w求局部梯度則都為正,這樣在反向傳播的過程中w要么都往正方向更新,要么都往負方向更新,導致有一種捆綁的效果,使得收斂緩慢。 如果你是按batch去訓練,那么每個batch可能得到不同的符號(正或負),那么相加一下這個問題還是可以緩解
- 指數運算耗時,計算效率低
自定義Sigmoid
class SelfDefinedSigmoid(torch.autograd.Function):@staticmethoddef forward(ctx, inp):result = torch.divide(torch.tensor(1), (1 + torch.exp(-inp)))ctx.save_for_backward(result)return result@staticmethoddef backward(ctx, grad_output):# ctx.saved_tensors is tuple (tensors, grad_fn)result, = ctx.saved_tensorsreturn grad_output * result * (1 - result)class Sigmoid(nn.Module):def __init__(self):super().__init__()def forward(self, x):out = SelfDefinedSigmoid.apply(x)return out與Torch定義的比較
# self defined torch.manual_seed(0)sigmoid = Sigmoid() # SelfDefinedSigmoid inp = torch.randn(5, requires_grad=True) out = sigmoid((inp + 1).pow(2))print(f'Out is\n{out}')out.backward(torch.ones_like(inp), retain_graph=True) print(f"\nFirst call\n{inp.grad}")out.backward(torch.ones_like(inp), retain_graph=True) print(f"\nSecond call\n{inp.grad}")inp.grad.zero_() out.backward(torch.ones_like(inp), retain_graph=True) print(f"\nCall after zeroing gradients\n{inp.grad}") Out is tensor([0.9984, 0.6223, 0.8005, 0.9213, 0.5018],grad_fn=<SelfDefinedSigmoidBackward>)First call tensor([ 0.0080, 0.3322, -0.3765, 0.2275, -0.0423])Second call tensor([ 0.0159, 0.6643, -0.7530, 0.4549, -0.0845])Call after zeroing gradients tensor([ 0.0080, 0.3322, -0.3765, 0.2275, -0.0423]) # torch defined torch.manual_seed(0) inp = torch.randn(5, requires_grad=True) out = torch.sigmoid((inp + 1).pow(2))print(f'Out is\n{out}')out.backward(torch.ones_like(inp), retain_graph=True) print(f"\nFirst call\n{inp.grad}")out.backward(torch.ones_like(inp), retain_graph=True) print(f"\nSecond call\n{inp.grad}")inp.grad.zero_() out.backward(torch.ones_like(inp), retain_graph=True) print(f"\nCall after zeroing gradients\n{inp.grad}") Out is tensor([0.9984, 0.6223, 0.8005, 0.9213, 0.5018], grad_fn=<SigmoidBackward>)First call tensor([ 0.0080, 0.3322, -0.3765, 0.2275, -0.0423])Second call tensor([ 0.0159, 0.6643, -0.7530, 0.4549, -0.0845])Call after zeroing gradients tensor([ 0.0080, 0.3322, -0.3765, 0.2275, -0.0423])從上面結果,可以看出與torch定義sigmoid得到是一樣的結果
可視化
# visualization inp = torch.arange(-8, 8, 0.1, requires_grad=True) out = sigmoid(inp) out.sum().backward()inp_grad = inp.gradplt.plot(inp.detach().numpy(),out.detach().numpy(),label=r"$\sigma(x)=\frac{1}{1+e^{-x}} $",alpha=0.7) plt.plot(inp.detach().numpy(),inp_grad.numpy(),label=r"$\sigma'(x)$",alpha=0.5) plt.grid() plt.legend() plt.show()總結
以上是生活随笔為你收集整理的Pytorch 自定义激活函数前向与反向传播 sigmoid的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 由于项目需要 接触使用特别好 亲测使用
- 下一篇: 谷歌翻译,一键复活脚本