パドルベースのコンピューター ビジョン入門チュートリアル - 講義 7 実践: 手書き番号認識

Bステーションチュートリアルアドレス

https://www.bilibili.com/video/BV18b4y1J7a6/

タスクの紹介

手書き数字認識はコンピュータ ビジョンの古典的なプロジェクトですが、手書き数字はランダムであるため、従来のコンピュータ ビジョン技術を使用して数字の共通特徴を見つけることは困難ですコンピュータ ビジョンの初期には、手書きの数字認識が大きな問題になりました。

先ほど説明した視覚タスクの分類から判断すると、手書きの数字認識が代表的な分類タスクであり、 10の分類には画像が入力されます実際には、手書き数字認識にも多くの応用シナリオがあります。以下の図に示すように、郵便番号の認識によって産業の自動化が大幅に促進され、畳み込みニューラル ネットワークを使用することで達成される精度は人間の精度を上回る可能性があることがわかります

このタスクは、手書きの数字の画像を入力することで正しい分類結果を出力できるモデルを構築することです。このような実践的なプロジェクトを通じて、これまでに説明した畳み込みやプーリングなどの一般的な操作を統合して理解するのに役立ち、深層学習の基本プロセスを復習することもできます。

データの準備

手書き数字認識用の一般的なデータ セットMNISTがあり、これにはマークされた数万の手書き数字が含まれており、トレーニング セットと評価セットに分割されています。写真の 1 つを視覚化すると、次のようなことがわかります。

イメージの形状は **(1, 28, 28)で、シングル チャネル イメージ**です。イメージのサイズは28*28のみで、ラベルは 7 です

通常、一般的なプロジェクトでは、データを順番にロードし、画像や注釈を返し、トレーニング用のトレーニング インターフェイスを提供するデータローダーを自分で作成する必要がありますここでエントリーした理由を考慮すると、書かれた API を直接使用します。興味のある学生は、圧縮パッケージをダウンロードして、高度な API を使用せずに自分でデータローダーを作成してみることができます。

train_loader = paddle.io.DataLoader(MNIST(mode='train', transform=ToTensor()), batch_size=10, shuffle=True)
valid_loader = paddle.io.DataLoader(MNIST(mode='test', transform=ToTensor()), batch_size=10)

上記でパッケージ化された API を通じて、トレーニング セット評価セットをロードしました。これらはトレーニング インターフェイスから呼び出すことができます。

ネットワーク構築

データを準備した後の 2 番目のステップは、畳み込みニューラル ネットワークの構築です。畳み込みニューラル ネットワークはモデルの精度に直接影響します。このステップは最も重要なリンクでもあります。今回の実戦ではデフォルトでLeNetを使用します。LeNet は、1998 年に誕生した最も初期の畳み込みニューラル ネットワークの 1 つであり、手書き数字認識タスクで大きな成功を収めています

そのネットワーク構造も非常に単純で、基本的には畳み込み層、その後にプーリング層が続き、最後に 2 つの全結合層を介して [1,10] 行列が出力されます。全結合層はこれまで紹介したことがありませんでしたが、通常は、多数の散在点などのバッチ データを曲線に適合させるために使用されます。その構造は次のとおりです。

つまり、各出力は前の層のすべてのパラメーターに関連しており、その数学的表現は実際には変換行列を乗算し、バイアスを追加して出力行列を取得することになります。畳み込み層が画像で広く使用されているのに、全結合層がめったに使用されないのはなぜですか? ここでは、授業が終わった後に自分で考えてもらいます。

LeNet はPaddleを使用してコードを次のように再現します。

import paddle
import numpy as np
from paddle.nn import Conv2D, MaxPool2D, Linear
import paddle.nn.functional as F

# 定义 LeNet 网络结构
class LeNet(paddle.nn.Layer):
    def __init__(self, num_classes=1):
        super(LeNet, self).__init__()
        self.conv1 = Conv2D(in_channels=1, out_channels=6, kernel_size=5)
        self.max_pool1 = MaxPool2D(kernel_size=2, stride=2)
        self.conv2 = Conv2D(in_channels=6, out_channels=16, kernel_size=5)
        self.max_pool2 = MaxPool2D(kernel_size=2, stride=2)
        self.conv3 = Conv2D(in_channels=16, out_channels=120, kernel_size=4)
        self.fc1 = Linear(in_features=120, out_features=64)
        self.fc2 = Linear(in_features=64, out_features=num_classes)
    def forward(self, x):                        #[N,1,28,28] 
        x = self.conv1(x)                        #[N,6,24,24]
        x = F.sigmoid(x)                         #[N,6,24,24]
        x = self.max_pool1(x)                    #[N,6,12,12]
        x = F.sigmoid(x)                         #[N,6,12,12]
        x = self.conv2(x)                        #[N,16,8,8]
        x = self.max_pool2(x)                    #[N,16,4,4]
        x = self.conv3(x)                        #[N,120,1,1]
        x = paddle.reshape(x, [x.shape[0], -1])  #[N,120]
        x = self.fc1(x)                          #[N,64]
        x = F.sigmoid(x)                         #[N,64]
        x = self.fc2(x)                          #[N,10]
        return x

Paddle が動的グラフを使用する方法は非常に明確です。クラス本体を定義し、初期化関数で使用するレイヤーを記述します。入出力チャネルの数とコンボリューション カーネルのサイズに特に注意を払う必要があります。次元エラー発生しますここで定義した後、画像を渡した後の実際の操作となる forward関数を記述します。

皆さんに理解していただくために、実行プロセスを詳しく説明しましょう。まず、クラス本体をインスタンス化します

model = LeNet(num_classes=10)

