Lenet 学習ノート pytorch 公式デモコード再現

序文:

参考コンテンツは次からのものです: 2.1 pytorch 公式デモ (Lenet)_bilibili_bilibili

コードとppt: https://github.com/WZMIAOMIAO/deep-learning-for-image-processing

pytorch 公式コード:分類子のトレーニング — PyTorch チュートリアル 1.13.1+cu117 ドキュメント

データセットのダウンロード:

リンク: https://pan.baidu.com/s/1NBHp0SxEOJ5EIyYUsDHm_g

抽出コード:qp3k


導入

pytorch Tensor のソート: [バッチ、チャネル、高さ、幅]、後で Tensor を処理する場合、この順序に従って処理されます。

バッチはバッチ内の画像の数を表します。

チャンネルは画像の大きさを表します。ここで使用されている CIFAR10 はカラー画像なので、チャンネルは 3 です。

LeNet ネットワークは、畳み込み層、ダウンサンプリング、畳み込み層、ダウンサンプリング、および 3 つの完全に接続された層グループで構成されます。

LeNet ネットワークは、1 次元のみのグレースケール イメージを使用します。

デモプロセス

  • model.py - LeNet ネットワーク モデルを定義します。

  • train.py - データ セットをロードしてトレーニングし、トレーニング セットで損失を計算し、テスト セットで精度を計算し、トレーニングされたネットワーク パラメーターを保存します。

  • detect.py——トレーニングされたネットワーク パラメーターを取得した後、見つかった画像を分類テストに使用します

1.model.py

import torch.nn as nn
import torch.nn.functional as F


class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 16, 5)   #分别代表输入特征矩阵的深度,卷积核的个数,卷积核的大小;
        self.pool1 = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(16, 32, 5)
        self.pool2 = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(32*5*5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = F.relu(self.conv1(x))    # input(3, 32, 32) output(16, 28, 28)
        x = self.pool1(x)            # output(16, 14, 14)
        x = F.relu(self.conv2(x))    # output(32, 10, 10)
        x = self.pool2(x)            # output(32, 5, 5)
        x = x.view(-1, 32*5*5)       # output(32*5*5)
        x = F.relu(self.fc1(x))      # output(120)
        x = F.relu(self.fc2(x))      # output(84)
        x = self.fc3(x)              # output(10)
        return x

pytorch でモデルを構築する手順:

  • 親クラス nn.Module を継承するクラスを定義します。

  • この中に __init__ 関数と forward 関数という 2 つのメソッドを実装します。

  • 初期化関数 __init__ は、初期化関数でネットワークを構築するために必要なネットワーク層構造を実装します。

  • 順伝播の処理は forward 関数で定義されており、このクラスをインスタンス化した後、このインスタンスにパラメータを渡すと順伝播が行われます。

LeNet を構築する手順:

  • 親クラス nn.Module を継承するクラス LeNet を定義します。

初期化関数を定義します。

  • __init__ 初期化関数をその中に実装し、その中でスーパー関数を使用します。スーパー関数は、親クラスのメソッドを呼び出すときに発生する可能性のある問題を解決できます。多重継承が関係する場合は、通常、スーパー関数が使用されます。

  • pytroch で畳み込み層を定義することは、nn.Conv2d 関数によって畳み込み層を定義することです (3、16、5) はそれぞれ入力特徴行列の深さ、畳み込みカーネルの数、畳み込みカーネルのサイズを表します。計算後、出力(16、28、28)を取得します。計算式は次のとおりです。

  • ダウンサンプリング操作を定義し、nn.MaxPool2d メソッドを使用します。(2, 2) は、プーリング合計 2、ストライド 2 を使用し、高さと幅を元の半分に縮小して、出力 (16, 14, 14);

  • 2 番目の畳み込み層を定義します。(16, 32, 5) は、入力特徴行列の深さが 16、畳み込みカーネルの数が 32、畳み込みカーネルのサイズが 5 であることを意味します。計算後の出力は、 (32, 10, 10 );

  • 2 番目のダウンサンプリング層を定義し、nn.MaxPool2d メソッドを使用してそれを (2, 2) として定義し、高さと幅を元の半分に縮小して、出力 (32, 5, 5) を取得します。

  • 全結合層を定義します。全結合層の入力は 1 次元ベクトルであるため、得られた特徴行列を1 次元ベクトルに平坦化する必要があります。最初の全結合層の入力ノードの数は (32) *5* 5)、同時にノード数を 120 に設定します。

  • 2 番目の全結合層を定義します。入力は前の層の出力 120、出力は 84 です。

  • 3 番目の全結合層を定義します。入力は前の層の出力 (84) です。出力はトレーニング セットに従って変更する必要があります。ここで使用される CIFAR10 はデータのカテゴリであるため、出力は 10 です。

