end0tknr's kipple - 新web写経開発

http://d.hatena.ne.jp/end0tknr/ から移転しました

STL-10データを使用した 敵対的生成ネットワーク ( GAN : Generative Adversarial Networks )

GitHub - miyamotok0105/pytorch_handbook: pytorch_handbook

上記urlの6章を写経。

deep learning による画像生成とは、GAN を使用しているらしい。

f:id:end0tknr:20191022101807p:plain

GANの学習不安定を改善する為、その後、DCGANやLSGANが現れたそうですが、 今回、STL-10データを使用し、LSGAN による画像生成を実施。

#!/usr/local/python3/bin/python3
# -*- coding: utf-8 -*-

import os
# ↓ $ sudo /usr/local/python3/bin/pip install google.colab
from google.colab import drive
import random
import numpy as np
# import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data
import torchvision.datasets as dset
import torchvision.transforms as transforms
import torchvision.utils as vutils
from PIL import Image

# 本src内にcopyしました
# from net import weights_init, Generator, Discriminator

# import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('Agg')
import matplotlib.pylab as plt

# google colaboratory なら google drive を mount 可
gdrive_mount_point = None
gdrive_path = None
#gdrive_mount_point = '/content/gdrive'
#gdrive_path = \
#   '/content/gdrive/My Drive/Colab Notebooks/pytorch_handbook/chapter6/'

workers = 2
batch_size=50
nz = 100
nch_g = 64
nch_d = 64
n_epoch = 200
lr = 0.0002
beta1 = 0.5
outf = './result_lsgan' # LSGANにより作成された画像の保存先
display_interval = 100

device = 'cuda' if torch.cuda.is_available() else 'cpu'


def main():
    print("DEVICE:", device)

    avoid_Imabe_colab_bug()
    mount_and_cd_gdrive()
    make_output_dir()

    random.seed(0)  # 乱数seed固定 (目的は理解していません)
    np.random.seed(0)
    torch.manual_seed(0)

    (dataloader) = load_train_and_test_data()

    (netG,netD) = make_generator_and_discriminator()     # 贋作生成器と識別器
    (optimizerG,optimizerD) = make_optimizer(netG,netD)  # optimizer

    criterion = nn.MSELoss()    # 損失関数は平均二乗誤差損失
    # 確認用の固定したノイズ
    fixed_noise = torch.randn(batch_size, nz, 1, 1, device=device)

    # LSGAN の学習が 1 epoch 進む毎に画像fileを自動保存します
    train_lsgan(dataloader,netG,netD,optimizerD,optimizerG,criterion,fixed_noise)

def make_optimizer(netG,netD):
    optimizerG = optim.Adam(netG.parameters(),
                            lr=lr,
                            betas=(beta1, 0.999),
                            weight_decay=1e-5) 
    optimizerD = optim.Adam(netD.parameters(),
                            lr=lr,
                            betas=(beta1, 0.999),
                            weight_decay=1e-5)
    return optimizerG,optimizerD

def train_lsgan(dataloader,netG,netD,optimizerD,optimizerG,criterion,fixed_noise):
    # 学習のループ
    for epoch in range(n_epoch):
        for itr, data in enumerate(dataloader):
            real_image = data[0].to(device)     # 元画像
            sample_size = real_image.size(0)    # 画像枚数
            
            # 正規分布からノイズを生成
            noise = torch.randn(sample_size, nz, 1, 1, device=device)
            # 元画像に対する識別信号の目標値「1」
            real_target = torch.full((sample_size,), 1., device=device)
            # 贋作画像に対する識別信号の目標値「0」
            fake_target = torch.full((sample_size,), 0., device=device)

            ############################
            # 識別器Dの更新
            ###########################
            netD.zero_grad()    # 勾配の初期化

            output = netD(real_image)   # 識別器Dで元画像に対する識別信号を出力
            # 元画像に対する識別信号の損失値
            errD_real = criterion(output, real_target)
            D_x = output.mean().item()

            fake_image = netG(noise)    # 生成器Gでノイズから贋作画像を生成
            # 識別器Dで元画像に対する識別信号を出力
            output = netD(fake_image.detach())
            # 贋作画像に対する識別信号の損失値
            errD_fake = criterion(output, fake_target)
            D_G_z1 = output.mean().item()

            errD = errD_real + errD_fake    # 識別器Dの全体の損失
            errD.backward()    # 誤差逆伝播
            optimizerD.step()   # Dのパラメーターを更新

            ############################
            # 生成器Gの更新
            ###########################
            netG.zero_grad()    # 勾配の初期化

            # 更新した識別器Dで改めて贋作画像に対する識別信号を出力
            output = netD(fake_image)
            # 生成器Gの損失値。Dに贋作画像を元画像と誤認させたいため目標値は「1」
            errG = criterion(output, real_target)
            errG.backward()     # 誤差逆伝播
            D_G_z2 = output.mean().item()

            optimizerG.step()   # Gのパラメータを更新

            if itr % display_interval == 0: 
                print('[{}/{}][{}/{}] Loss_D: {:.3f} Loss_G: {:.3f} D(x): {:.3f} D(G(z)): {:.3f}/{:.3f}'
                      .format(epoch + 1, n_epoch,
                              itr + 1, len(dataloader),
                              errD.item(), errG.item(), D_x, D_G_z1, D_G_z2))
            if epoch == 0 and itr == 0:     # 初回に元画像を保存する
                vutils.save_image(real_image, '{}/real_samples.png'.format(outf),
                                  normalize=True, nrow=10)

        ############################
        # 確認用画像の生成
        ############################
        # 1エポック終了ごとに確認用の贋作画像を生成する
        fake_image = netG(fixed_noise)
        vutils.save_image(fake_image.detach(),
                          '{}/fake_samples_epoch_{:03d}.png'.format(outf,
                                                                    epoch + 1),
                          normalize=True,
                          nrow=10)

        ############################
        # モデルの保存
        ############################
        if (epoch + 1) % 50 == 0:   # 50エポックごとにモデルを保存する
            torch.save(netG.state_dict(),
                       '{}/netG_epoch_{}.pth'.format(outf, epoch + 1))
            torch.save(netD.state_dict(),
                       '{}/netD_epoch_{}.pth'.format(outf, epoch + 1))
    
def make_generator_and_discriminator():
    # 生成器G。ランダムベクトルから贋作画像を生成する
    netG = Generator(nz=nz, nch_g=nch_g).to(device)
    netG.apply(weights_init)    # weights_init関数で初期化
    # print(netG)


    # 識別器D。画像が、元画像か贋作画像かを識別する
    netD = Discriminator(nch_d=nch_d).to(device)
    netD.apply(weights_init)
    # print(netD)
    return netG, netD

def load_train_and_test_data():
    # STL-10のtrain & test data (stl10_binary.tar.gz 2.5GB)を download & read
    trainset = dset.STL10(root='./dataset/stl10_root',
                          download=True,
                          # labalを使用しない為
                          # labelなしを混在した'train+unlabeled'を使用
                          split='train+unlabeled',
                          transform=transforms.Compose([
                              transforms.RandomResizedCrop(64,
                                                           scale=(88/96, 1.0),
                                                           ratio=(1., 1.)),
                              transforms.RandomHorizontalFlip(),
                              transforms.ColorJitter(brightness=0.05,
                                                     contrast=0.05,
                                                     saturation=0.05,
                                                     hue=0.05),
                              transforms.ToTensor(),
                              transforms.Normalize((0.5, 0.5, 0.5),
                                                   (0.5, 0.5, 0.5)),
                          ]))
    testset = dset.STL10(root='./dataset/stl10_root',
                         download=True,
                         split='test',
                         transform=transforms.Compose([
                             transforms.RandomResizedCrop(64,
                                                          scale=(88/96, 1.0),
                                                          ratio=(1., 1.)),
                             transforms.RandomHorizontalFlip(),
                             transforms.ColorJitter(brightness=0.05,
                                                    contrast=0.05,
                                                    saturation=0.05,
                                                    hue=0.05),
                             transforms.ToTensor(),
                             transforms.Normalize((0.5, 0.5, 0.5),
                                                  (0.5, 0.5, 0.5)),
                         ]))
    # STL-10の train dataとtest dataを合わせ訓練データとする
    dataset = trainset + testset

    # 訓練データをセットしたデータローダを作成
    dataloader = torch.utils.data.DataLoader(dataset,
                                             batch_size=batch_size,
                                             shuffle=True,
                                             num_workers=int(workers))
    return dataloader
    
def make_output_dir():
    try:
        os.makedirs(outf, exist_ok=True)
    except OSError as error:
        print(error)
        pass

    
# colab固有のerror回避の為らしい
def avoid_Imabe_colab_bug():
    Image.register_extension = register_extension
    Image.register_extensions = register_extensions
    
def register_extension(id, extension): 
    Image.EXTENSION[extension.lower()] = id.upper()

