化学系エンジニアがAIを学ぶ

PyTorchでディープラーニング、強化学習を学び、主に化学工学の問題に取り組みます

メモ: Python defaultdict の使い方

辞書型は存在しないキーを参照しようとするとエラーになるが、

>>> d = {}
>>> d['a']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'a'

defaultdictは存在しないキーを参照すると、自動的にそのキーのデータを生成する

>>> import collections
>>> dd = collections.defaultdict(int)
>>> dd['a']
0
>>> dd
defaultdict(<class 'int'>, {'a': 0})

自動的に生成されるデータ値はdefaultdict()の()内の関数で初期化される。上の場合はint()が実行される(int()はint型の0が返る)。例えば次のようにすると'ああああ'で初期化される。

>>> func = lambda: 'ああああ'
>>> dd = collections.defaultdict(func)
>>> dd[0]
'ああああ'
>>> dd['い']
'ああああ'
>>> dd
defaultdict(<function <lambda> at 0x7fbb199c5dc0>, {0: 'ああああ', 'い': 'ああああ'})
辞書型に次々に新しいキーのデータを追加する必要がある場合に便利と思われる。

OpenAI Gym 基本的な使い方のメモ

はじめに

すぐに使い方を忘れてネットで調べているので、基本的な使い方をメモしておく。ObservationWrapperの使用例も記載する。

基本的な使い方

CartPole

環境の構築(環境インスタンス生成)

import gym
env = gym.make("CartPole-v0")

環境の初期化。観測値が返る

env.reset()
>>> array([ 0.01308375, -0.00076966,  0.03593438, -0.04269914])

actionの数の確認

env.action_space 
>>> Discrete(2)  # Discrete型

env.action_space.n
>>> 2  # 数値で取り出せる

actionをランダムサンプリング

env.action_space.sample()
>>> 0

observation_spaceの型、上下限値、(次元)、数値型

env.observation_space
>>> Box(-3.4028234663852886e+38, 3.4028234663852886e+38, (4,), float32)

observation_spaceの次元の数を数値で得る

env.observation_space.shape[0]
>>> 4

observation をランダムサンプリング

env.observation_space.sample()
>>> array([ 3.4541728e+00, -2.6457083e+38, -2.0181824e-01, -5.4519964e+37], dtype=float32)

環境に対しactionし、その後の観測値、報酬、終了判定フラグ、追加情報 を得る

action = 0
env.step(action)
>>> (array([ 0.01306835, -0.19638798,  0.0350804 ,  0.26110135]), 1.0, False, {})

FrozenLake

環境の構築と初期化

env = gym.make("FrozenLake-v0")
env.reset()
>>> 0  # 観測値は数値1つ

action_space と observation_space

env.action_space
>>> Discrete(4)

env.observation_space
>>> Discrete(16)  # 観測値はDiscrete型

ObservationWrapper

FrozenLakeのobservation_spaceはDiscrete型で0 - 15の数値が1つ返るが、これをOne-hotにエンコーディングしたいときにObservationWrapperを利用すると便利である。

参考: Deep-Reinforcement-Learning-Hands-On-Second-Edition/02_frozenlake_naive.py at master · PacktPublishing/Deep-Reinforcement-Learning-Hands-On-Second-Edition · GitHub

observation_spaceをOne-hotなBox型に変えるWrapper

import gym.spaces
import numpy as np

class OneHotWrapper(gym.ObservationWrapper):
    def __init__(self, env):
        super(OneHotWrapper, self).__init__(env)
        shape = (env.observation_space.n,)
        self.observation_space = gym.spaces.Box(0.0, 1.0, shape, dtype=np.float32)
    
    def observation(self, observation):
        res = np.copy(self.observation_space.low)  # 観測データの最小値を取る、つまり 0
        res[observation] = 1.0
        return res

環境の構築と初期化、および環境へのaction実施。観測値がOne-hotになっている

env_oh = OneHotWrapper(gym.make("FrozenLake-v0"))
env_oh.reset()
>>> array([1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], 
           dtype=float32)

