Pytouch逆伝搬における詳細-勾配計算時のデフォルトのアキュムレータ動作


Pytoch逆伝搬計算勾配はデフォルトのアキュムレータです。
今日はpytouchを勉強して簡単なリニア回帰を実現して、pytouchの逆方向伝播を発見しました。勾配を計算するために採用されたアキュムレータメカニズムを発見しました。そこで、多くのブログでアキュムレータメカニズムを説明しましたが、多くはこのアキュムレータメカニズムが一体何の影響があるかを説明していません。
pytochはリニア回帰を実現します。
まずテストコードを添付して感じてみます。

torch.manual_seed(6)
lr = 0.01   #    
result = []

#       
x = torch.rand(20, 1) * 10
y = 2 * x + (5 + torch.randn(20, 1)) 

#         
w = torch.randn((1), requires_grad=True)
b = torch.zeros((1), requires_grad=True)
#        ,   pytorch            ,      
for iteration in range(2):

    #     
    wx = torch.mul(w, x)
    y_pred = torch.add(wx, b)

    #    MSE loss
    loss = (0.5 * (y - y_pred) ** 2).mean()
    
    #     
    loss.backward()
    
    #               
    print("w.grad:", w.grad)
    print("b.grad:", b.grad)
    
    #     
    b.data.sub_(lr * b.grad)
    w.data.sub_(lr * w.grad)
上記のコードは比較的簡単で、2回繰り返しました。計算の勾配結果を見てください。
w.grad:tenssor(-74.6861))
b.grad:tenssor(-12.5532)
w.grad:tenssor([-122.95])
b.grad:tenssor(-20.9364)
それから、私は2行のコードを少し入れます。つまり、逆伝搬の上に、手動で勾配クリア操作のコードを追加して、結果を感じます。

torch.manual_seed(6)
lr = 0.01
result = []
#       
x = torch.rand(20, 1) * 10
#print(x)
y = 2 * x + (5 + torch.randn(20, 1)) 
#print(y)
#         
w = torch.randn((1), requires_grad=True)
#print(w)
b = torch.zeros((1), requires_grad=True)
#print(b)
for iteration in range(2):
    #     
    wx = torch.mul(w, x)
    y_pred = torch.add(wx, b)

    #    MSE loss
    loss = (0.5 * (y - y_pred) ** 2).mean()
    
    #   pytorch     ,      ,                    ,     0
     if iteration > 0: 
        w.grad.data.zero_()
        b.grad.data.zero_()
    
    #     
    loss.backward()
    
    #      
    print("w.grad:", w.grad)
    print("b.grad:", b.grad)
    
    #     
    b.data.sub_(lr * b.grad)
    w.data.sub_(lr * w.grad)
w.grad:tenssor(-74.6861))
b.grad:tenssor(-12.5532)
w.grad:tenssor(-48.2813)
b.grad:tenssor(-8.3831)
上から、pytouchが逆方向に伝播する時、確かにデフォルトで前回求めた勾配を積算しました。前回の勾配が自分の今回の勾配に影響を与えたくないなら、手動でクリアする必要があります。
しかし、手動でゼロをクリアしないとどうなりますか?私は今回の線形回帰試験で、遭遇した結果はloss値の繰り返しの揺れが収束しないことです。以下で感じてみます。

torch.manual_seed(6)
lr = 0.01
result = []
#       
x = torch.rand(20, 1) * 10
#print(x)
y = 2 * x + (5 + torch.randn(20, 1)) 
#print(y)
#         
w = torch.randn((1), requires_grad=True)
#print(w)
b = torch.zeros((1), requires_grad=True)
#print(b)

for iteration in range(1000):
    #     
    wx = torch.mul(w, x)
    y_pred = torch.add(wx, b)

    #    MSE loss
    loss = (0.5 * (y - y_pred) ** 2).mean()
#     print("iteration {}: loss {}".format(iteration, loss))
    result.append(loss)
    
    #   pytorch     ,      ,                    ,     0
    #if iteration > 0: 
    #    w.grad.data.zero_()
    #    b.grad.data.zero_()
  
    #     
    loss.backward()
 
    #     
    b.data.sub_(lr * b.grad)
    w.data.sub_(lr * w.grad)
    
    if loss.data.numpy() < 1:
        break
   plt.plot(result)
上のコードの中で、手動でゼロをクリアしていません。1000回繰り返して、毎回のlossをresultに入れて、画像を描きます。結果を感じてください。
没有进行手动清零
次に、手動でゼロをクリアした注釈を開いて、反復後の手動クリアを行います。
手动清零之后的操作
これこそ理想的な逆伝搬コンダクタンスであり、パラメータを更新して得られるloss値の変化が見られます。
締め括りをつける
今回は主に、pytouchが逆伝搬計算勾配を行っている時のアキュムレータメカニズムはいったいどうなっているかを記録します。なぜこのような仕組みを採用したのかについても調べてみました。

しかし、積算したくないなら、手動でゼロをクリアする方式を採用してもいいです。反復のたびに加算すればいいです。

w.grad.data.zero_()
b.grad.data.zero_()
また、資料を検索する時、ブログで2つの悪くない線形回帰を見た時のpytouchの計算図をここで借ります。
前向传播
反向传播
以上は個人の経験ですので、参考にしていただければと思います。