def register_extensions(id, extensions): 
    for extension in extensions: 
        register_extension(id, extension)

def mount_and_cd_gdrive():
    if gdrive_mount_point:
        drive.mount(gdrive_mount_point)
        os.chdir(gdrive_path)
    print(os.getcwd())

def weights_init(m):
    """
    ニューラルネットワークの重みを初期化する。
    作成したインスタンスに対しapplyメソッドで適用する
    :param m: ニューラルネットワークを構成する層
    """
    classname = m.__class__.__name__
    if classname.find('Conv') != -1:            # 畳み込み層の場合
        m.weight.data.normal_(0.0, 0.02)
        m.bias.data.fill_(0)
    elif classname.find('Linear') != -1:        # 全結合層の場合
        m.weight.data.normal_(0.0, 0.02)
        m.bias.data.fill_(0)
    elif classname.find('BatchNorm') != -1:     # バッチノーマライゼーションの場合
        m.weight.data.normal_(1.0, 0.02)
        m.bias.data.fill_(0)


class Generator(nn.Module):
    """
    生成器Gのクラス 
    """
    def __init__(self, nz=100, nch_g=64, nch=3):
        """
        :param nz: 入力ベクトルzの次元
        :param nch_g: 最終層の入力チャネル数
        :param nch: 出力画像のチャネル数
        """
        super(Generator, self).__init__()
        
        # ニューラルネットワークの構造を定義する
        self.layers = nn.ModuleDict({
            'layer0': nn.Sequential(
                nn.ConvTranspose2d(nz, nch_g * 8, 4, 1, 0),     # 転置畳み込み
                nn.BatchNorm2d(nch_g * 8),                      # バッチノーマライゼーション
                nn.ReLU()                                       # 正規化線形関数
            ),  # (B, nz, 1, 1) -> (B, nch_g*8, 4, 4)
            'layer1': nn.Sequential(
                nn.ConvTranspose2d(nch_g * 8, nch_g * 4, 4, 2, 1),
                nn.BatchNorm2d(nch_g * 4),
                nn.ReLU()
            ),  # (B, nch_g*8, 4, 4) -> (B, nch_g*4, 8, 8)
            'layer2': nn.Sequential(
                nn.ConvTranspose2d(nch_g * 4, nch_g * 2, 4, 2, 1),
                nn.BatchNorm2d(nch_g * 2),
                nn.ReLU()
            ),  # (B, nch_g*4, 8, 8) -> (B, nch_g*2, 16, 16)

            'layer3': nn.Sequential(
                nn.ConvTranspose2d(nch_g * 2, nch_g, 4, 2, 1),
                nn.BatchNorm2d(nch_g),
                nn.ReLU()
            ),  # (B, nch_g*2, 16, 16) -> (B, nch_g, 32, 32)
            'layer4': nn.Sequential(
                nn.ConvTranspose2d(nch_g, nch, 4, 2, 1),
                nn.Tanh()
            )   # (B, nch_g, 32, 32) -> (B, nch, 64, 64)
        })

    def forward(self, z):
        """
        順方向の演算
        :param z: 入力ベクトル
        :return: 生成画像
        """
        for layer in self.layers.values():  # self.layersの各層で演算を行う
            z = layer(z)
        return z


class Discriminator(nn.Module):
    """
    識別器Dのクラス
    """
    def __init__(self, nch=3, nch_d=64):
        """
        :param nch: 入力画像のチャネル数
        :param nch_d: 先頭層の出力チャネル数
        """
        super(Discriminator, self).__init__()

        # ニューラルネットワークの構造を定義する
        self.layers = nn.ModuleDict({
            'layer0': nn.Sequential(
                nn.Conv2d(nch, nch_d, 4, 2, 1),     # 畳み込み
                nn.LeakyReLU(negative_slope=0.2)    # leaky ReLU関数
            ),  # (B, nch, 64, 64) -> (B, nch_d, 32, 32)
            'layer1': nn.Sequential(
                nn.Conv2d(nch_d, nch_d * 2, 4, 2, 1),
                nn.BatchNorm2d(nch_d * 2),
                nn.LeakyReLU(negative_slope=0.2)
            ),  # (B, nch_d, 32, 32) -> (B, nch_d*2, 16, 16)
            'layer2': nn.Sequential(
                nn.Conv2d(nch_d * 2, nch_d * 4, 4, 2, 1),
                nn.BatchNorm2d(nch_d * 4),
                nn.LeakyReLU(negative_slope=0.2)
            ),  # (B, nch_d*2, 16, 16) -> (B, nch_d*4, 8, 8)
            'layer3': nn.Sequential(
                nn.Conv2d(nch_d * 4, nch_d * 8, 4, 2, 1),
                nn.BatchNorm2d(nch_d * 8),
                nn.LeakyReLU(negative_slope=0.2)
            ),  # (B, nch_d*4, 8, 8) -> (B, nch_g*8, 4, 4)
            'layer4': nn.Conv2d(nch_d * 8, 1, 4, 1, 0)
            # (B, nch_d*8, 4, 4) -> (B, 1, 1, 1)
        })

    def forward(self, x):
        """
        順方向の演算
        :param x: 元画像あるいは贋作画像
        :return: 識別信号
        """
        for layer in self.layers.values():  # self.layersの各層で演算を行う
            x = layer(x)
        return x.squeeze()     # Tensorの形状を(B)に変更して戻り値とする



if __name__ == '__main__':
    main()

↑こちらを実行すると、以下の画像ファイルが生成されます。

google colab の gpu付環境で、6時間程、学習させ、80epochの時点で停止させました。 80epochの学習程度では、まだまだといった印象です。

サンプル画像 f:id:end0tknr:20191022102148p:plain

1epoch後の画像  f:id:end0tknr:20191022102241p:plain

81epoch後の画像 f:id:end0tknr:20191022102255p:plain

「ModuleNotFoundError: No module named '_sqlite3'」には「./configure --enable-loadable-sqlite-extensions」からの python 3.7の再installが必要

$ ./foo_6_1.py 
Traceback (most recent call last):
  File "./foo_6_1.py", line 7, in <module>
    from google.colab import drive
  File "/usr/local/python3/lib/python3.7/site-packages/google/colab/__init__.py", line 25, in <module>
    from google.colab import auth
  File "/usr/local/python3/lib/python3.7/site-packages/google/colab/auth.py", line 26, in <module>
    import sqlite3 as _sqlite3  # pylint: disable=g-bad-import-order
  File "/usr/local/python3/lib/python3.7/sqlite3/__init__.py", line 23, in <module>
    from sqlite3.dbapi2 import *
  File "/usr/local/python3/lib/python3.7/sqlite3/dbapi2.py", line 27, in <module>
    from _sqlite3 import *
ModuleNotFoundError: No module named '_sqlite3'

というエラーが発生。

どうやら、「yum install sqlite-devel」と、python の再installが必要らしい。

$ yum install sqlite-devel

# ↓次に既にinstall済のpython moduleの一覧作成
$ sudo /usr/local/python3/bin/pip freeze > requirements.txt

# ↓「--enable-loadable-sqlite-extensions」を追加し、python本体の再install
$ ./configure --prefix=/usr/local/python3 \
            --enable-loadable-sqlite-extensions \
        --enable-optimizations
$ make
$ sudo make install

# ↓先程の requirements.txt で python module群も再install
$ sudo /usr/local/python3/bin/pip install -r requirements.txt

上記により「ModuleNotFoundError: No module named '_sqlite3'」は解消されました。

ただ、以下のような warning が発生するようになりました。 まぁ、warning ですので、今後、気が向いたら調べます。

$ ./foo_6_1.py 
/usr/local/python3/lib/python3.7/site-packages/IPython/utils/traitlets.py:5: UserWarning: IPython.utils.traitlets has moved to a top-level traitlets package.
  warn("IPython.utils.traitlets has moved to a top-level traitlets package.")

google colaboratory for python で google driveに mount

https://colab.research.google.com って スゴい

from google.colab import drive
drive.mount('/content/gdrive')

google colaboratory for python で↑このように実行後、 ↓こちらのurlへブラウザでアクセスし、そこで表示された  「authorization code」をgoogle colaboratory へ入力するだけ

Go to this URL in a browser: https://accounts.google.com/o/oauth2/auth?client_id=ないしょ
Enter your authorization code:
··········
Mounted at /content/gdrive

独自データセットを CNN(AlexNet) + 転移学習 で画像分類

独自データセットを CNN(AlexNet) で画像分類 - end0tknr's kipple - 新web写経開発 の続きとして 前回エントリの内容を、「CNN(AlexNet) + 転移学習」で実施。

転移学習とは、学習済のモデルを再利用するもので、 今回の場合、CNN(AlexNet) の最終のみ、重みを更新する学習を行っています。

