1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Pytorchの機械学習のあれこれ

Last updated at Posted at 2020-12-17

#はじめに
こちら ~会社の業務をRPAで効率化した話~の記事でわざわざ作成したニューラルネットワークについての記事です!
あまり効率のよいコーディングはしていませんが、学習までの流れのわかりやすさを重視しました!

#ネットワークをわざわざ作った経緯
PythonではTesseract OCRを使用すればPCの画面上の文字や数字を読み取ることが可能ですが、いろいろと都合が悪かったためニューラルネットワークを作成しました...LoL
###Tesseractの都合が悪いこと

  • パラメーターをいじっても誤認識がそこそこあった
  • いじくりまわせない
  • Pyinstallerでexe化したときにexeファイルがかなり肥大する
  • exe化後の問題が多発

シンクライアント端末でPythonやTesseractがインストールできないためexe化するときに一緒にパッケージングしてあげなければならず、Pyinstaller周りで非常に面倒で、数字認識しかしないのでネットワークを作成しました。

#開発環境

  • iMac 2019モデル Big sur (Boot Campでwindows10を使用)
  • Google Colaboratry

#本編
##データセットの準備

digital_data = np.arange(47040000).reshape(60000,1,28,28)      #(28x28)が60000枚 60000x28x28=47040000
digital_ans = []
for i in range(10000):
    img = Image.open('0.png')                                 #デジタルデータ
    img = ImageOps.invert(img.convert('L')).resize((28,28))   #8bitのグレイスケールに変換して(28x28)にリサイズ
    data = np.array(img,np.float32).reshape(1,28,28)          #imgをfloat型にし、次元を増やして代入
    for n in range(28):
        digital_data[i][0][n] = data[0][n]                    #1行ずつ代入していく
    digital_ans.append(0)                                     #答えも同時に入れていく

まずはpngデータをnumpy配列に落とし込みます。
今回はデジタル数字を使用しましたが、ほかの画像も使用できるかと思います。
digit_dataの3,4次元の値を任意のピクセル数に変え、for文内のresize(),reshapeを同じ値に変えれば可能です!
digit_ansはそのままでも可能です。0->ピカ〇ュウ,1->ヒト〇ゲのように定義しておけば問題ありません。
##importしたもの

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision.transforms as transforms
from torch.utils.data import DataLoader

import numpy as np

import random
from PIL import Image, ImageOps

import pickle
ライブラリ 特徴
os OS依存の機能を利用するためのライブラリ
torch ニューラルネットワークを簡単に構築するフレームワーク
numpy 行列計算や次元を操るライブラリ
random ランダムで値をつくれるようになるライブラリ
PIL 画像処理を簡単に行うライブラリ
pickle 学習したニューラルネットをファイルとして保存するライブラリ
##おまじない
if torch.cuda.is_available():
    device = 'cuda'
else:
    device = 'cpu'
print('Device:',device)

Google ColaboratryではGPUが使用可能です。GPUが使用な環境ではできるだけGPUを使用すること強くお勧めします!
GPUのほうが学習時間が短く効率が良いです。しかし、学習結果をGPUが使用できない環境で用いる場合はニューラルネットワークがGPUと依存関係をもってしまうためCPUで学習しなければなりません。
##学習データと訓練データの振り分け

rand_idx = list(range(60000))                       #0~59999の数を生成
rand_idx = random.sample(rand_idx,len(rand_idx))    #rand_idxをシャッフルする  random.sampleで重複なしで取得できる

#訓練:テスト=8:2
rand_train = rand_idx[:48000]                       #0~47999までのインデックス
rand_test = rand_idx[48000:60000]                   #48000~59999までのインデックス

#各データ配列の初期化
x_dig_train = np.arange(37632000).reshape(48000,1,28,28)    #訓練用デジタルデータ
x_dig_test = np.arange(9408000).reshape(12000,1,28,28)      #テスト用デジタルデータ
t_dig_train = np.arange(48000)                              #訓練用の答え
t_dig_test  = np.arange(12000)                              #訓練用の答え

