例による Pytorch に基づく CNN の詳細な分析

I.はじめに

1) 免責事項

国際的な慣例によれば、最初の免責事項: この記事は、畳み込みニューラル ネットワークの学習についての私個人の理解にすぎません。内容には多くの不正確さ、さらには間違いさえあります。私は批判し、修正し、一緒に進歩することを望んでいます。

2) 私自身の理解を少し書きます

私の学習に対する姿勢は常に「歩くことを学ぶ前に、まず走ることを学び、それから歩くことを学ぶ」というものでした。CNN やディープラーニング理論については、インターネット上で多くの学習教材やトレーニング コースを見つけることができますが、なぜ私がそれを知っているのかわかりませんし、最初に思いついたときは多くの複雑な理論について話しましたが、CNN の本質を理解していませんでした。
畳み込みニューラル ネットワーク、またはあらゆる種類のニューロン ネットワーク モデルは、本質的に、目的の目的関数を取得するための複数の非線形演算の線形結合であり、特定の入力後に正しい (正確に近い) 出力を取得できます。その性質は複雑ではありません。
私たちが目にする多くの深層学習ネットワーク モデルが非常に複雑である理由は、宮殿が非常に複雑であるのと同じように、解決すべき実際の問題が非常に複雑であるためですが、その本質は単純なレンガで積み上げられています。
この記事の主な目的は、単純化された例を使用して畳み込みニューロン ネットワーク (CNN) を深く学習することです。

3) この記事を読む前に知っておくべきこと

この記事では、畳み込みニューラル ネットワーク理論の詳細については繰り返しません。この記事を理解するには、最も基本的な畳み込み層の操作を理解し、基本的な Pytorch プログラミングの基礎を持っていることだけが必要です。

2. 例

1) どのような問題を解決するか

次のような畳み込みニューラル ネットワークを構築したいとします。 与えられた 3×3 行列について、行列内のすべての要素が同じである場合 (
[6, 6, 6]
[6, 6, 6]
[6, 6]など) , 6 ]
の場合、「1」(True) が出力されます。すべての要素が異なる場合(乱数)、「0」(False)を出力します。

2) CNN ネットワーク モデルのトレーニング データ

トレーニング セット データには、-50 から 49 までの 100 個の真のサンプル (Train_set_true) が含まれています:
[-50, -50, -50] [-49, -49, -49] [-48, -48, -48] …[ 0 、0、0] ... [49、49、49]
[-50、-50、-50] [-49、-49、-49] [-48、-48、-48] ... [0 、0、0] ...[49、49、49]
[-50、-50、-50] [-49、-49、-49] [-48、-48、-48] ... [0 , 0, 0] ... [ 49, 49, 49]
torch.rand() を使用して 100 個の 3×3 行列をランダムに生成する偽のサンプル (Train_set_false) もあります。

3) CNNネットワークモデル

  • モデル構造:畳み込み層(ストライド1、カーネル2×2) + LeakyReLu+畳み込み層(ストライド1、カーネル2×2) + Sigmoid。問題は複雑ではないため、非常に単純なモデル構造を構築するだけで実現できます。
  • 損失関数: この記事の例は、BCE (Binary CrossEntropy) 損失関数を使用した典型的な二値分類問題 (かどうかを判定する) です。この例には 2 つの損失関数があります: true と判定される損失関数 (loss_true) と、真と判定される損失関数 (loss_true) falseと判定された損失関数(loss_false);
  • 最適化関数: Adadelta (さまざまな最適化関数の紹介に関する記事をお勧めします:最適化関数の紹介。ここで Adadelta を選択する理由。実際、他の最適化関数の推定結果は似ています。この例は非常に単純すぎるためです)

もちろん、実際にはこのような単純な CNN アプリケーションは存在しませんが、この例は実際の問題を抽象化したものとみなすことができます。200 個の入力トレーニング セット行列は 200 枚の現実の写真とみなすことができ、すべての要素が等しい 100 個の行列は写真内に「歩行者」が写っている写真とみなすことができ、ランダムに生成された 100 個の行列は、Zuo が存在しないものとして見ることができます。 「歩行者」の写真。私たちがしなければならないのは、写真に「歩行者」が写っている可能性を判断できる畳み込みニューラル ネットワーク モデルを構築することだけです。