#!/usr/local/python3/bin/python3
# -*- coding: utf-8 -*-
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
from torch.autograd import Variable
import numpy as np
import torchvision
from torchvision import datasets, models, transforms

# import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('Agg')
import matplotlib.pylab as plt

from PIL import Image
import time
import os

import cv2
from PIL import Image

device = 'cuda' if torch.cuda.is_available() else 'cpu'

num_epochs = 50


def main():
    print("DEVICE:", device)


    #画像の前処理を定義
    data_transforms = {
        'train': transforms.Compose([
            transforms.RandomResizedCrop(224),
            transforms.RandomHorizontalFlip(),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ]),
        'val': transforms.Compose([
            transforms.Resize(256),
            transforms.CenterCrop(224),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ]),
    }


    #画像とラベルを読み込む
    data_dir = 'hymenoptera_data'
    image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
                                              data_transforms[x])
                      for x in ['train', 'val']}

    train_loader = torch.utils.data.DataLoader(image_datasets['train'], batch_size=5,
                                                 shuffle=True, num_workers=4)
    test_loader = torch.utils.data.DataLoader(image_datasets['val'], batch_size=5,
                                                 shuffle=False, num_workers=4)

    dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
    class_names = image_datasets['train'].classes


    net = models.alexnet(pretrained=True)  # 学習済み重みを利用するAlexNet
    net = net.to(device)

    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9, weight_decay=5e-4)


    # ネットワークのパラメータを凍結
    for param in net.parameters():
        param.requires_grad = False  # backward時に重みを更新させない
    net = net.to(device)
    # 最終層を2クラス用(アリ,ハチ)に変更.
    # 変更することで、最終層はbackward時に重みが更新される
    num_ftrs = net.classifier[6].in_features
    net.classifier[6] = nn.Linear(num_ftrs, 2).to(device)


    # 最適化関数.
    # lr_schedulerは学習率を変更する為のものらしいが、理解していません
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9, weight_decay=5e-4)
    lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)


    train_loss_list = []
    train_acc_list = []
    val_loss_list = []
    val_acc_list = []

    for epoch in range(num_epochs):
        
        train_loss = 0
        train_acc = 0
        val_loss = 0
        val_acc = 0

        #train
        net.train()
        for i, (images, labels) in enumerate(train_loader):
            images, labels = images.to(device), labels.to(device)

            optimizer.zero_grad()
            outputs = net(images)
            loss = criterion(outputs, labels)
            train_loss += loss.item()
            train_acc += (outputs.max(1)[1] == labels).sum().item()
            loss.backward()
            optimizer.step()

        avg_train_loss = train_loss / len(train_loader.dataset)
        avg_train_acc = train_acc / len(train_loader.dataset)

        #val
        net.eval()
        with torch.no_grad():
            for images, labels in test_loader:
                images = images.to(device)
                labels = labels.to(device)
                outputs = net(images)
                loss = criterion(outputs, labels)
                val_loss += loss.item()
                val_acc += (outputs.max(1)[1] == labels).sum().item()
        avg_val_loss = val_loss / len(test_loader.dataset)
        avg_val_acc = val_acc / len(test_loader.dataset)

        output_str_format = ', '.join(['Epoch [{}/{}]','Loss: {loss:.4f}',
                                       'val_loss: {val_loss:.4f}','val_acc: {val_acc:.4f}',
                                       'lr:{learning_rate}'])
        print(output_str_format.format(epoch+1, num_epochs, i+1, loss=avg_train_loss,
                                       val_loss=avg_val_loss, val_acc=avg_val_acc,
                                       learning_rate=optimizer.param_groups[0]["lr"]))
        
        #学習率調整
        lr_scheduler.step()
        train_loss_list.append(avg_train_loss)
        train_acc_list.append(avg_train_acc)
        val_loss_list.append(avg_val_loss)
        val_acc_list.append(avg_val_acc)
        
    output_to_file(train_loss_list,train_acc_list,val_loss_list,val_acc_list)

    
def output_to_file(train_loss_list,train_acc_list,val_loss_list,val_acc_list):

    plt.figure()
    plt.plot(range(num_epochs),
             train_loss_list,
             color='blue',
             linestyle='-',
             label='train_loss')
    plt.plot(range(num_epochs),
             val_loss_list,
             color='green',
             linestyle='--',
             label='val_loss')
#    plt.ylim([0,0.04])
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('loss')
    plt.title('Training and validation loss')
    plt.grid()
    plt.savefig( 'test_4_1.png' )

    plt.figure()
    plt.plot(range(num_epochs),
             train_acc_list,
             color='blue',
             linestyle='-',
             label='train_acc')
    plt.plot(range(num_epochs),
             val_acc_list,
             color='green',
             linestyle='--',
             label='val_acc')
    plt.ylim([0,1])
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('acc')
    plt.title('Training and validation accuracy')
    plt.grid()
    plt.savefig( 'test_4_2.png' )


if __name__ == '__main__':
    main()

↑こう書くと、↓こう出力されます。

DEVICE: cuda
Epoch [1/50], Loss: 0.8953, val_loss: 1.2442, val_acc: 0.8366, lr:0.01
  :
Epoch [20/50], Loss: 0.0634, val_loss: 1.4746, val_acc: 0.8954, lr:0.001
Epoch [21/50], Loss: 0.2592, val_loss: 1.4652, val_acc: 0.8954, lr:0.00010000000000000002
  :
Epoch [30/50], Loss: 0.2551, val_loss: 1.4208, val_acc: 0.8889, lr:0.00010000000000000002
Epoch [31/50], Loss: 0.2619, val_loss: 1.4198, val_acc: 0.8889, lr:1.0000000000000003e-05
  :
Epoch [50/50], Loss: 0.2745, val_loss: 1.4169, val_acc: 0.8889, lr:1.0000000000000002e-06

f:id:end0tknr:20191020075721p:plainf:id:end0tknr:20191020075724p:plain

独自データセットを CNN(AlexNet) で画像分類

deep learningにおけるhello worldのMLP (Multi Layer Perceptron) から、畳込みニューラルネットワーク(CNN : Convolutional Neural Network )におけるhello worldのAlexNetへ - end0tknr's kipple - 新web写経開発

GitHub - miyamotok0105/pytorch_handbook: pytorch_handbook

先日の CNN(AlexNet) エントリの続きで、やはり上記urlの写経。

前回は、CIFAR-10 https://www.cs.toronto.edu/~kriz/cifar.html というバイナリ?で用意されたデータを使用しましたが、 今回は、 https://download.pytorch.org/tutorial/hymenoptera_data.zip にある アリとハチの画像を分類します。

画像のサンプルは以下。 f:id:end0tknr:20191019185851p:plain f:id:end0tknr:20191019185850p:plain

#!/usr/local/python3/bin/python3
# -*- coding: utf-8 -*-
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
from torch.autograd import Variable
import numpy as np
import torchvision
from torchvision import datasets, models, transforms

# import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('Agg')
import matplotlib.pylab as plt

from PIL import Image
import time
import os

import cv2
from PIL import Image


DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'

root = 'hymenoptera_data'
num_classes = 2
TRAIN_NUM_EPOCHS = 500
fc_size = 9216


def main():
    print("DEVICE:", DEVICE)
    

    (data_transforms, to_tensor_transforms) = get_pre_process()

    # 定義したDatasetとDataLoaderを使います。
    custom_train_dataset = CustomDataset(root, data_transforms["train"], train=True)
    train_loader = torch.utils.data.DataLoader(dataset=custom_train_dataset,
                                               batch_size=5, 
                                               shuffle=True)
    custom_test_dataset = CustomDataset(root, data_transforms["val"])
    test_loader = torch.utils.data.DataLoader(dataset=custom_test_dataset,
                                              batch_size=5, 
                                              shuffle=False)
    # for i, (images, labels) in enumerate(train_loader):
    #     print(images.size())
    #     print(images[0].size())
    #     print(labels[0].item())
    #     #ここに訓練などの処理をきます。
    #     break

    global fc_size
    # batch_size=10, channel=3, size=224x224
    fc_size = get_fc_size( torch.FloatTensor(10, 3, 224, 224) )
    print("fc_size:",fc_size)
    
    net = AlexNet(num_classes, fc_size).to(DEVICE)
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9, weight_decay=5e-4)

    (train_loss_list, train_acc_list, val_loss_list, val_acc_list) = \
        train_epochs(train_loader, test_loader, net, criterion, optimizer)

    output_to_file(train_loss_list,train_acc_list,val_loss_list,val_acc_list)