順伝播プロセスは次のように定義されます。

  • X は、Tensor のソートである入力データを表します: [バッチ、チャネル、高さ、幅]

  • データを畳み込み層 1 に渡し、取得したデータを Relu アクティベーション関数に渡します。

  • 出力を最初のダウンサンプリングに渡します。

  • 出力を 2 番目の畳み込み層に渡し、取得したデータを Relu 活性化関数に渡します。

  • 出力を 2 番目のダウンサンプリングに渡してから、平坦化する必要がある完全に接続されたレイヤーと結合します。

  • フラット化はビュー関数 (-1, 32*5*5) によって実行されます。-1 は最初の次元を表します。最初の次元はバッチであるため、-1 に設定され、2 番目の次元はフラット化されます。ノード数、一次元データが得られます。

  • 完全に接続されたレイヤー 1 とアクティベーション機能を介してデータを渡します。

  • 完全に接続されたレイヤー 2 とアクティベーション機能を介してデータを渡します。

  • 完全に接続されたレイヤー 3 にデータを渡して、最終出力を取得します。

1.1 変換 2d

関数定義で使用されるパラメータの定義:

def __init__(
        self,
        in_channels: int,                   #输入特征矩阵的深度
        out_channels: int,                  #对应卷积核的个数
        kernel_size: _size_2_t,             #卷积核的大小
        stride: _size_2_t = 1,              #步距,默认为0
        padding: Union[str, _size_2_t] = 0, #在四周进行补零处理,默认为0
        dilation: _size_2_t = 1,
        groups: int = 1,
        bias: bool = True,                  #偏置,默认使用
    )

pytorchの公式定義:

dilation のデフォルトは 1 です。次の式と同じ式を入力します。

self.conv1 = nn.Conv2d(3, 16, 5) の畳み込み後の行列サイズを計算します。

W は入力画像のサイズ 32 × 32、F はコンボリューション カーネル サイズ 5、P は 0、S は 1

(32-5)/ 1 + 1 = 28

input(3, 32, 32) は畳み込みを通じて出力 (16, 28, 28) を取得します。16 は畳み込みカーネルの数で、次の層の入力特徴行列の深さになります。

1.2 MaxPool2d

関数定義で使用されるパラメータの定義:

def __init__(self, 
              kernel_size: _size_any_t,                #池化和的大小
              stride: Optional[_size_any_t] = None,    #步距
              padding: _size_any_t = 0, 
              dilation: _size_any_t = 1,
              return_indices: bool = False, 
              ceil_mode: bool = False) -> None:
        super(_MaxPoolNd, self).__init__()
        self.kernel_size = kernel_size
        self.stride = stride if (stride is not None) else kernel_size
        self.padding = padding
        self.dilation = dilation
        self.return_indices = return_indices
        self.ceil_mode = ceil_mode

self.pool1 = nn.MaxPool2d(2, 2) は、プーリング合計が 2 であり、ストライドも 2 であることを意味します。

