end0tknr's kipple - web写経開発

太宰府天満宮の狛犬って、妙にカワイイ

excel 97-2003(xls)作成のたvbaマクロを excel 2016 で実行したら、実行エラー '3706'

excel 97-2003(xls)作成のたvbaマクロを excel 2016 で実行したら、

実行エラー '3706':

プロバイダーが見つかりません。正しくインストールされていない可能性があります。

というエラー。

どうやら、win10では、「Microsoft.Jet.OLEDB.4.0」が利用できないらしく、 「Microsoft.ACE.OLEDB.12.0」への置き換えが必要らしい。

  • old: 'Const DB_PROVIDER = "Microsoft.Jet.OLEDB.4.0;"
  • new: Const DB_PROVIDER = "Provider=Microsoft.ACE.OLEDB.12.0;"

iisサーバのログ書式

iisサーバでは、logファイルの先頭がコメントになっており、 出力項目は、そちらに記載されています。

$ head -4 u_ex190906.log
#Software: Microsoft Internet Information Services 7.5
#Version: 1.0
#Date: 2019-09-06 00:00:00
#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) sc-status sc-substatus sc-win32-status time-taken
Fields 内容
date 日付
time 時刻
s-ip サーバIP
cs-method メソッド
cs-uri-stem URL (PATH)
cs-uri-query URL QUERY (PARAMEMTER)
s-port サーバポート
cs-username ユーザ名
c-ip ユーザIP
cs(User-Agent) ユーザエージェント
sc-status HTTPレスポンスコード
sc-substatus HTTPレスポンスコード・サブ?
sc-win32-status WINDOWS終了コード
time-taken 処理時間

参考url

https://www.ibm.com/support/knowledgecenter/ja/SSKMKU/com.ibm.dsm.doc/t_DSM_guide_Microsoft_IIS_cfgusingProtocol.html

SQL::Statement depends on Text::Soundex for perl

SQL::Statement を installする際、 Text::Soundex という初めて見る perl module を見かけた。

https://metacpan.org/pod/Text::Soundex

上記 url の DESCRIPTION によれば、

Soundex is a phonetic algorithm for indexing names by sound,
as pronounced in English.

らしく、Soundex アルゴリズムについては、次のurlが参考になります。

が、 SQL::Statement は、Text::Soundex を何の為に利用しているんでしょうね?

そこまでは調べていません。

install openldap 2.4.48 from src to centos8 for openam

centos8 $ sudo yum install openldap-servers -> No match for argument: openldap-servers - end0tknr's kipple - 新web写経開発

先日のエントリに関連しますが、 centos8 / rhel8 で openldap-servers がリポジトリから削除され、 yum install できなくなりました。

これにより、OpenAM + OpenLDAP 構築において yum install openldap-servers が利用できなくなった為、 OpenAM用 OpenLDAP from source code の build 手順を記載しておきます。

download openldap and extract

wget ftp://ftp.openldap.org/pub/OpenLDAP/openldap-release/openldap-2.4.48.tgz
tar -xvf openldap-2.4.48.tgz
cd openldap-2.4.48.tgz

install required software

less README
  :
REQUIRED SOFTWARE
  :
    Base system (libraries and tools):
        Standard C compiler (required)
        Cyrus SASL 2.1.21+ (recommended)
        OpenSSL 0.9.7+ (recommended)
        Reentrant POSIX REGEX software (required)
  :
    SLAPD:
        BDB and HDB backends require Oracle Berkeley DB 4.4 - 4.8,
    or 5.0 - 5.1.  It is highly recommended to apply the
        patches from Oracle for a given release.

↑の為、Cyrus SASL , OpenSSL , Berkeley DB を install。

$ wget https://github.com/cyrusimap/cyrus-sasl/releases/download/cyrus-sasl-2.1.27/cyrus-sasl-2.1.27.tar.gz
$ tar -xvf cyrus-sasl-2.1.27.tar.gz
$ cd cyrus-sasl-2.1.27
$ ./configure --prefix=/usr/local/cyrus_sasl
$ make
$ make check
$ sudo make install

$ wget https://www.openssl.org/source/openssl-1.1.1d.tar.gz
$ tar -xvf openssl-1.1.1d.tar.gz
$ cd openssl-1.1.1d
$ ./config --prefix=/usr/local/openssl_1_1_1
$ make
$ make test
$ sudo make install

$ wget https://download.oracle.com/berkeley-db/db-5.1.29.tar.gz
$ tar -xvf db-5.1.29.tar.gz
$ cd db-5.1.29
$ cd db-5.1.29/build_unix
$ ../dist/configure --prefix=/usr/local/BerkeleyDB.5.1
$ make
$ sudo make install