#各numpy配列にinsertしていく
for i in rand_train:
    np.insert(x_dig_train,count,digital_data[i][0][0])
    np.insert(t_dig_train,count,digital_ans[i])
    count += 1
for i in rand_test:
    np.insert(x_dig_test,count,digi_data[i][0][0])
    np.insert(t_dig_test,count,digi_ans[i])
    count += 1

##ネットワークの構築

class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1,32,3)
        self.pool = nn.MaxPool2d(2,2)
        self.conv2 = nn.Conv2d(32,64,3)
        self.fc1 = nn.Linear(64*5*5,800)
        self.dropout = nn.Dropout(p=0.5)
        self.fc2 = nn.Linear(800,10)
    
    def forward(self,x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1,64*5*5)
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = F.relu(self.fc2(x))
        return x
説明
nn.Conv2d 2次元畳み込み層 引数(サンプル数: int,チャネル数: int,フィルタサイズ: tuple(正方形ならint))
nn.MaxPool2d 2次元マックスプーリング層 引数(フィルタサイズ:tuple(正方形ならint),ストラド:tuple(正方形ならint))
nn.Linear 全結合層 引数(直前のニューロンの数,結合後のニューロンの数)
nn.Dropout ドロップアウト層 引数(p: float) default p=0.5
F.relu 出力層  引数(直前の隠れ層)

###畳み込みから全結合層へ入る前の計算方法

畳み込み層

H = 入出力画像の高さ\\
W = 入出力画像の幅\\
\\
H_{out} = \lfloor \frac{H_{in}+2*padding[0]-dilation[0]*(kernelSize[0]-1)-1}{stride[0]} + 1 \rfloor\\
\\
W_{out} = \lfloor \frac{W_{in}+2*padding[1]-dilation[1]*(kernelSize[1]-1)-1}{stride[1]} + 1 \rfloor
\\ 

入力画像が正方形で**【stride,padding,dilation】=【1,0,1】(デフォルト値)フィルターも正方形**なら
一般に,

X=入力画像の高さ、幅\\
H_{out}=W_{out}=\lfloor X-kernelSize+1 \rfloor

プーリング層

H = 入出力画像の高さ\\
W = 入出力画像の幅\\
\\
H_{out} = \lfloor \frac{H_{in}+2*padding[0]-dilation[0]*(kernelSize[0]-1)-1}{stride[0]} + 1 \rfloor\\
\\
W_{out} = \lfloor \frac{W_{in}+2*padding[1]-dilation[1]*(kernelSize[1]-1)-1}{stride[1]} + 1 \rfloor!
\\ 

畳み込み層とプーリング層の計算式は同じ!!

####例
NN.001.png

16x16の入力画像に対して、畳み込み層Conv2d(1,32,3)をかませると、上の式より、14x14の出力が32枚になります。
その32枚に対してプーリング層Maxpool2d(2,2)をすると7x7の出力が32枚になります。
よって、全結合層に入るときは、32x7x7になるというわけです!

##学習スタート

net = Net()                                 #ニューラルネットのインスタンス生成
net.to(device)                              #CPUまたはGPUの切り替え

loss_func = nn.CrossEntropyLoss()           #損失関数の定義

optimizer = optim.Adam(net.parameters())    #最適化アルゴリズムの選定

record_loss_train = []                      #訓練時の損失関数の値を格納
record_loss_test = []                       #テスト時の損失関数の値を格納

n_epoch = 30                                #エポック数(=学習回数)
batch_size = 1500                           #バッチ数(どれだけまとめて学習するか)