def train_epochs(train_loader, test_loader, net, criterion, optimizer):
    train_loss_list = []
    train_acc_list =  []
    val_loss_list =   []
    val_acc_list =    []

    for epoch in range(TRAIN_NUM_EPOCHS):
        train_loss = 0
        train_acc =  0
        val_loss =   0
        val_acc =    0
    
        #train
        net.train()
        for i, (images, labels) in enumerate(train_loader):
            images, labels = images.to(DEVICE), labels.to(DEVICE)
            
            optimizer.zero_grad()
            outputs = net(images)
            loss = criterion(outputs, labels)
            train_loss += loss.item()
            train_acc += (outputs.max(1)[1] == labels).sum().item()
            loss.backward()
            optimizer.step()
    
        avg_train_loss = train_loss / len(train_loader.dataset)
        avg_train_acc =  train_acc  / len(train_loader.dataset)
    
        #val
        net.eval()
        with torch.no_grad():
            for images, labels in test_loader:
                images = images.to(DEVICE)
                labels = labels.to(DEVICE)
                outputs = net(images)
                loss = criterion(outputs, labels)
                val_loss += loss.item()
                val_acc += (outputs.max(1)[1] == labels).sum().item()
        avg_val_loss = val_loss / len(test_loader.dataset)
        avg_val_acc = val_acc / len(test_loader.dataset)

        
        output_str_format = ', '.join(['Epoch [{}/{}]','Loss: {loss:.4f}',
                                       'val_loss: {val_loss:.4f}','val_acc: {val_acc:.4f}'])
        print (output_str_format.format(epoch+1, TRAIN_NUM_EPOCHS, i+1, loss=avg_train_loss,
                                        val_loss=avg_val_loss, val_acc=avg_val_acc))
        
        train_loss_list.append(avg_train_loss)
        train_acc_list.append(avg_train_acc)
        val_loss_list.append(avg_val_loss)
        val_acc_list.append(avg_val_acc)
        
    return train_loss_list, train_acc_list, val_loss_list, val_acc_list


def show_img(img):
    npimg = img.numpy()
#   ↓この行は理解できていません
#    plt.imshow(np.transpose(npimg, (1,2,0)), interpolation='nearest')


def get_pre_process():
    #画像の前処理
    data_transforms = {
        'train': transforms.Compose([
            transforms.RandomResizedCrop(224),  # re-size
            transforms.RandomHorizontalFlip(),  # 反転
            transforms.ToTensor(),
            transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) #正規化
        ]),
        'val': transforms.Compose([
            transforms.Resize(256),
            transforms.CenterCrop(224),
            transforms.ToTensor(),
            transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
        ]),
    }
    #正規化をしない前処理
    to_tensor_transforms = transforms.Compose([
        transforms.RandomResizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor()
    ])
    return data_transforms , to_tensor_transforms

def get_nn_features():
    features = nn.Sequential(
        nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
        nn.ReLU(inplace=True),
        nn.MaxPool2d(kernel_size=2, stride=2),
        nn.Conv2d(64, 192, kernel_size=5, padding=2),
        nn.ReLU(inplace=True),
        nn.MaxPool2d(kernel_size=2, stride=2),
        nn.Conv2d(192, 384, kernel_size=3, padding=1),
        nn.ReLU(inplace=True),
        nn.Conv2d(384, 256, kernel_size=3, padding=1),
        nn.ReLU(inplace=True),
        nn.Conv2d(256, 256, kernel_size=3, padding=1),
        nn.ReLU(inplace=True),
        nn.MaxPool2d(kernel_size=2, stride=2),
    )
    return features

def get_fc_size( size_check ):
    features = get_nn_features()
    
    #バッチサイズ10, 6×6のフィルターが256枚
    #10バッチは残して、6×6×256を1次元に落とす=>6×6×256=9216
    print("size1:",features(size_check).size())
    #バッチ10の値を軸にして残りの次元を1次元へ落とした場合の
    #Tensorの形状をチェックすると9216。
    print("size2:",features(size_check).view(size_check.size(0), -1).size())
    #fc_sizeを全結合の形状として保持
    global fc_size
    fc_size = features(size_check).view(size_check.size(0), -1).size()[1]
    print("size3:",fc_size)
    return fc_size


def output_to_file(train_loss_list,train_acc_list,val_loss_list,val_acc_list):

    plt.figure()
    plt.plot(range(TRAIN_NUM_EPOCHS),
             train_loss_list,
             color='blue',
             linestyle='-',
             label='train_loss')
    plt.plot(range(TRAIN_NUM_EPOCHS),
             val_loss_list,
             color='green',
             linestyle='--',
             label='val_loss')
#    plt.ylim([0,0.04])
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('loss')
    plt.title('Training and validation loss')
    plt.grid()
    plt.savefig( 'test_4_1.png' )

    plt.figure()
    plt.plot(range(TRAIN_NUM_EPOCHS),
             train_acc_list,
             color='blue',
             linestyle='-',
             label='train_acc')
    plt.plot(range(TRAIN_NUM_EPOCHS),
             val_acc_list,
             color='green',
             linestyle='--',
             label='val_acc')
    plt.ylim([0,1])
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('acc')
    plt.title('Training and validation accuracy')
    plt.grid()
    plt.savefig( 'test_4_2.png' )


class CustomDataset(torch.utils.data.Dataset):
    classes = ['ant', 'bee']
  
    def __init__(self, root, transform=None, train=True):
        
        self.transform = transform   # 指定する場合、前処理クラスを受取り
        
        self.images = []  # 画像とlabelの保持用
        self.labels = []

        # 訓練と検証で、読込むpathを取得
        if train == True:
            root_ants_path = os.path.join(root, 'train', 'ants')
            root_bees_path = os.path.join(root, 'train', 'bees')
        else:
            root_ants_path = os.path.join(root, 'val', 'ants')
            root_bees_path = os.path.join(root, 'val', 'bees')
        
        ant_images = os.listdir(root_ants_path)  # アリの画像一覧を取得
        ant_labels = [0] * len(ant_images)       # アリをlabel=0に指定
       
        bee_images = os.listdir(root_bees_path)  # ハチの画像一覧を取得
        bee_labels = [1] * len(bee_images)       # ハチをlabel=1に指定

        # listのmerge
        for image, label in zip(ant_images, ant_labels):
            self.images.append(os.path.join(root_ants_path, image))
            self.labels.append(label)
        for image, label in zip(bee_images, bee_labels):
            self.images.append(os.path.join(root_bees_path, image))
            self.labels.append(label)
        
    def __getitem__(self, index):
        image = self.images[index]     # indexを元に画像のpathとlabel取得
        label = self.labels[index]
       
        with open(image, 'rb') as f:   # pathから画像読込み
            image = Image.open(f)
            image = image.convert('RGB')
        
        if self.transform is not None: # 前処理がある場合は入れる
            image = self.transform(image)
            
        return image, label
        
    def __len__(self):
        # ここにはデータ数を指定します。
        return len(self.images)


class AlexNet(nn.Module):
    def __init__(self, num_classes, fc_size):
        super(AlexNet, self).__init__()
        self.features = get_nn_features()

        self.classifier = nn.Sequential(
            nn.Dropout(p=0.5),
            nn.Linear(fc_size, 4096),  #fc_sizeで計算した形状を指定
            nn.ReLU(inplace=True),
            nn.Dropout(p=0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes)
        )
        
    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x


if __name__ == '__main__':
    main()

↑こう書くと、↓こう出力されます。

手元のcent os 8 でも実行しましたが、GPUがなく時間がかかる為、 google colaboratory でも実行しています。

$ wget https://download.pytorch.org/tutorial/hymenoptera_data.zip
$ unzip hymenoptera_data.zip
$ ./foo.py
DEVICE: cuda
size1: torch.Size([10, 256, 6, 6])
size2: torch.Size([10, 9216])
size3: 9216
fc_size: 9216
Epoch [1/500], Loss: 0.1392, val_loss: 0.1386, val_acc: 0.5061
Epoch [2/500], Loss: 0.1394, val_loss: 0.1386, val_acc: 0.5061
  :
Epoch [500/500], Loss: 0.0758, val_loss: 0.0612, val_acc: 0.8653

f:id:end0tknr:20191019185806p:plainf:id:end0tknr:20191019185810p:plain

python の import cv2 で ImportError: libSM.so.6: cannot open shared object file: No such file or directory

事前の 「$ sudo /usr/local/python3/bin/pip install opencv-python」だけでは、不足らしい。

$ ./foo_4_2.py 
Traceback (most recent call last):
  File "./foo_4_2.py", line 21, in <module>
    import cv2
  File "/usr/local/python3/lib/python3.7/site-packages/cv2/__init__.py", line 3, in <module>
    from .cv2 import *
ImportError: libSM.so.6: cannot open shared object file: No such file or directory

とエラーとなった為、以下のように「yum search libSM」で検索し、