install openldap

まず、先程、installした Cyrus SASL , OpenSSL , Berkeley DB の場所を OpenLDAPが把握できるよう、環境変数を設定。

$ export CPPFLAGS="-I/usr/local/BerkeleyDB.5.1/include -I/usr/local/cyrus_sasl/include -I/usr/local/openssl_1_1_1/include"
$ export LDFLAGS="-L/usr/local/BerkeleyDB.5.1/lib -L/usr/local/cyrus_sasl/lib -L/usr/local/openssl_1_1_1/lib"
$ export LD_LIBRARY_PATH="/usr/local/BerkeleyDB.5.1/lib:/usr/local/cyrus_sasl/lib:/usr/local/openssl_1_1_1/lib"
$ ./configure --prefix=/usr/local/openldap \
            --enable-cleartext=yes \
            --enable-crypt=yes \
            --enable-rwm=yes \
            --enable-overlays=yes \
            --enable-modules=yes \
            --enable-monitor=yes \
            --enable-memberof=mod \
            --enable-syncprov=mod \
            --enable-unique=mod \
            --enable-ppolicy=mod \
            --enable-collect=mod \
            --with-tls=openssl \
            --with-cyrus-sasl
$ make depend
$ make
$ make test
$ sudo make install

configu OpenLDAP

基本設定

$ sudo groupadd ldap
$ sudo useradd -g ldap ldap

$ cd /usr/local/openldap

# slapd.conf は、後程、初期設定(slapd.d作成)の為に編集します
$ sudo chown root.ldap etc/openldap/slapd.conf
$ sudo chmod 644 etc/openldap/slapd.conf

# var 以下には、openldap のデータが保存されます
$ sudo chown ldap.ldap -R var

$ sudo cp -p var/openldap-data/DB_CONFIG.example var/openldap-data/DB_CONFIG
$ sudo vi var/openldap-data/DB_CONFIG

  # Berkeley DB の cache size 変更
  org) set_cachesize 0 268435456 1
  new) set_cachesize 0 805306368 1

  # Berkeley DB への更新が発生後、削除可能であれば削除
  set_flags DB_LOG_AUTOREMOVE

syslog

$ sudo vi /etc/rsyslog.conf
  ↓追加
  #openldap slapd log
  local4.*       /var/log/slapd/slapd.log

$ sudo systemctl restart rsyslog

slapd.conf / slapd.d

OpenLDAPの設定は、以前からある slapd.conf でなく slapd.d が推奨されており、 今回は、slapd.conf を slapd.d 用の初期データとして利用します。

# 暗号化されたパスワードを表示させます。
$ /usr/local/openldap/sbin/slappasswd
New password: 
Re-enter new password: 
{SSHA}cwqMnl8H6x8nxa3jphm3+LbOoUUvHkit
↑この文字列は後から利用する為、メモしておくように

$ cd /usr/local/openldap/
$ sudo vi etc/openldap/slapd.conf

# 後程、slaptest コマンドにより slapd.conf → slapd.d を自動作成しますが
# olcDatabase={1}monitor.ldif , olcDatabase={2}bdb.ldif にある {1}{2}等の
# 連番は 「database  ~」の記載順になっています。
↓追加
> include         /usr/local/openldap/etc/openldap/schema/cosine.schema
> include         /usr/local/openldap/etc/openldap/schema/inetorgperson.schema
> include         /usr/local/openldap/etc/openldap/schema/nis.schema
> include         /usr/local/openldap/etc/openldap/schema/ppolicy.schema

↓追加
> modulepath    /usr/local/openldap/libexec/openldap
> # moduleload memberof.la
> moduleload syncprov.la
> moduleload unique.la
> moduleload ppolicy.la
> moduleload auditlog.la
↓追加 ( 動的更新である OLC(On-Line Configuration) を利用する為 )
> database  config
> rootdn    cn=admin,cn=config
> access to *
>        by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
>        by * break
↓追加 ( monitor 閲覧は manager のみ)
> database monitor
> access to *
>        by dn.exact="cn=Manager,dc=sso,dc=xmart-helm,dc=com" read
>        by * none
↓変更や削除
< database      mdb
> database      bdb
< maxsize       1073741824
< suffix "dc=my-domain,dc=com"
> suffix "dc=sso,dc=xmart-helm,dc=com"
< rootdn "cn=Manager,dc=my-domain,dc=com"
> rootdn "cn=Manager,dc=sso,dc=xmart-helm,dc=com"
< rootpw secret
> rootpw {SSHA}cwqMnl8H6x8nxa3jphm3+LbOoUUvHkit