action = 1
env_oh.step(action)
>>> (array([0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       dtype=float32),
       0.0,
       False,
       {'prob': 0.3333333333333333})

メモ: Miniconda インストール on macOS

はじめに

Anacondaは便利だが使うことのないパッケージがかなり大量に入っていて、ストレージに余裕のない状況ではつらいのでMinicondaを入れてみる。

準備

こちらに公式のinstructionがあるので一読しておく。

Installation — conda 4.9.2.post24+e37cf84a documentation

インストール

Macについてはこちらに手順が記載されている。

Installing on macOS — conda 4.9.2.post24+e37cf84a documentation

インストーラをインストールしたいフォルダに置き、ターミナルで下記を実行する。

bash Miniconda3-latest-MacOSX-x86_64.sh

途中でライセンスの同意と初期設定の実施要否を聞かれるのでyes を回答する。これでMinicondaへのPATHが通される(.bash_profile にPATHの内容が追加される)。

動作確認

ターミナルを再起動して、例えば `conda list` を実行するとインストールされているパッケージが表示され、きちんとMinicondaがインストールされていることを確認できる。

> conda list
# packages in environment at /Users/miniconda3:
#
# Name                    Version                   Build  Channel
brotlipy                  0.7.0           py38h9ed2024_1003  
ca-certificates           2020.10.14                    0  
certifi                   2020.6.20          pyhd3eb1b0_3  
cffi                      1.14.3           py38h2125817_2  
chardet                   3.0.4           py38hecd8cb5_1003  
conda                     4.9.2            py38hecd8cb5_0  
conda-package-handling    1.7.2            py38h22f3db7_0  
cryptography              3.2.1            py38hbcfaee0_1  
idna                      2.10                       py_0  
libcxx                    10.0.0                        1  
libedit                   3.1.20191231         h1de35cc_1  
libffi                    3.3                  hb1e8313_2  
ncurses                   6.2                  h0a44026_1  
openssl                   1.1.1h               haf1e3a3_0  
pip                       20.2.4           py38hecd8cb5_0  
pycosat                   0.6.3            py38h1de35cc_1  
pycparser                 2.20                       py_2  
pyopenssl                 19.1.0             pyhd3eb1b0_1  
pysocks                   1.7.1                    py38_1  
python                    3.8.5                h26836e1_1  
python.app                2                       py38_10  
readline                  8.0                  h1de35cc_0  
requests                  2.24.0                     py_0  
ruamel_yaml               0.15.87          py38haf1e3a3_1  
setuptools                50.3.1           py38hecd8cb5_1  
six                       1.15.0           py38hecd8cb5_0  
sqlite                    3.33.0               hffcf06c_0  
tk                        8.6.10               hb0a8c7a_0  
tqdm                      4.51.0             pyhd3eb1b0_0  
urllib3                   1.25.11                    py_0  
wheel                     0.35.1             pyhd3eb1b0_0  
xz                        5.2.5                h1de35cc_0  
yaml                      0.2.5                haf1e3a3_0  
zlib                      1.2.11               h1de35cc_3 

終わりに

インストール後のサイズは300MB強となった。必要なパッケージを入れたらもう少し大きくなるかもしれないが、Anacondaはたしか数GBあったはず。。。

メモ: ベイズ推定 MCMC法のためのPyStanの使い方

はじめに

ベイズ推定で用いるMCMC法の計算をするためのPyStanの使いかたについて簡単な例とともにメモを残す。

問題

平均値既知(10)の標準偏差不明の20個のデータから、標準偏差の値(確率分布)を推定する。

やり方

jupyter notebook使用を想定。

準備

import numpy as np
import matplotlib.pyplot as plt
import pystan
import arviz
%matplotlib inline
np.random.seed(1)

問題用のデータ生成。平均10、標準偏差0.1としてデータ20個作る。このデータから標準偏差を推定する。

average = 10
sigma = 0.1
data_num = 20
data = np.ones(data_num)*average + np.random.randn(data_num)*sigma
In [ ]: data

Out[ ]:
array([10.14621079,  9.79398593,  9.96775828,  9.96159456, 10.11337694,
        9.89001087,  9.98275718,  9.91221416, 10.00422137, 10.05828152])

PyStanのモデル

データ数N、データY、推定するパラメータ(標準偏差)sigma。モデル(分布の式)は正規分布。PyStanの正規分布はnormal(平均, 標準偏差)と記載。

stan_model = """
data {
  int N;
  real Y[N];
}

parameters {
  real<lower=0> sigma;
}

model {
  for (n in 1:N){
  Y[n] ~ normal(10, sigma);
  }
}
"""

コンパイル

これでコンパイルが始まる(数十秒〜数分かかる)。上のモデルの記載内容が間違っているとエラーが出るので注意。

sm = pystan.StanModel(model_code=stan_model)

データセット

stan_data = {'N':data.shape[0], 'Y':data}

計算

繰り返し数2000、バーンイン期間(回数)500、同時計算するMCMCの数3。ランダムシードは固定。

fit = sm.sampling(data=stan_data, iter=2000, warmup=500, chains=3, seed=1)

結果

真値0.1に対し、推定値の中心値は0.12。

In [ ]: fit

Out [ ]:
Inference for Stan model: anon_model_c31d538a0cfaeb031647a7bdeb16c047.
3 chains, each with iter=2000; warmup=500; thin=1; 
post-warmup draws per chain=1500, total post-warmup draws=4500.

        mean se_mean     sd   2.5%    25%    50%    75%  97.5%  n_eff   Rhat
sigma   0.12  9.3e-4   0.03   0.07   0.09   0.11   0.13   0.19   1198    1.0
lp__   15.11    0.02   0.73  13.13  14.92  15.39  15.57  15.63   1231    1.0

Samples were drawn using NUTS at Sun Jun 21 11:36:18 2020.
For each parameter, n_eff is a crude measure of effective sample size,
and Rhat is the potential scale reduction factor on split chains (at 
convergence, Rhat=1).
arviz.plot_trace(fit)

f:id:schemer1341:20200621151856p:plain

参考

MCMC法の理屈を知らなくても計算はできるが、理屈は知っておいたほうがよい。以下の図書とオンライン講座が 参考になった。

www.amazon.co.jp

www.udemy.com

CNN(Convolutional Neural Network)を用いた画像識別の簡単な例

はじめに

CNNによる画像識別の簡単な例として、下記の波形図の1つ目のピークが高いか2つ目のピークが高いかの識別をPyTorchを使って試みる。

f:id:schemer1341:20200223215448p:plain:w500

データの準備

画像をCNNで使える配列の形に変換する方法はいくつかあると思うが、ここではpillowを用いた。波形の画像データがフォルダ「testfig」に置いてあるものとして、下記内容でデータを準備した。 (ここでは識別のラベルを、1つ目のピークが高い→0、2つ目のピークが高い→1とし、ファイル名を例えば0-23.pngのように(ラベル)-(画像番号).pngの形式として、ファイル名からラベルを読み込む形としている)

画像データ

ここで気をつけないといけないのは配列の形状である。PyTorchのCNNの入力の形状[バッチサイズ, チャンネル数, 高さ(ピクセル), 幅(ピクセル)]に合わせないといけない。チャンネル数は画像の奥行き(または深さ)であり、画像の場合は色に対応する。チャンネル数は例えばカラーならR, G, Bの3つ、モノクロなら1つ、などになる。

import glob
from PIL import Image
import numpy as np

# フォルダ testfig においた画像ファイルのリストを取得
image_path = "./testfig"
files = glob.glob(image_path + "/*.png")

x = []
y = []

for filename in files:
    # 画像ファイルをndarrayに変換する
    im = Image.open(filename)
    im = im.convert("1")  # モノクロ画像に変換
    im = im.resize((28, 28))  # 28x28にリサイズ
    im = np.array(im).astype(int)  # intのarrayに変換
    x.append([im])  # CNNの入力に合うshapeとなるよう注意

    # ファイル名から正解ラベルを取得
    label = int(filename[len(image_path)+1])
    y.append(label)

x = np.array(x)  # x.shape (画像数, 1, 28, 28)
y = np.array(y)  # y.shape (画像数,)


# 訓練データとテストデータに分ける
x_train, x_test, y_train, y_test = \
    train_test_split(x, y, test_size=0.2, shuffle=False)

# テンソルに変換
x_train = torch.FloatTensor(x_train)
y_train = torch.LongTensor(y_train)
x_test = torch.FloatTensor(x_test)
y_test = torch.LongTensor(y_test)

# Dataloaderを準備
train_dataloader = torch.utils.data.TensorDataset(x_train, y_train)
train_dataloader = torch.utils.data.DataLoader(train_dataloader, batch_size=4)

ニューラルネットワークの定義

ここでは、畳み込み層→プーリング層→畳み込み層→プーリング層→全結合層→全結合層→出力層という構造とした。最初の全結合層への入力はその直前の層の出力を一次元に変換する必要がある。直前の層の出力サイズの計算は例えばこちら(定番のConvolutional Neural Networkをゼロから理解する - DeepAge)が参考になるが、ここでは高さ・幅が4x4の16チャンネルとなっており、Numpyのviewメソッドを用いて形状の変換を行っている。フィルタ数やフィルタサイズはPyTorchのチュートリアル(Training a Classifier — PyTorch Tutorials 1.4.0 documentation)を参考に適当に決めた。

from sklearn.model_selection import train_test_split
import torch
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, 6, 5)  # チャンネル1, フィルタ6, フィルタサイズ5x5
        self.pool1 = nn.MaxPool2d(2, 2)  # 2x2のプーリング層
        self.conv2 = nn.Conv2d(6, 16, 5)  # チャンネル6, フィルタ16, フィルタサイズ5x5
        self.pool2 = nn.MaxPool2d(2, 2)

        # これまでの畳込みとプーリングで、16チャンネルの4x4が入力サイズ
        self.fc1 = nn.Linear(16 * 4 * 4, 64)  # fc: fully connected
        self.fc2 = nn.Linear(64, 16)
        self.fc3 = nn.Linear(16, 2)

    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = self.pool1(x)
        x = F.relu(self.conv2(x))
        x = self.pool2(x)
        x = x.view(-1, 16 * 4 * 4)  # [(バッチサイズ), (一次元配列データ)]に並び替え
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