$ yum search libSM
CentOS-8 - AppStream                                   189 kB/s | 6.0 MB     00:32    
CentOS-8 - Base                                        910 kB/s | 7.9 MB     00:08    
CentOS-8 - Extras                                      587  B/s | 2.1 kB     00:03    
============================= Name Exactly Matched: libSM =============================
libSM.i686 : X.Org X11 SM runtime library
libSM.x86_64 : X.Org X11 SM runtime library
libSM.i686 : X.Org X11 SM runtime library
libSM.x86_64 : X.Org X11 SM runtime library
============================ Name & Summary Matched: libSM ============================
libsmbios.i686 : Libsmbios C/C++ shared libraries
libsmbios.x86_64 : Libsmbios C/C++ shared libraries
================================= Name Matched: libSM =================================
libsmi.i686 : A library to access SMI MIB information
libsmi.x86_64 : A library to access SMI MIB information
libSM-devel.i686 : X.Org X11 SM development package
libSM-devel.x86_64 : X.Org X11 SM development package
libsmartcols.x86_64 : Formatting library for ls-like programs.
libsmbclient.x86_64 : The SMB client library
libsmartcols.i686 : Formatting library for ls-like programs.
libsmartcols.x86_64 : Formatting library for ls-like programs.
libsmbclient.i686 : The SMB client library
libsmbclient.x86_64 : The SMB client library
libsmartcols-devel.i686 : Formatting library for ls-like programs.
libsmartcols-devel.x86_64 : Formatting library for ls-like programs.

以下のようにinstallし、完了

$ sudo yum install libSM.x86_64

deep learningにおけるhello worldのMLP (Multi Layer Perceptron) から、畳込みニューラルネットワーク(CNN : Convolutional Neural Network )におけるhello worldのAlexNetへ

MLP と AlexNet の違い

f:id:end0tknr:20191019082403p:plain

全結合のニューラルネットワーク (例: MLP)

3次元データも1次元に変換し、全結合層への入力データとする為、 3次元データが持つ形状を無視してしまう

畳込みニューラルネットワーク (例: AlexNet)

3次元データを受け、畳込み(≒フィルタ)演算し、3次元を出力する為、形状を保つ. また、プーリングとは縦横の空間を小さくする演算.

pytorch for pythonによる CIFAR10 に対する AlexNet 画像分類

pytorch for pythonによる CIFAR10 に対する画像分類 - end0tknr's kipple - 新web写経開発

上記urlにある先日のエントリはMLPによるものですが、 これをAlexNetで実装すると以下の通り。

#!/usr/local/python3/bin/python3
# -*- coding: utf-8 -*-
import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms as transforms
import numpy as np
# from matplotlib import pyplot as plt
import matplotlib
matplotlib.use('Agg')
import matplotlib.pylab as plt

NUM_CLASSES = 10  # CIFAR10データは、10種類のdata
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
DATA_DIR = './data/'
IMG_SIZE_AND_CHANNEL = 0
TRAIN_NUM_EPOCHS = 30   # 学習回数
#TRAIN_NUM_EPOCHS = 50   # 学習回数


def main():
    (train_loader, test_loader) = get_train_test_data()
    print("train_loader: ", train_loader)
    print("test_loader: ", test_loader)

    net = AlexNet(NUM_CLASSES).to(DEVICE)
#    net = MLPNet().to(DEVICE)

    (criterion, optimizer) = get_net_criterion_optimizer(net)

    (train_loss_list, train_acc_list, val_loss_list, val_acc_list) = \
        train(train_loader, test_loader, net, criterion, optimizer)

    output_to_file(train_loss_list, train_acc_list, val_loss_list, val_acc_list)

    # torch.save(net.state_dict(), 'net.ckpt')

    # net2 = MLPNet().to(device)
    # net2.load_state_dict(torch.load('net.ckpt'))

    # net2.eval()
    # with torch.no_grad():
    #     total = 0
    #     test_acc = 0
    #     for images, labels in test_loader:
    #         images, labels = images.view(-1, 32*32*3).to(device), labels.to(device)
    #         outputs = net2(images)
    #         test_acc += (outputs.max(1)[1] == labels).sum().item()
    #         total += labels.size(0)
    #     print('精度: {} %'.format(100 * test_acc / total))


def get_train_test_data():
    # CIFAR10とは、ラベル付済の5万枚の訓練画像と1万枚のテスト画像のデータセットで
    # 以下で、STEP1) www.cs.toronto.edu から自動的にダウンロード & 解凍します
    #         STEP2) 画像と正解ラベルのペアを返却 が行われます
    train_dataset = torchvision.datasets.CIFAR10(root=DATA_DIR,
                                                 train=True,
                                                 transform=transforms.ToTensor(),
                                                 download=True)
    test_dataset = torchvision.datasets.CIFAR10(root=DATA_DIR,
                                                train=False,
                                                transform=transforms.ToTensor(),
                                                download=True)
    # 試しに1つの訓練用データの内容を見ると、3チャネル , 32x32size だと分かります
    image, label = train_dataset[0]
    # print( image.size() )
    # print(label)
    global IMG_SIZE_AND_CHANNEL
    IMG_SIZE_AND_CHANNEL = image.size()[0] * image.size()[1] * image.size()[2]

    # DataLoader() は、batch_size分だけ、画像と正解ラベルを返します
    train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                               batch_size=64,
                                               shuffle=True,
                                               num_workers=2)
    test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                              batch_size=64,
                                              shuffle=False,
                                              num_workers=2)
    return train_loader, test_loader
    

# MLP ネットワークの定義
# (多層パーセプトロン, 入力層,隠れ層,出力層が全結合である最も単純なdeep learning)
class MLPNet (nn.Module):
    def __init__(self):
        super(MLPNet, self).__init__()
        # 32x32size & 3チャネル, 隠れ層のunit数は、600
        self.fc1 = nn.Linear(IMG_SIZE_AND_CHANNEL, 600)
        self.fc2 = nn.Linear(600, 600)
        self.fc3 = nn.Linear(600, NUM_CLASSES)
        self.dropout1 = nn.Dropout2d(0.2)
        self.dropout2 = nn.Dropout2d(0.2)
            
    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = self.dropout1(x)
        x = F.relu(self.fc2(x))
        x = self.dropout2(x)
        return F.relu(self.fc3(x))


class AlexNet(nn.Module):
    def __init__(self, num_classes):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=5),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(64, 192, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )
        self.classifier = nn.Linear(256, num_classes)

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x
    
def get_net_criterion_optimizer(net):

    criterion = nn.CrossEntropyLoss()
    # 以下のparameterの妥当性は理解していません
    optimizer = optim.SGD(net.parameters(),
                          lr=0.01,
                          momentum=0.9,
                          weight_decay=5e-4)
    return criterion, optimizer

def train(train_loader, test_loader, net, criterion, optimizer):
    #最後にlossとaccuracyのグラフを出力する為
    train_loss_list = []
    train_acc_list =  []
    val_loss_list =   []
    val_acc_list =    []

    for epoch in range(TRAIN_NUM_EPOCHS):
        #エポックごとに初期化
        train_loss = 0
        train_acc = 0
        val_loss = 0
        val_acc = 0
    
        net.train()  #訓練モードへ切り替え
        #ミニバッチで分割し読込み
        for i, (images, labels) in enumerate(train_loader):
            # AlexNetの場合、view()での1次元化変換を行わない
            images, labels = images.to(DEVICE), labels.to(DEVICE)
            # #viewで縦横32x32 & 3channelのimgを1次元に変換し、toでDEVICEに転送
            # images, labels = \
            #     images.view(-1,IMG_SIZE_AND_CHANNEL).to(DEVICE),labels.to(DEVICE)

            optimizer.zero_grad()               # 勾配をリセット
            outputs = net(images)               # 順伝播の計算
            loss = criterion(outputs, labels)   #lossの計算
            train_loss += loss.item()           #lossのミニバッチ分を溜め込む
            #accuracyをミニバッチ分を溜め込む
            #正解ラベル(labels)と予測値のtop1(outputs.max(1))が一致時、1が返る
            train_acc += (outputs.max(1)[1] == labels).sum().item()
            #逆伝播の計算
            loss.backward()
            #重みの更新
            optimizer.step()
        #平均lossと平均accuracyを計算
        avg_train_loss = train_loss / len(train_loader.dataset)
        avg_train_acc = train_acc / len(train_loader.dataset)
    

        net.eval()   #評価モードへ切り替え
        
        #評価するときに必要のない計算が走らないようtorch.no_gradを使用
        with torch.no_grad():
            for images, labels in test_loader:        
                # AlexNetの場合、view()での1次元化変換を行わない
                images, labels = images.to(DEVICE), labels.to(DEVICE)