# slapd.conf の書式check
$ sudo /usr/local/openldap/sbin/slaptest -u \
    -f /usr/local/openldap/etc/openldap/slapd.conf
config file testing succeeded

# slapd.conf による起動test
$ sudo tail -f /var/log/slapd/slapd.log
$ sudo /usr/local/openldap/libexec/slapd \
      -u ldap -f /usr/local/openldap/etc/openldap/slapd.conf
$ ps -ef | less
$ sudo kill $SLAPD_PROC_NO      

# 上記までに問題ないことが確認できた為、slapd.conf をベースに slapd.d を作成
$ sudo mkdir /usr/local/openldap/etc/openldap/slapd.d
$ sudo chown ldap.ldap /usr/local/openldap/etc/openldap/slapd.d
$ sudo -u ldap /usr/local/openldap/sbin/slaptest \
    -f /usr/local/openldap/etc/openldap/slapd.conf \
    -F /usr/local/openldap/etc/openldap/slapd.d
# ↑こちらが自動生成 ↓こちらが作成されたslapd.dのtest
$ sudo /usr/local/openldap/sbin/slaptest -u \
    -F /usr/local/openldap/etc/openldap/slapd.d
config file testing succeeded

# slapd.d 作成により、こちらを参照するはずですが
# 念の為、slapd.conf , slapd.ldif を退避
# ( slapd.ldif の利用のされ方は理解していません )
$ sudo mv /usr/local/openldap/etc/openldap/slapd.conf \
          /usr/local/openldap/etc/openldap/slapd.conf.20191101
$ sudo rm /usr/local/openldap/etc/openldap/slapd.ldif

# slapd.d による起動test
$ sudo tail -f /var/log/slapd/slapd.log
$ sudo /usr/local/openldap/libexec/slapd \
      -u ldap -F /usr/local/openldap/etc/openldap/slapd.d
      
$ ps -ef | less
$ sudo kill $SLAPD_PROC_NO

OpenLDAP 自動起動

以下は、「入門LDAP/OpenLDAPディレクトリサービス導入・運用ガイド第3版」に 記載の起動scriptを参考にしています。

$ cd /lib/systemd/system
$ sudo vi slapd.service
  
  [Unit]
  Description=OpenLDAP Server Daemon
  After=syslog.target network.target
   
  [Service]
  Type=forking
  PIDFile=/usr/local/openldap/var/run/slapd.pid
  #「-h」は待受アドレスの指定. 
  #   ldap:/// -> ldap port(389).  ldapi:/// -> UnixDomainSocket
  ExecStart=/usr/local/openldap/libexec/slapd  -u ldap -h "ldap:/// ldapi:///"
  ExecReload=/bin/kill -HUP  $MAINPID
  ExecStop=/bin/kill   -TERM $MAINPID
  
  [Install]
  WantedBy=multi-user.target

$ sudo systemctl enable slapd.service
$ sudo systemctl start  slapd.service

OpenAM 用 スキーマ投入

https://github.com/hiroyuki-sato/openldap-schemas-for-openam

上記urlにある「cn={99}openam.ldif」を投入します。

私の手元環境において、実際には「cn={99}openam.ldif」を少々、変更し 独自カラム追加等を行っています。

$ sudo  /usr/local/openldap/bin/ldapmodify \
     -Y EXTERNAL -H ldapi:// -D cn=config \
     -f cn={99}openam.ldif

OpenAM 用 データストア作成

schemaが完成すれば、データストア作成 を行い、完了です。

$ vi 2_CREATE_DATA_STORE.ldif

dn: dc=sso,dc=xmart-helm,dc=com
objectclass: dcObject
objectclass: organization
o:  xmart-helm
dc: sso

dn: cn=Manager,dc=sso,dc=xmart-helm,dc=com
objectclass: organizationalRole
cn: Manager

dn: ou=People,dc=sso,dc=xmart-helm,dc=com
objectClass: organizationalUnit
ou: People

dn: ou=Groups,dc=sso,dc=xmart-helm,dc=com
objectClass: organizationalUnit
ou: Groups

dn: cn=sidNext,dc=sso,dc=xmart-helm,dc=com
cn: sidNext
uidNumber: 1
objectClass: uidNext

dn: cn=defaultpolicy,dc=sso,dc=xmart-helm,dc=com
objectClass: top
objectClass: device
objectClass: pwdPolicyChecker
objectClass: pwdPolicy
cn: defaultpolicy
pwdAttribute: userPassword