学習

Dataloaderを使ってミニバッチ学習させた。学習に関してはCNNだからといって特別なところはない。

model = Model()

criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

num_epochs = 20

for epoch in range(num_epochs):
    total_loss = 0.0
    for inputs, labels in train_dataloader:
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()

    print('Epoch: {}  Loss: {}'.format(epoch, total_loss))

検証(正答率の確認)

はじめに準備したテストデータを用いて正答率を確認。

outputs = model(x_test)
_, predicted = torch.max(outputs.data, 1)
correct = (y_test == predicted).sum().item()  # Tensorの比較で正答数を確認
print("正答率: {} %".format(correct/len(predicted)*100.0))

結果

問題が単純なためか、正答率は高い。

Epoch: 0  Loss: 6.9794119000434875
Epoch: 1  Loss: 6.852706849575043
Epoch: 2  Loss: 6.788195848464966
Epoch: 3  Loss: 6.761787533760071
Epoch: 4  Loss: 6.738182067871094
Epoch: 5  Loss: 6.654882311820984
Epoch: 6  Loss: 6.485922873020172
Epoch: 7  Loss: 6.05694118142128
Epoch: 8  Loss: 5.022340148687363
Epoch: 9  Loss: 2.5453680232167244
Epoch: 10  Loss: 0.31626373156905174
Epoch: 11  Loss: 0.011260125378612429
Epoch: 12  Loss: 0.0037041376635897905
Epoch: 13  Loss: 0.00182408066757489
Epoch: 14  Loss: 0.0011753853141271975
Epoch: 15  Loss: 0.0009152144466497703
Epoch: 16  Loss: 0.0007907451872597449
Epoch: 17  Loss: 0.0007194795762188733
Epoch: 18  Loss: 0.0006725111852574628
Epoch: 19  Loss: 0.0006386453569575679
正答率: 100.0 %