#*******************ここから学習スタート*******************#
for epoch in range(n_epoch):
    net.train()                                                                # トレーニングモード
    loss_train = 0
    perm = np.random.permutation(len(x_dig_train[0]))                          #トレーニングデータのindexをシャッフルした配列を生成
    for i in range(0, len(x_dig_train[0]), batch_size):
        x = torch.from_numpy(x_dig_train[perm[i:i+batch_size]]).to(device)     #配列permからbatch_size分とってくる
        t = torch.from_numpy(t_dig_train[perm[i:i+batch_size]]).to(device)
        y = net(x)                                                             #Net()のfowardが開始される
        loss = loss_func(y,t)
        loss_train += loss.item()
        optimizer.zero_grad()                                                  #勾配をクリアしてモデルを再計算できるようにする
        loss.backward()                                                        #誤差を逆伝播させる
        optimizer.step()                                                       #パラメーターを更新する
    loss_train /= i+1
    record_loss_train.append(loss_train)

    net.eval()                                                                 # 評価モード
    y_test = net(x)
    loss_test = loss_func(y_test, t).item()
    record_loss_test.append(loss_test)

    if i%1 == 0:
        print("Epoch:",epoch, "Loss_Train:", loss_train, "Loss_Test:", loss_test)

今回はミニバッチ学習をしました。画像分類の時の損失関数は交差エントロピーを使用することが多いのでそのまま交差エントロピーを使用しました。また、最適化関数はadamを使用しましたが、最適化アルゴリズムはたくさんあり、パラメーター次第で変わってくるのでネットワークに適した最適化アルゴリズムを選択すると良いと思います!
perm = np.random.permutation(len(x_dig_train[0])) ここは個人的に少し考えたところなので解説しときます。

perm = np.random.permutation(range(10))

実行結果
array([6, 4, 2, 7, 0, 9, 5, 1, 8, 3])

このように引数に与えられた数をシャッフルして新しい配列を生成する機能になっています。引数をlen(x_dig_train[0])とすることで0~47999(トレーニングの画像枚数)の数字がランダムに入った配列が生成されます。そして配列permがスライスで1500個選ばれx_dig_trainのindexになるわけです!

正直言うと、この辺りは理解しきらなくても、画像の枚数とかさえ合っていれば動きますのでおまじない的に使用することも可能です

##学習済みのモデルについて
pickleを用いた保存方法と使用方法を紹介します!
####保存

filename = 'digit_Learn.sav'
pickle.dump(net,open(filename,'wb'))

たった2行でモデルの保存が可能です。
####読み込み

loaded_model = pickle.load(open(filename,'rb'))

これで保存したモデルが使用可能になります!
####別のファイルで使用する方法
#####~手順~

  1. ニューラルネットのクラスをコピペする
  2. CPU,GPUの選択
  3. モデルをロードする
smaple.py
#ニューラルネットコピー
class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1,32,3)
        self.pool = nn.MaxPool2d(2,2)
        self.conv2 = nn.Conv2d(32,64,3)
        self.fc1 = nn.Linear(64*5*5,800)
        self.dropout = nn.Dropout(p=0.5)
        self.fc2 = nn.Linear(800,10)
    
    def forward(self,x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1,64*5*5)
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = F.relu(self.fc2(x))
        return x

#CPU,GPUの選択
if torch.cuda.is_available():
    device = 'cuda'
else:
    device = 'cpu'

#モデルのロード
loaded_model = pickle.load(open('digit_Learn.sav','rb'))

#予測結果を受け取る
output = loaded_model.forward(torch.from_numpy("イメージデータ").to(device)) 
predic = output.argmax()    #最も予測値の高いものを返す

これで別のファイルでも学習済みモデルが使用可能になります。
loaded_model.forward(torch.from_numpy("イメージデータ").to(device))の"イメージデータ"**学習したときの次元と合わせる必要があります。**この記事の場合、reshape(1,1,28,28)の四次元にしてforwardに渡してあげます!!

##最後に
非常に簡単にではありますが、Pytorchを使用した機械学習についての解説でした。この記事を書いて、今まで曖昧だった部分も詳しく調べ理解することができたのでよかったです!Pytorchはいろいろな機能が用意されているのでそれを使用してもっと遊んでみたいです☺

~参考文献~
PyTorch documentation

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?