dn: cn=HelpDesk,dc=sso,dc=xmart-helm,dc=com
objectclass: organizationalRole
cn: HelpDesk

dn: cn=HelpDesk,cn=HelpDesk,dc=sso,dc=xmart-helm,dc=com
objectClass: person
cn: HelpDesk
sn: HelpDesk
userPassword: {SSHA}cwqMnl8H6x8nxa3jphm3+LbOoUUvHkit


$ /usr/local/openldap/bin/ldapadd -x -w sasa24ki \
  -D cn=Manager,dc=sso,dc=xmart-helm,dc=com \
  -f /home/end0tknr/tmp/LDAP/2_CREATE_DATA_STORE.ldif


# 上記の確認
$ /usr/local/openldap/bin/ldapsearch -x -b "dc=sso,dc=xmart-helm,dc=com" \
  "(objectclass=*)"

centos8 $ sudo yum install openldap-servers -> No match for argument: openldap-servers

「はて?」と思ったら、 openldap-servers は SSLv3に伴う脆弱性(POODLE)の影響で、 非推奨となり、centos8 標準のリポジトリから外れたみたい。

$ sudo yum install openldap-servers
[sudo] password for end0tknr: 
Last metadata expiration check: 0:07:11 ago on Sat 02 Nov 2019 08:16:08 PM JST.
No match for argument: openldap-servers
Error: Unable to find a match

RDKit + python で、化合物の構造式を描く

描画だけの graphviz でなく、 部分構造探索や(フィンガープリントによる)類似性判定を利用したかったので、お試し。

試してみたものの、化学式以外のグラフ構造を描くことができなかった為、 今後、しばらくは使用しない気がします。

RDKit + python の install は anaconda で

まずは anaconda

普段であれば、source から build (compile)しますが、 軽いインターネット検索による情報からでは上手くいかず、今回は anaconda を利用。

※「Anaconda3-2019.10-Linux-x86_64.sh」が最新でしたが、 私の環境であるcentos8では、インストーラであるこの.shが動作しなかったので 以下のverを利用しています。

※ また、anacondaのインストーラにより .bashrcに環境変数設定等が追記されます

$ wget https://repo.anaconda.com/archive/Anaconda3-2019.07-Linux-x86_64.sh
$ chmod 755 Anaconda3-2019.07-Linux-x86_64.sh
$ ./Anaconda3-2019.10-Linux-x86_64.sh 
   :
PREFIX=/home/end0tknr/anaconda3

$ cat ~/.bashrc
   :
__conda_setup="$('/home/end0tknr/anaconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
    eval "$__conda_setup"
else
    if [ -f "/home/end0tknr/anaconda3/etc/profile.d/conda.sh" ]; then
        . "/home/end0tknr/anaconda3/etc/profile.d/conda.sh"
    else
        export PATH="/home/end0tknr/anaconda3/bin:$PATH"
    fi
fi
unset __conda_setup
# <<< conda initialize <<<

次の condaによる環境作成と rdkit のinstall

## rdkit のchannel から my-rdkit-env という名称の環境を作成し
## rdkit を install
$ conda create -c rdkit -n my-rdkit-env rdkit

## my-rdkit-env 環境を起動
$ conda activate my-rdkit-env

## my-rdkit-env 環境を停止
$ conda deactivate

## anaconda install 以降、linux へのlogin時に condaが起動する為
## 以下のコマンドにより、これを 有効化/無効化
$ conda config --set changeps1 False
$ conda config --set changeps1 True

RDKit による化学式の画像作成

以下のscriptで化学構造式をpngファイルに描画できます。

#!/home/end0tknr/anaconda3/bin/python3
# -*- coding: utf-8 -*-

from rdkit import Chem
from rdkit.Chem import Draw


def main():
    save_image_to_file()
    

def save_image_to_file():
    mol_0 = Chem.MolFromSmiles("O=C(N)c1ccc[nH0]c1")
    Draw.MolToFile(mol_0,'mol_0_image.png')

    mol_1 = Chem.MolFromSmiles("[LDK]")
#    mol_1 = Chem.MolFromSmiles("O=C")
    if mol_1 == None:
        print("invalid mol code")
        return
    
    Draw.MolToFile(mol_1,'mol_1_image.png')
    
    
if __name__ == '__main__':
    main()
    

f:id:end0tknr:20191102155621p:plain

gcc の compile option -fPIC や ldd

メモ。 次のurlにまとめられている通り。

「-fPIC」は、Position-Independent Code の略で、shared object作成時のoption。

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