#                images, labels = \
#                images.view(-1,IMG_SIZE_AND_CHANNEL).to(DEVICE),labels.to(DEVICE)
                outputs = net(images)
                loss = criterion(outputs, labels)
                val_loss += loss.item()
                val_acc += (outputs.max(1)[1] == labels).sum().item()
        avg_val_loss = val_loss / len(test_loader.dataset)
        avg_val_acc = val_acc / len(test_loader.dataset)
    

        # 訓練データのlossと検証データのlossとaccuracyをログ出力.
        print ("Epoch [{}/{}], Loss: {loss:.4f},val_loss: {val_loss:.4f},val_acc: {val_acc:.4f}"
               .format(epoch+1,
                       TRAIN_NUM_EPOCHS,
                       i+1,
                       loss=avg_train_loss,
                       val_loss=avg_val_loss, val_acc=avg_val_acc))

        train_loss_list.append(avg_train_loss)
        train_acc_list.append(avg_train_acc)
        val_loss_list.append(avg_val_loss)
        val_acc_list.append(avg_val_acc)

    return train_loss_list,train_acc_list,val_loss_list,val_acc_list

def output_to_file(train_loss_list,train_acc_list,val_loss_list,val_acc_list):

    plt.figure()
    plt.plot(range(TRAIN_NUM_EPOCHS),
             train_loss_list,
             color='blue',
             linestyle='-',
             label='train_loss')
    plt.plot(range(TRAIN_NUM_EPOCHS),
             val_loss_list,
             color='green',
             linestyle='--',
             label='val_loss')
    plt.ylim([0,0.04])
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('loss')
    plt.title('Training and validation loss')
    plt.grid()
    plt.savefig( 'test_4_1.png' )

    plt.figure()
    plt.plot(range(TRAIN_NUM_EPOCHS),
             train_acc_list,
             color='blue',
             linestyle='-',
             label='train_acc')
    plt.plot(range(TRAIN_NUM_EPOCHS),
             val_acc_list,
             color='green',
             linestyle='--',
             label='val_acc')
    plt.ylim([0,1])
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('acc')
    plt.title('Training and validation accuracy')
    plt.grid()
    plt.savefig( 'test_4_2.png' )

    
if __name__ == '__main__':
    main()

MLP , AlexNet の実行結果を比較すると、 以下のように CNNである AlexNet の精度が高いことが分かります。

MLP

f:id:end0tknr:20191019082442p:plain

CNN (AlexNet)

f:id:end0tknr:20191019082450p:plain

pytorch for pythonによる CIFAR10 に対する画像分類

https://github.com/miyamotok0105/pytorch_handbook

上記urlにある3章の写経.

srcの内容は理解できる. が、srcに記載されているロジック妥当性までは理解できていない。

そう考えると「まだまだ」というより「さっぱり」だ

ちなみに CIFAR10とは、次のurlにある10種類の画像群です。

CIFAR-10 and CIFAR-100 datasets

#!/usr/local/python3/bin/python3
# -*- coding: utf-8 -*-
import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms as transforms
import numpy as np
# from matplotlib import pyplot as plt
import matplotlib
matplotlib.use('Agg')
import matplotlib.pylab as plt

NUM_CLASSES = 10  # CIFAR10データは、10種類のdata
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
DATA_DIR = './data/'
IMG_SIZE_AND_CHANNEL = 0
TRAIN_NUM_EPOCHS = 50   #50エポック


def main():
    (train_loader, test_loader) = get_train_test_data()
    print("train_loader: ", train_loader)
    print("test_loader: ", test_loader)

    (net, criterion, optimizer) = get_net_criterion_optimizer()

    (train_loss_list, train_acc_list, val_loss_list, val_acc_list) = \
        train(train_loader, test_loader, net, criterion, optimizer)

    output_to_file(train_loss_list, train_acc_list, val_loss_list, val_acc_list)

    # torch.save(net.state_dict(), 'net.ckpt')

    # net2 = MLPNet().to(device)
    # net2.load_state_dict(torch.load('net.ckpt'))

    # net2.eval()
    # with torch.no_grad():
    #     total = 0
    #     test_acc = 0
    #     for images, labels in test_loader:
    #         images, labels = images.view(-1, 32*32*3).to(device), labels.to(device)
    #         outputs = net2(images)
    #         test_acc += (outputs.max(1)[1] == labels).sum().item()
    #         total += labels.size(0)
    #     print('精度: {} %'.format(100 * test_acc / total))


def get_train_test_data():
    # CIFAR10とは、ラベル付けされた5万枚の訓練画像と1万枚のテスト画像のデータセットで
    # 以下で、STEP1) www.cs.toronto.edu から自動的にダウンロード & 解凍します
    #         STEP2) 画像と正解ラベルのペアを返却 が行われます
    train_dataset = torchvision.datasets.CIFAR10(root=DATA_DIR,
                                                 train=True,
                                                 transform=transforms.ToTensor(),
                                                 download=True)
    test_dataset = torchvision.datasets.CIFAR10(root=DATA_DIR,
                                                train=False,
                                                transform=transforms.ToTensor(),
                                                download=True)
    # 試しに1つの訓練用データの内容を見ると、3チャネル , 32x32size だと分かります
    image, label = train_dataset[0]
    # print( image.size() )
    # print(label)
    global IMG_SIZE_AND_CHANNEL
    IMG_SIZE_AND_CHANNEL = image.size()[0] * image.size()[1] * image.size()[2]

    # DataLoader() は、batch_size分だけ、画像と正解ラベルを返します
    train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                               batch_size=64,
                                               shuffle=True,
                                               num_workers=2)
    test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                              batch_size=64,
                                              shuffle=False,
                                              num_workers=2)
    return train_loader, test_loader
    

# MLP ネットワークの定義
# (多層パーセプトロン, 入力層,隠れ層,出力層が全結合である最も単純なdeep learning)
class MLPNet (nn.Module):
    def __init__(self):
        super(MLPNet, self).__init__()
        # 32x32size & 3チャネル, 隠れ層のunit数は、600
        self.fc1 = nn.Linear(IMG_SIZE_AND_CHANNEL, 600)
        self.fc2 = nn.Linear(600, 600)
        self.fc3 = nn.Linear(600, NUM_CLASSES)
        self.dropout1 = nn.Dropout2d(0.2)
        self.dropout2 = nn.Dropout2d(0.2)
            
    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = self.dropout1(x)
        x = F.relu(self.fc2(x))
        x = self.dropout2(x)
        return F.relu(self.fc3(x))

def get_net_criterion_optimizer():
    net = MLPNet().to(DEVICE)

    criterion = nn.CrossEntropyLoss()
    # 以下のparameterの妥当性は理解していません
    optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9, weight_decay=5e-4)
    return net, criterion, optimizer

def train(train_loader, test_loader, net, criterion, optimizer):
    #最後にlossとaccuracyのグラフを出力する為
    train_loss_list = []
    train_acc_list =  []
    val_loss_list =   []
    val_acc_list =    []

    for epoch in range(TRAIN_NUM_EPOCHS):
        #エポックごとに初期化
        train_loss = 0
        train_acc = 0
        val_loss = 0
        val_acc = 0
    
        net.train()  #訓練モードへ切り替え
        
        for i, (images, labels) in enumerate(train_loader):  #ミニバッチで分割し読込み
            #viewで縦横32x32 & 3channelのimgを1次元に変換し、toでDEVICEに転送
            images, labels = images.view(-1, IMG_SIZE_AND_CHANNEL).to(DEVICE), labels.to(DEVICE)

            
            optimizer.zero_grad()               # 勾配をリセット
            outputs = net(images)               # 順伝播の計算
            loss = criterion(outputs, labels)   #lossの計算
            train_loss += loss.item()           #lossのミニバッチ分を溜め込む
            #accuracyをミニバッチ分を溜め込む
            #正解ラベル(labels)と予測値のtop1(outputs.max(1))が合っている場合に1が返る
            train_acc += (outputs.max(1)[1] == labels).sum().item()
            #逆伝播の計算
            loss.backward()
            #重みの更新
            optimizer.step()
            #平均lossと平均accuracyを計算
        avg_train_loss = train_loss / len(train_loader.dataset)
        avg_train_acc = train_acc / len(train_loader.dataset)
    

        net.eval()   #評価モードへ切り替え
        
        #評価するときに必要のない計算が走らないようtorch.no_gradを使用
        with torch.no_grad():
            for images, labels in test_loader:        
                images, labels = images.view(-1,IMG_SIZE_AND_CHANNEL).to(DEVICE),labels.to(DEVICE)
                outputs = net(images)
                loss = criterion(outputs, labels)
                val_loss += loss.item()
                val_acc += (outputs.max(1)[1] == labels).sum().item()
        avg_val_loss = val_loss / len(test_loader.dataset)
        avg_val_acc = val_acc / len(test_loader.dataset)
    

        # 訓練データのlossと検証データのlossとaccuracyをログ出力.
        print ("Epoch [{}/{}], Loss: {loss:.4f},val_loss: {val_loss:.4f},val_acc: {val_acc:.4f}"
               .format(epoch+1,
                       TRAIN_NUM_EPOCHS,
                       i+1,
                       loss=avg_train_loss,
                       val_loss=avg_val_loss, val_acc=avg_val_acc))

        train_loss_list.append(avg_train_loss)
        train_acc_list.append(avg_train_acc)
        val_loss_list.append(avg_val_loss)
        val_acc_list.append(avg_val_acc)

    return train_loss_list,train_acc_list,val_loss_list,val_acc_list