3. Pytorch に基づく CNN ネットワーク モデル コード

最後に添付されている

4 番目、詳細な分析

1) 学習率とエポックをどのように決定するか?

この記事の冒頭で、CNN ネットワーク モデルが実際にはエンジニアリング アプリケーションに近いことを説明しました。学習率とエポックを決定する理論的な方法はありません。ただの試練だと思います。
以下は学習率=0.5、エポック=200の結果です(青が真と判定された損失関数(loss_true)、赤が偽と判定された損失関数(loss_false))
ここに画像の説明を挿入

2) 重みがどのように見えるかを確認します

.state_dict() 関数を使用して 20 エポックごとの重みを出力すると、各畳み込み層の重みとバイアスを確認できます。(スペースを節約するために、ここでは最後の重みパラメータのみがリストされています)


OrderedDict([('model.0.weight', tensor([[[[-0.4180,  0.8206],
          [ 0.5418, -0.9463]]]])), ('model.0.bias', tensor([0.0090])), ('model.2.weight', tensor([[[[-1.0951, -0.4438],
          [-0.7047, -0.8174]]]])), ('model.2.bias', tensor([6.7611]))])

実際のネットワーク モデルの場合、重みファイルは非常に大きくなり、「見る」ものは何もありません。ただし、この単純なモデルでは、重量値を印刷したり、手動で計算したりすることもできます。
この例の重みから、メイン関数が畳み込みの最初の層であり、カーネルの 4 つの要素の合計が約 0 であることがわかります。元のデータ (畳み込み行列) の要素が類似している場合、畳み込みの第 1 層 積の出力はほぼ [0] ですが、畳み込みの第 2 層は比較的大きなバイアスを加えてシグモイド後は約 1 になります。元のデータ要素の偏差が大きい場合 (ランダムに生成される)、畳み込みの最初の層は高い確率で正の値を生成し (最初の畳み込み層のバイアスが正であるため)、畳み込みの 2 番目の層のカーネル要素は、高い確率で正の値を生成します。すべて絶対値です。負の値が大きい場合、畳み込み演算後の出力も負の値が大きいため、シグモイド後の出力は基本的に0となります。

3)理解loss.backward()

逆伝播は実際には関数から変数への偏導関数であり、簡単な例を作ることで理解できます。

import torch

a = torch.tensor([1],dtype=float,requires_grad=True)
b = torch.tensor([4],dtype=float,requires_grad=True)
c = a**3 + b
c.backward()

print(a.grad)
print(b.grad)
------------------------------输出------------------------------------
tensor([3.], dtype=torch.float64)
tensor([1.], dtype=torch.float64)

損失関数の偏導関数は、次のステップ .step() 操作を与えることです (重みを最小損失結果に近づけます)。

4)理解optimize.step()

前のステップで取得した偏微分値を使用して重み値を更新します。更新方法は、確率的勾配降下法 (SGD) を例として、最適化関数のタイプ (SGD、Momentum、Adams...) に関連しています。

  • 得られた偏導関数が負の場合、ステップ サイズを追加します。得られた偏導関数が正の場合は、ステップ サイズを小さくします。
  • ステップサイズ = 学習率 × 偏導関数の絶対値

高校数学の知識

5) なぜ zero_grad() を使用するのですか?

初心者にとっては非常にわかりにくい操作ですが、説明も簡単です。前述したように、.step()は「前のステップで得られた偏微分値を使って重み値を更新する」というものなので、.stepの前に複数の偏微分値(.backward())があった場合はどうなるでしょうか。 ()管理しますか?最新のものを取りますか?
実際、 .step() は上記の偏導関数をすべて取得してそれらを合計し、その後 .step() 操作を実行しますが、これは明らかに私たちが望んでいることではありません。したがって、必要な偏導関数値以外の他の偏導関数値をクリアするには、zero_grad() を使用する必要があります (これは少し複雑です。つまり、前の逆変換で取得した値を削除し、これに従って重みのみを更新します)偏導関数値)