参考

シンプルな CNN | PyTorch で簡単なニューラルネットワークを構築し画像を分類する方法
シンプルな例で詳しく説明されています。

Pytorchのニューラルネットワーク(CNN)のチュートリアル1.3.1の解説 - Qiita
畳み込みの部分が図を用いて詳しく説明されています。

CNN(Convolutional Neural Network)を理解する - sagantaf
フィルタサイズやパディング、ストライドの影響について解説があります。

Convolutional Neural Networkとは何なのか - Qiita
CNNが具体的に何を見ているかについてわかりやすい説明があります。

Chainer 開発終了

Chainerが開発終了の模様です。

tech.nikkeibp.co.jp

こういうことがあるのでフレームワークや言語選びは悩みますね。 はじめはChainerを使おうとしていたのですが、PyTorchにしておいてひとまず良かったようです。

メモ: Pillowを用いた深層学習用の画像データ数値化

画像データの深層学習を行うためのPillowを用いた画像データ数値化のメモ。 次の適当な画像データを、深層学習に使えるように数値化する。

f:id:schemer1341:20191027221206p:plain:w300 (testfig.png)
from PIL import Image
import numpy as np

im = Image.open("./testfig.png") # 画像ファイル読み込み
im = im.convert("L") # 画像のモード変更 "L"は8bitグレイスケール
im = im.resize((256, 256)) # 画像サイズ変更
data = np.array(im) # 画像を配列として数値データ化
im.show() # 処理後の画像表示

カラー、透過度等も含めたその他の画像モードは下記リンク参考

Concepts — Pillow (PIL Fork) 6.2.1 documentation

その他参考

https://mmm-ssss.com/2018/11/12/deeplearning_3/#toc

PIL/Pillow チートシート - Qiita