def output_to_file(train_loss_list,train_acc_list,val_loss_list,val_acc_list):

    plt.figure()
    plt.plot(range(TRAIN_NUM_EPOCHS),
             train_loss_list,
             color='blue',
             linestyle='-',
             label='train_loss')
    plt.plot(range(TRAIN_NUM_EPOCHS),
             val_loss_list,
             color='green',
             linestyle='--',
             label='val_loss')
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('loss')
    plt.title('Training and validation loss')
    plt.grid()
    plt.savefig( 'test_1.png' )

    plt.figure()
    plt.plot(range(TRAIN_NUM_EPOCHS),
             train_acc_list,
             color='blue',
             linestyle='-',
             label='train_acc')
    plt.plot(range(TRAIN_NUM_EPOCHS),
             val_acc_list,
             color='green',
             linestyle='--',
             label='val_acc')
    plt.legend()
    plt.xlabel('epoch')
    plt.ylabel('acc')
    plt.title('Training and validation accuracy')
    plt.grid()
    plt.savefig( 'test_2.png' )

    
if __name__ == '__main__':
    main()

↑こう書くと、↓こう表示されます。

[end0tknr@cent80 PYTORCH]$ ./foo_3_2.py 
Files already downloaded and verified
Files already downloaded and verified
train_loader:  <torch.utils.data.dataloader.DataLoader object at 0x7f1f2821ab50>
test_loader:  <torch.utils.data.dataloader.DataLoader object at 0x7f1f28970e50>
Epoch [1/50], Loss: 0.0304,val_loss: 0.0276,val_acc: 0.3715
Epoch [2/50], Loss: 0.0274,val_loss: 0.0272,val_acc: 0.3773
 :
Epoch [10/50], Loss: 0.0233,val_loss: 0.0228,val_acc: 0.4799
 :
Epoch [40/50], Loss: 0.0197,val_loss: 0.0208,val_acc: 0.5325
 :
Epoch [50/50], Loss: 0.0191,val_loss: 0.0206,val_acc: 0.5276
$ 

f:id:end0tknr:20191012204621p:plain

f:id:end0tknr:20191012204629p:plain

pytorch for pythonによる最適化関数(勾配法) - SGD , Momentum SGD , AdaGrad , RMSprop , AdaDelta , Adam

様々な最適化関数 - SGD , Momentum SGD , AdaGrad , RMSprop , AdaDelta , Adam

qiitaの次のurlが、数式付きで分かりやすいです

Optimizer : 深層学習における勾配法について - Qiita

pytorchによる最適化関数(勾配法)

以下の通り

#!/usr/local/python3/bin/python3
# -*- coding: utf-8 -*-
import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms as transforms
import numpy as np
# from matplotlib import pyplot as plt
import matplotlib
matplotlib.use('Agg')
import matplotlib.pylab as plb


def main():
    losss_dict = {}
    losss_dict["sgd"] = []
    losss_dict["momentum_sgd"] = []
    losss_dict["adadelta"] = []
    losss_dict["adam"] = []
    losss_dict["rmsprop"] = []
    
    for key, value in losss_dict.items():
        print(key)
        losss_dict[key] = calc_loss_list(key)

    plb.figure()
    plb.plot(losss_dict["sgd"], label='sgd')
    plb.plot(losss_dict["momentum_sgd"], label='momentum_sgd')
    plb.plot(losss_dict["adadelta"], label='adadelta')
    plb.plot(losss_dict["adam"], label='adam')
    plb.plot(losss_dict["rmsprop"], label='rmsprop')
    plb.legend()
    plb.grid()

    plb.savefig( '1_2_1.png' )



class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        #nn.Linear(入力次元、出力次元)
        self.lin1 = nn.Linear(in_features=10, out_features=10, bias=False)
    
    def forward(self, x):
        x = self.lin1(x)
        return x



def calc_loss_list(opt_conf):
    loss_list = []
    # データ作成
    x = torch.randn(1, 10)
    w = torch.randn(1, 1)
    y = torch.mul(w, x) +2

    # ネットワーク定義
    net = Net()

    # 損失関数
    criterion = nn.MSELoss()

    # 最適化関数
    if opt_conf == "sgd":
        optimizer = optim.SGD(net.parameters(), lr=0.1)
    elif opt_conf == "momentum_sgd":
        optimizer = optim.SGD(net.parameters(), lr=0.1, momentum=0.9)
    elif opt_conf == "adadelta":
        optimizer = optim.Adadelta(net.parameters(), rho=0.95, eps=1e-04)
    elif opt_conf == "adagrad":
        optimizer = optim.Adagrad(net.parameters())
    elif opt_conf == "adam":
        optimizer = optim.Adam(net.parameters(), lr=1e-1, betas=(0.9, 0.99), eps=1e-09)
    elif opt_conf == "rmsprop":
        optimizer = optim.RMSprop(net.parameters())
    
    # 学習
    for epoch in range(20):
      optimizer.zero_grad()
      y_pred = net(x)

      loss = criterion(y_pred, y)
      loss.backward()

      optimizer.step()

      loss_list.append(loss.data.item())
    return loss_list

    
if __name__ == '__main__':
    main()

↑こう書くと、以下のような画像ファイルが作成されます f:id:end0tknr:20191012134304p:plain

pytorch for python における損失関数 (誤差関数)

損失関数の種類

問題例 損失関数 概要
回帰 nn.MSELoss 平均二乗誤差
nn.L1Loss 平均絶対値誤差
二値分類 nn.BCELoss バイナリ交差エントロピ
nn.BCEWithLogitsLoss ロジット・バイナリ交差エントロピ
多クラス分類 nn.CrossEntropyLoss ソフトマックス交差エントロピ誤差

回帰 - nn.MSELoss - 平均二乗誤差損失関数の種類

{ \Large {
  L(y, t) =  \frac{1}{B} \sum _{i=1}^{B} (y_i - t_i)^2
} }
{ \hspace{6pc}
  ( L: 損失関数 , y: 推論値 , t: 正解データ , B: バッチサイズ )
}

回帰 - nn.L1Loss - 平均絶対値誤差

{ \Large {
  L(y, t) =  \frac{1}{B} \sum _{i=1}^{B} |y_i - t_i |
} }

二値分類 - nn.BCELoss - バイナリ交差エントロピ

{ \Large {
  L(y, t) =  \\
    - \frac{1}{B} \sum_{i=1}^{B}
    [ w_i \{ t_i ・ log(p_i) + (1 - t_i) ・ log(1 - p_i) \} ]
} }
{ \hspace{6pc}
  ( w: 重み , t_i: 正解ラベル(1,0) , p_i: 確率 )
}

二値分類 - nn.BCEWithLogitsLoss - ロジット・バイナリ交差エントロピ

{ \Large {
  L(y, t) = \\
   - \frac{1}{B} \sum_{i=1}^{B}
      [ w_i \{ t_i ・      log( Sigmoid(y_i)) + \\
\hspace{6pc}               (1 - t_i) ・ log(1 - Sigmoid(y_i)) \} ]
} }

「ロジット」とは?

まず、確率( { \Large{ p }} )に対し、 それが起こらない確率との比 ( { \Large{ \frac{p}{1-p} }} )を「オッズ」と呼び、 更にこのlogをとったものをロジットと呼ぶ(以下)。

{ \Large {
  logit(p) = log \left( \frac{p}{1-p} \right) = log(p) - log(1-p)
} }

また、ロジットはロジスティック関数の逆関数でもある

多クラス分類 - nn.CrossEntropyLoss - ソフトマックス交差エントロピ

{ \Large {
  L(y, t) = \\
   - \frac{1}{B} \sum_{i=1}^{B}
      \left[ \frac{ \sum_{k=0}^{N-1} w^k ・t_i^k ・ log( exp(y_i^k) ) }
                   { \sum_{j=0}^{N-1} exp(y_j^k) } \right] = \\
   - \frac{1}{B} \sum_{i=1}^{B}
      \left[ \frac{ \sum_{k=0}^{N-1} w^k ・t_i^k ・y_i^k }
                   { \sum_{j=0}^{N-1} exp(y_j^k) } \right]
} }

pytorchによる例:

#!/usr/local/python3/bin/python3
# -*- coding: utf-8 -*-
import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms as transforms
import numpy as np
from matplotlib import pyplot as plt