zero_grad() をコメントアウトして、損失が収束するかどうかを確認してください。

最後にソースコードを添付します

import torch
import matplotlib.pyplot as plt
import numpy
import codecs

class CNN(torch.nn.Module):

    def __init__(self):
        super(CNN,self).__init__()
        self.model = torch.nn.Sequential(

            torch.nn.Conv2d(in_channels=1, out_channels=1, kernel_size=2),
            torch.nn.LeakyReLU(),

            torch.nn.Conv2d(in_channels=1, out_channels=1, kernel_size=2),
            torch.nn.Sigmoid()   #最后一个要用sigmoid,否则loss的输入可能不在0~1之间
        )

    def forward(self, x):
        return self.model(x)

cnn = CNN()



train_base = numpy.ones([1,1,3,3])
train_set_true = torch.tensor([i*train_base for i in range(-50,50,1)])  #生成(batch=100,in_channel=1,out_channel=1,3,3)的训练数据集,这些都是真样本,符合规律
train_set_false = torch.rand(100,1,1,3,3)*100-50  #生成(batch=100,in_channel=1,out_channel=1,3,3)的训练数据集,这些都是假样本,都是噪声点

train_set_true = train_set_true.to(torch.float64)
train_set_false = train_set_false.to(torch.float64)

train_target_true = torch.ones(1,1,1,1)  #生成训练目标,真目标为1
train_target_false = torch.zeros(1,1,1,1)  #生成训练目标,假目标为0

cnn_loss = torch.nn.BCELoss()   #定义损失函数为二分交叉熵

cnn_opt = torch.optim.Adadelta(cnn.parameters(),lr=0.5)   #定义cnn权重的优化函数为adadelta

epoch = 200

for i in range(epoch):

    for iteration,train_sample_true in enumerate(train_set_true):

        cnn_opt.zero_grad()   #训练的过程通常使用mini-batch方法,所以如果不将梯度清零的话,梯度会与上一个batch的数据相关,因此该函数要写在反向传播和梯度下降之前。
        cnn_output_true = cnn(train_sample_true.to(torch.float32))
        loss_true = cnn_loss(cnn_output_true, train_target_true)

        loss_true.backward()
        cnn_opt.step() #optimizer.step()函数的作用是执行一次优化步骤,通过梯度下降法来更新参数的值。因为梯度下降是基于梯度的,所以在执行optimizer.step()函数前应先执行loss.backward()函数来计算梯度。

    for iteration, train_sample_false in enumerate(train_set_false):

        cnn_opt.zero_grad()
        cnn_output_false = cnn(train_sample_false.to(torch.float32))
        loss_false = cnn_loss(cnn_output_false, train_target_false)

        loss_false.backward()
        cnn_opt.step()


    if i%5 == 0 :
        loss_true_numpy = loss_true.detach().numpy()
        loss_false_numpy = loss_false.detach().numpy()
        plt.scatter(i, loss_true_numpy, c='b')
        plt.scatter(i, loss_false_numpy, c='r')

    if i%20 == 0:
        print(cnn.state_dict())

plt.show()



if __name__ == '__main__':
    print(cnn(torch.tensor([[[[200,200,200],[200,200,200],[200,200,200]]]]).to(torch.float32)))
    print(cnn(torch.tensor([[[[11.11,11.11,11.11],[11.11,11.11,11.11],[11.11,11.11,11.11]]]]).to(torch.float32)))


    print(cnn(torch.tensor([[[[10,-43,7],[49,50,-51],[39,-59,71]]]]).to(torch.float32)))
    print(cnn(torch.tensor([[[[5.6,9.8,100],[12,65,6],[0.7,43,4]]]]).to(torch.float32)))

おすすめ

転載: blog.csdn.net/m0_49963403/article/details/126678362