インスタンス化されると、クラス本体は自動的に init() 初期化関数 を実行し、 init() 関数はConv2D と MaxPool2Dをインスタンス化します。これらは実際にはクラス本体です。これらのクラス本体は、LeNet と同様に、init() および forward 関数もあります。それに応じて初期化関数でインスタンス化されます。インスタンス化プロセスは実際に計算を開始するわけではなく、使用するレイヤーを定義するだけです。

output = model(img)

上記のコードを再度実行すると、クラス本体を呼び出してimg を入力したことと同じになりますが、このときクラス本体は自動的にcall() 関数を呼び出しますが、なぜ forward 関数が実行されるのでしょうか。その理由は、すべての操作がpaddle.nn.Layer の親クラスを継承するためです。親クラスでは、forward 関数が call() の下に記述されており、これはLeNetクラス本体を呼び出すときに同じです。実際の計算プロセスが始まります

皆さんも納得できるまで繰り返し精査していただきたいと思います。このようなネットワーク構築の形式を連続的にネストできることを見つけるのは難しくありません。これは非常に明確な形式であり、この利点は後で複雑なモデルを説明するときに反映されます。

モデルトレーニング

# -*- coding: utf-8 -*-
# LeNet 识别手写数字
import imp
import paddle
import numpy as np
import paddle
from model import LeNet
from paddle.vision.transforms import ToTensor
from paddle.vision.datasets import MNIST


def train(model, opt, train_loader, valid_loader):
    use_gpu = True
    paddle.device.set_device('gpu:0') if use_gpu else paddle.device.set_device('cpu')
    print('start training ... ')
    model.train()
    for epoch in range(EPOCH_NUM):
        for batch_id, data in enumerate(train_loader()):
            img = data[0]              #[10,1,28,28]
            label = data[1]            #[10,1]
            # 计算模型输出
            logits = model(img)
            # 计算损失函数
            loss_func = paddle.nn.CrossEntropyLoss(reduction='none')
            loss = loss_func(logits, label)
            avg_loss = paddle.mean(loss)

            if batch_id % 500 == 0:
                print("epoch: {}, batch_id: {}, loss is: {:.4f}".format(epoch+1, batch_id, float(avg_loss.numpy())))
            avg_loss.backward()
            opt.step()
            opt.clear_grad()

        model.eval()
        accuracies = []
        losses = []
        for batch_id, data in enumerate(valid_loader()):
            img = data[0]
            label = data[1] 
            # 计算模型输出
            logits = model(img)
            # 计算损失函数
            loss_func = paddle.nn.CrossEntropyLoss(reduction='none')
            loss = loss_func(logits, label)
            acc = paddle.metric.accuracy(logits, label)
            accuracies.append(acc.numpy())
            losses.append(loss.numpy())
        print("[validation] accuracy/loss: {:.4f}/{:.4f}".format(np.mean(accuracies), np.mean(losses)))
        model.train()

    # 保存模型参数
    paddle.save(model.state_dict(), 'mnist.pdparams')


model = LeNet(num_classes=10)
EPOCH_NUM = 5
opt = paddle.optimizer.Momentum(learning_rate=0.001, parameters=model.parameters())
train_loader = paddle.io.DataLoader(MNIST(mode='train', transform=ToTensor()), batch_size=10, shuffle=True)
valid_loader = paddle.io.DataLoader(MNIST(mode='test', transform=ToTensor()), batch_size=10)
train(model, opt, train_loader, valid_loader)

トレーニング コードは、私たちが学んだことに基づいており、非常に明確です。データセットインターフェイスからデータセットを取得し、画像をモデルに入力します。モデルは予測値を取得します。CrossEntropyLoss損失関数を使用して、予測値とラベルの実際の値の間の損失を計算し、損失を元に戻します。ネットワークパラメータを調整し、最後にオプティマイザを使用してパラメータを修正し、損失を削減します。

CrossEntropyLoss 損失関数には Softmax が付属していることに注意してください。分類問題の最後に、出力 [1,10] 行列を [0,1] に戻し、10 個の数値の合計を返すには、softmax 活性化関数が必要です。は 1 です。これは、この画像が 0 ~ 9 である確率を意味します

モデル予測

import numpy as np
import paddle
from model import LeNet
from paddle.vision.datasets import MNIST
from paddle.vision.transforms import ToTensor
import paddle.nn.functional as F

valid_loader = MNIST(mode='test', transform=ToTensor())
img = np.array(valid_loader[0][0])

# import matplotlib.pyplot as plt
# plt.imshow(img.squeeze(), cmap='gray')
# plt.show()

model = LeNet(num_classes=10)
model_dict = paddle.load("mnist.pdparams")
model.set_state_dict(model_dict)
model.eval()
x = valid_loader[0][0].reshape((1,1,28,28)).astype('float32')
result = F.softmax(model(x))
print(result.numpy()[0])

モデルをトレーニングした後、モデルをロードして予測を行う必要があります。ここでは、評価セット内の画像予測を選択して、出力結果が正しいかどうかを確認します。

model = LeNet(num_classes=10)
model_dict = paddle.load("mnist.pdparams")
model.set_state_dict(model_dict)

このメソッドを使用してモデルをロードし、最後に出力を予測します。

[7.3181213e-06 1.4578840e-05 3.3818762e-04 2.1557527e-04 2.6723552e-05 
 6.7271581e-06 1.3456239e-08 9.9840504e-01 4.1231990e-05 9.4459485e-04]

これはそれぞれ0 ~ 9 の確率も表しており、7 の確率は 99.84% と高く、モデルの出力は正しいです。

参考文献

https://www.paddlepaddle.org.cn/tutorials/projectdetail/2227103

おすすめ

転載: blog.csdn.net/weixin_45747759/article/details/122636048