def main():
    # MSELoss
    x = torch.randn(4)
    y = torch.randn(4)
    print("x: ",x)
    print("y: ",y)
    criterion = nn.MSELoss()
    loss = criterion(x, y)
    print("MSELoss: ",loss)

    # L1Loss
    criterion = nn.L1Loss()
    loss = criterion(x, y)
    print("L1Loss: ",loss)

    # CrossEntropyLoss
    x = torch.randn(1, 4)
    y = torch.LongTensor([1]).random_(4)
    criterion = nn.CrossEntropyLoss()
    loss = criterion(x, y)
    print("CrossEntropyLoss: ",loss)
    
if __name__ == '__main__':
    main()

↑こう書くと、↓こう表示されます

$ ./foo.py 
x:  tensor([-1.3960, -0.1662, -0.3867, -1.3959])
y:  tensor([ 0.3319,  0.7981,  0.5625, -0.0747])
MSELoss:  tensor(1.6406)
L1Loss:  tensor(1.2407)
CrossEntropyLoss:  tensor(1.4561)

その他参考

pythonによる数値微分 - end0tknr's kipple - 新web写経開発

pytorch for python の自動微分を試す

自動微分とは?

そもそも「自動微分」という用語すら知りませんでした。 微分には「数式微分」「数値微分」「自動部分」があり、 更に自動微分は「前進法 (ボトムアップ型、フォワードモード、狭義の自動微分)」 「後進法 (トップダウン型、リバースモード、高速自動微分)」がある。

前進法と後進法の詳細は、次のurlにあるpwcのページが分かりやすいように思えます。

https://www.pwc.com/jp/ja/knowledge/column/viewpoint/grc-column003.html

pytorchの自動微分

pytorchの自動微分は、後進法らしく backward() として実装されています。

#!/usr/local/python3/bin/python3
# -*- coding: utf-8 -*-
import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms as transforms
import numpy as np
from matplotlib import pyplot as plt


def main():
    # tensor作成. x のみ、requires_grad=False で微分計算なしで作成.
    x = torch.tensor(1, requires_grad=False, dtype=torch.float32)
    w = torch.tensor(2, requires_grad=True,  dtype=torch.float32)
    b = torch.tensor(3, requires_grad=True,  dtype=torch.float32)
    
    # 関数?作成
    y = w * x + b    # y = 1 * 2 + 3
    
    print(y)
    
    # 勾配計算
    y.backward()

    # 勾配を確認
    print("x.grad:", x.grad) #xのみ微分計算なし
    print("w.grad:", w.grad)
    print("b.grad:", b.grad)

    
if __name__ == '__main__':
    main()

具体的には↑こう書くと、↓こう表示されます

$ ./foo.py 
tensor(5., grad_fn=<AddBackward0>)
x.grad: None
w.grad: tensor(1.)
b.grad: tensor(1.)

上記以外の自動微分については、次のurlが参考になります。

PyTorch (2) 自動微分 - 人工知能に関する断創録

install pytorch from source to python3.7 + centos8

メモ。

以下のurlにて、自身の環境に応じたインストール方法を、コマンドライン・レベルで示されます。

https://pytorch.org/

今回は、ソースからインストールですので、以下のようになります。

$ git clone --recursive https://github.com/pytorch/pytorch
$ cd pytorch
$ git submodule sync
$ git submodule update --init --recursive

$ curl -kL https://bootstrap.pypa.io/get-pip.py | sudo /usr/local/python3/bin/python3

$ sudo /usr/local/python3/bin/pip install pyyaml

# 私の手元環境では、3-4h程、要しました
$ sudo /usr/local/python3/bin/python3 setup.py install

# 追加でtorchvisionも
$ sudo /usr/local/python3/bin/pip install torchvision
#!/usr/local/python3/bin/python3
# -*- coding: utf-8 -*-
from __future__ import print_function
import getopt
import sys
import torch


def main():
    x = torch.rand(5, 3)
    print(x)

if __name__ == '__main__':
    main()

↑こう書くと、↓こう出力されます

[end0tknr@cent80 tmp]$ ./foo.py 
tensor([[0.0855, 0.8716, 0.3512],
        [0.7714, 0.7623, 0.8344],
        [0.8667, 0.1638, 0.0306],
        [0.8024, 0.4010, 0.4024],
        [0.9326, 0.5903, 0.6700]])

更にネットワーク定義(nn)は、次のように複数の書き方があります

#!/usr/local/python3/bin/python3
# -*- coding: utf-8 -*-
import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torchvision.transforms as transforms
import numpy as np
from matplotlib import pyplot as plt


def main():
    print(make_nn_model_1())
    print(make_nn_model_2())
    print(make_nn_model_3())
    print(make_nn_model_4())
    print(make_nn_model_5())
    print(make_nn_model_6())

    
def make_nn_model_1():
    model = nn.Sequential(
        nn.Conv2d(1,20,5),
        nn.ReLU(),
        nn.Conv2d(20,64,5),
        nn.ReLU()
    )
    return model

def make_nn_model_2():
    model = torch.nn.Sequential()
    model.add_module("conv1", nn.Conv2d(1,20,5))
    model.add_module("relu1", nn.ReLU())
    model.add_module("conv2", nn.Conv2d(20,64,5))
    model.add_module("relu2", nn.ReLU())
    model
    return model

def make_nn_model_3():
    from collections import OrderedDict
    model = nn.Sequential(OrderedDict([
        ('conv1', nn.Conv2d(1,20,5)),
        ('relu1', nn.ReLU()),
        ('conv2', nn.Conv2d(20,64,5)),
        ('relu2', nn.ReLU())
    ]))
    return model

def make_nn_model_4():
    import torch.nn as nn
    import torch.nn.functional as F

    class Model(nn.Module):
        def __init__(self):
            super(Model, self).__init__()
            self.conv1 = nn.Conv2d(1, 20, 5)
            self.conv2 = nn.Conv2d(20, 64, 5)
            
        def forward(self, x):
            x = F.relu(self.conv1(x))
            return F.relu(self.conv2(x))
    model = Model()
    return model

def make_nn_model_5():
    class Model(nn.Module):
        def __init__(self):
            super(Model, self).__init__()
            self.convs = nn.ModuleList([nn.Conv2d(1, 20, 5), nn.Conv2d(20, 64, 5)])

        def forward(self, x):
            for i, l in enumerate(self.convs):
                x = l(x)
            return x
    model = Model()
    return model

def make_nn_model_6():
    class Model(nn.Module):
        def __init__(self):
            super(Model, self).__init__()
            self.convs = nn.ModuleDict({'conv1' : nn.Conv2d(1, 20, 5),
                                        'conv2' : nn.Conv2d(20, 64, 5)})

        def forward(self, x):
            for l in self.convs.values():
                x = l(x)
            return x
    model = Model()
    return model

    
if __name__ == '__main__':
    main()

↑こう書くと、↓こう出力されます

Sequential(
  (0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
  (1): ReLU()
  (2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
  (3): ReLU()
)
Sequential(
  (conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
  (relu1): ReLU()
  (conv2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
  (relu2): ReLU()
)
Sequential(
  (conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
  (relu1): ReLU()
  (conv2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
  (relu2): ReLU()
)
Model(
  (conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
)
Model(
  (convs): ModuleList(
    (0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
    (1): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
  )
)
Model(
  (convs): ModuleDict(
    (conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
    (conv2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
  )
)

centos8 + perl5.30 の組合せで、mod_perlの configure時に error - ✕:usethreads ○:useithreads + usemultiplicity

perl 5.30 を

$ wget https://www.cpan.org/src/5.0/perl-5.30.0.tar.gz
$ tar -xvf perl-5.30.0.tar.gz
$ cd perl-5.30.0
$ ./Configure -Dusethreads -de -Accflags='-fPIC'
$ make
$ make test
$ sudo make install

で installし

その後、mod_perl を install しようとしたら

$ vi /xing/local/httpd/bin/apxs
old) #!/replace/with/path/to/perl/interpreter -w
new) #!/xing/local/perl/bin/perl –w

$ wget http://www.apache.org/dyn/closer.cgi/perl/mod_perl-2.0.11.tar.gz
$ tar -zxvf mod_perl-2.0.11.tar.gz
$ cd mod_perl-2.0.11
$ /xing/local/perl/bin/perl Makefile.PL MP_APXS=/xing/local/httpd/bin/apxs
    :
Configuring Apache/2.4.41 mod_perl/2.0.11 Perl/v5.30.0
[  error] mod_perl does not currently support multiplicity without ithreads.
[  error] Please recompile Perl with -Duseithreads and -Dusemultiplicity

のようなエラー発生。

どうやら perl の Configureで使用した「-Dusethreads」は古いらしく、 最近は「-Duseithreads -Dusemultiplicity」を使うらしい。

なので、perl 5.30 を次のように再installして解消。

$ ./Configure -des \
            -Dprefix=/xing/local/perl -Duseithreads -Dusemultiplicity \
            -Accflags='-fPIC'
$ make
$ make test
$ sudo make install