最大のダウンサンプリングを通じて取得された出力は #output(16, 14, 14) で、高さと幅が元の値の半分に減少します。プーリング操作では、特徴行列の高さと幅のみが変更され、深さは変更されません;

1.3 簡単なテスト: ブレークポイントのデバッグ

左側の出力が設定された入力であることがわかります。

最初のプーリング層の後

2.train.py

パッケージのインポート

import torch
import torchvision
import torch.nn as nn
from model import LeNet
import torch.optim as optim
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
import time

2.1 データの前処理

入力画像データを前処理します。

ToTensor関数数:つまり [0, 255] の範囲のshape (H x W x C) → [0.0, 1.0] の範囲のshape (C x H x W);

注通过PIL或者numpy导入的图片都是 (H x W x C) 格式,必须使用Tensor函数转化为(C x H x W)格式才能使用;

Normalize函数:output[channel] = (input[channel] - mean[channel]) / std[channel];

即输出=(原始数据-均值)/标准差

transform = transforms.Compose(
        [transforms.ToTensor(),
         transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

数据集介绍

利用torchvision.datasets函数可以在线导入pytorch中的数据集,包含一些常见的数据集如MNIST等

导入数据集:通过CIFAR10导入训练集

# 第一次使用时要将download设置为True才会自动去下载数据集
# 导入50000张训练图片
train_set = torchvision.datasets.CIFAR10(root='./data',      # 数据集存放目录
                                         train=True,         # 表示是数据集中的训练集
                                        download=True,       # 第一次运行时为True,下载数据集,下载完成后改为False
                                        transform=transform) # 预处理过程
# 加载训练集,实际过程需要分批次(batch)训练                                        
train_loader = torch.utils.data.DataLoader(train_set,       # 导入的训练集
                                           batch_size=50, # 每批训练的样本数
                                          shuffle=False,  # 是否打乱训练集
                                          num_workers=0)  # 使用线程数,在windows下设置为0

导入测试集

# 导入10000张测试图片
test_set = torchvision.datasets.CIFAR10(root='./data', 
                                        train=False,    # 表示是数据集中的测试集
                                        download=False,transform=transform)
# 加载测试集
test_loader = torch.utils.data.DataLoader(test_set, 
                                          batch_size=10000, # 每批用于验证的样本数
                                          shuffle=False, num_workers=0)
# 获取测试集中的图像和标签,用于accuracy计算
test_data_iter = iter(test_loader)
test_image, test_label = test_data_iter.next()

数据处理:

val_data_iter = iter(val_loader)             #iter将val_loader变为可迭代的迭代器
val_image, val_label = next(val_data_iter)   #next可得到一批数据,即图像和标签
    
classes = ('plane', 'car', 'bird', 'cat',    #导入标签,元组类型(值不能改变)
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

2.2 训练过程
    net = LeNet()                                                    #实例化模型
    loss_function = nn.CrossEntropyLoss()                            #定义损失函数,这个函数已经包含softmax函数,所以网络的最后一层不再使用
    optimizer = optim.Adam(net.parameters(), lr=0.001)                    #定义优化器,Adam优化器,第一个参数是需要训练的参数,将lenet可训练的参数都进行训练,lr是学习率

    for epoch in range(5):  # loop over the dataset multiple times        #将训练集训练多少次

        running_loss = 0.0                                                #累加训练过程中的损失
        for step, data in enumerate(train_loader, start=0):               #通过循环遍历训练集样本,通过enumerate函数,不仅返回数据,还可以返回每一批数据的步数
            # get the inputs; data is a list of [inputs, labels]
            inputs, labels = data                                         #将数据分成输入和标签

            # zero the parameter gradients
            optimizer.zero_grad()                                         #将历史损失梯度清零
            # forward + backward + optimize
            outputs = net(inputs)                                         #将输入的图片进行正向传播
            loss = loss_function(outputs, labels)                         #outputs是网络的预测值,labels是输入图片对应的真实标签
            loss.backward()                                               #反向传播
            optimizer.step()                                              #参数的更新

            # print statistics
            running_loss += loss.item()                                   #将损失累加
            if step % 500 == 499:    # print every 500 mini-batches       #每隔500步打印一次数据信息
                with torch.no_grad():                                     #with是一个上下文管理器,torch.no_grad表示在接下来不要计算误差损失梯度
                    outputs = net(val_image)  # [batch, 10]               #进行正向传播

                    predict_y = torch.max(outputs, dim=1)[1]              #寻找最大的index在哪里,[batch, 10] 第0个维度是batch,第一个维度是输出,[1]是索引,得到最大值对应的标签类别

                    accuracy = torch.eq(predict_y, val_label).sum().item() / val_label.size(0)            
# 预测标签类别和真实标签类别比较,在相同的地方返回1  true,.sum()计算在预测过程中预测对了多少样本,得到结果是tensor,通过.item()拿到数值,在除以训练样本的数目

                    print('[%d, %5d] train_loss: %.3f  test_accuracy: %.3f' %
                          (epoch + 1, step + 1, running_loss / 500, accuracy))
                                         # %d=epoch,%5d是某一轮的多少步, %.3f训练过程中累加的误差
                    running_loss = 0.0   #清零,进行下一次的500步

    print('Finished Training')

    save_path = './Lenet.pth'
    torch.save(net.state_dict(), save_path)                               #保存模型

打印信息如下:

[1,   500] train_loss: 1.779  test_accuracy: 0.451
[1,  1000] train_loss: 1.435  test_accuracy: 0.527
[2,   500] train_loss: 1.230  test_accuracy: 0.578
[2,  1000] train_loss: 1.177  test_accuracy: 0.613
[3,   500] train_loss: 1.053  test_accuracy: 0.632
[3,  1000] train_loss: 1.034  test_accuracy: 0.635
[4,   500] train_loss: 0.943  test_accuracy: 0.651
[4,  1000] train_loss: 0.951  test_accuracy: 0.666
[5,   500] train_loss: 0.846  test_accuracy: 0.668
[5,  1000] train_loss: 0.856  test_accuracy: 0.666
Finished Training

3. predict.py

import torch
import torchvision.transforms as transforms
from PIL import Image

from model import LeNet


def main():
    transform = transforms.Compose(
        [transforms.Resize((32, 32)),           #缩放图片 
         transforms.ToTensor(),                 #转化为tensor
         transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])    #标准化处理

    classes = ('plane', 'car', 'bird', 'cat',
               'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

    net = LeNet()                                                           #实例化LeNet
    net.load_state_dict(torch.load('Lenet.pth'))                            #载入权重文件

    im = Image.open('1.jpg')                                                #使用Image模块载入图片
    im = transform(im)                                         # [C, H, W],通过PIL载入的图片是高度,宽度,深度,必须变为tensor格式才能进行正向传播,进行预处理,得到[C, H, W]格式
    im = torch.unsqueeze(im, dim=0)  # [N, C, H, W],          #使用unsqueeze增加一个维度

    with torch.no_grad():
        outputs = net(im)                                                   #图像传入网络
        predict = torch.max(outputs, dim=1)[1].numpy()                      #寻找输出中最大的index
    print(classes[int(predict)])                                            #传入classes


if __name__ == '__main__':
    main()

输出即为预测的标签

预测结果也可以用 softmax 表示,输出10个概率:

with torch.no_grad():
    outputs = net(im)
    predict = torch.softmax(outputs, dim=1)
print(predict)

输出结果中最大概率值对应的索引即为预测标签的索引

tensor([[8.9619e-01, 8.9764e-04, 5.7712e-02, 4.5166e-03, 1.2172e-02, 9.6238e-04,
         7.4281e-04, 1.6205e-04, 2.4677e-02, 1.9665e-03]])

おすすめ

転載: blog.csdn.net/weixin_45897172/article/details/128550035