ゆけ ! Amazon ElastiCache 警察 ~第一部コンテナを学習するの巻~

2021-09-02
日常で楽しむクラウドテクノロジー

呉 和仁, 林 政利


登場人物

呉 : 機械学習完全に理解したマン(※)。コンテナのコの字も知らない。Amazon ElastiCache の誤字を見つけることに生きがいを感じている。Amazon ElastiCache 警察が通り名。作者と同姓ですが関係ありません。

林 : コンテナスペシャリスト。きかいがくしゅうを使えば何でもできると思っている。作者と同姓ですが関係ありません。


本記事は実際のコンテナ活用方法に基づいたフィクションです。

※ エンジニア用語でチュートリアルを一通り終えた、の意。学習が習熟するにつれ、完全に理解した (全体がわかっていないので、チュートリアルを終えれば自信に満ちている) →何もわからない (学習が進むに連れ自分の理解がいかに浅く狭いかを理解する) → チョットデキル (学習を続けることでわかるところはわかる様になる)、に到達する

ご注意

本記事で紹介する AWS サービスを起動する際には、料金がかかります。builders.flash メールメンバー特典の、クラウドレシピ向けクレジットコードプレゼントの入手をお勧めします。

*ハンズオン記事およびソースコードにおける免責事項 »

この記事のデモを無料でお試しいただけます »

毎月提供されるデベロッパー向けアップデート情報とともに、クレジットコードを受け取ることができます。 


第一章 : Amazon ElastiCache の誤字検知モデルをコンテナも知らずに自分の PC で作る ~頻発する似たようなエラー~

呉 : 「あー今日も Amazon ElastiCache の誤字をみつけなければ。なんでこんなに、Amazon ElasticCache と間違える人がおおいのだろうか。テキストなら検索すれば引っかかるけれども、画像にされると見つけるのが大変なんだよなぁ」


そうです。呉は機械学習ソリューションアーキテクトとして働く傍ら、Amazon ElastiCache の誤字を見つけては訂正依頼を出すのを日課としていたのです。


呉 : 
「あ、そうだ ! 私は機械学習ができるから、画像で誤字を判別するモデルを作れば自動化できるぞ !」


思い立ったが吉日、早速 Amazon ElastiCache の誤字を見つけるモデルを作るべく、買ったばかりのお気に入りの PC を開きました。


呉 : 
「まずは教師データを作らないとね。教師データなんて簡単に作れるさ ! 文字を書いた画像いっぱい作ろう。」


どんな時でも手を抜くことを忘れません。得意になって雑なコードを書きました。

generate_image.py

from random import randint,seed
from PIL import Image,ImageDraw,ImageFont
from os import makedirs
from glob import glob
import numpy as np
import matplotlib.font_manager as fm
seed(1234) # 結果を再現できるようにするために乱数のシードを固定
def main():
    system_font_list = fm.findSystemFonts()
    system_font_list = [font.replace('\\','/') for font in system_font_list]
    # 文字化けするfontを除外
    NG_FONT_LIST = ['BSSYM7.TTF','holomdl2.ttf','marlett.ttf','MTEXTRA.TTF','OUTLOOK.TTF','REFSPCL.TTF','segmdl2.ttf','symbol.ttf','TSPECIAL1.TTF','webdings.ttf','wingding.ttf','WINGDNG2.TTF','WINGDNG3.TTF',]
    font_list = []
    for font in system_font_list:
        if font.split('/')[-1] not in NG_FONT_LIST:
            font_list.append(font)
    output_dir = './img/'
    makedirs(output_dir, exist_ok=True)
    seed(1234) # 結果を再現できるようにするために乱数のシードを固定
    pixel_size = (700,50)

    for text in ['Amazon ElastiCache','Amazon ElasticCache']:
        for i in range(5):
            for font_path in font_list:
                font = ImageFont.truetype(font_path, randint(20,30))
                font = ImageFont.truetype(font_path, 30)
                img = Image.new('L', (pixel_size[0],pixel_size[1]),(255))
                d = ImageDraw.Draw(img)
                d.text((0, 0), text, font=font, fill=(0))

                # 描画部分だけ切り出し
                img_array = np.array(img)
                w,h=0,0
                for j in range(pixel_size[0]-1,-1,-1):
                    if not (np.all(img_array[:,j]==255)):
                        w = j+1
                        break
                for j in range(pixel_size[1]-1,-1,-1):
                    if not (np.all(img_array[j,:]==255)):
                        h = j+1
                        break
                img_array = img_array[0:h,0:w]
                max_pad_h = pixel_size[1] - img_array.shape[0]
                max_pad_w = pixel_size[0] - img_array.shape[1]
                pad_h = randint(0,max_pad_h)
                pad_w = randint(0,max_pad_w)
                # 真っ白なキャンバスを作成
                canvas_array = np.ones((pixel_size[1],pixel_size[0]),dtype=np.uint8)*255
                # 文字をランダムに移植
                canvas_array[pad_h:pad_h+img_array.shape[0],pad_w:pad_w+img_array.shape[1]] = img_array
                img = Image.fromarray(canvas_array)

                file_path = output_dir + text.replace(' ','') + '_' +font_path.split('/')[-1] + str(i) +  '.png'
                img.save(file_path)
    img_file_list = sorted(glob('./img/*.png'))
    train_X = np.zeros((len(img_file_list),pixel_size[1],pixel_size[0]),dtype=np.uint8)
    train_y = np.zeros((len(img_file_list)),dtype=np.float32)

    for i,img_file in enumerate(img_file_list):
        img = Image.open(img_file)
        img_array = np.array(img)
        train_X[i,:,:] = img_array
        if 'ElasticCache' in img_file:
            train_y[i] = 1
    train_X = ((train_X-127.5)/127.5).astype(np.float32)
    np.save('./train_X.npy',train_X)
    np.save('./train_y.npy',train_y)

if __name__=='__main__':
    main()

Enter キーを強めに叩いて、コードを実行します。

python generate_image.py # ターン!!!

すると悲鳴が聞こえました。


呉 : 
「ギャーーーーーーーーー」


よくよく見るとエラーが発生しています。

No module named import from PIL import

新しい PC に入れ替えたばっかりで、Python3.8 はインストールしてありましたが、何もモジュールをインストールしていなかったのです。あわてて pip コマンドを叩いて、再実行します。

pip install Pillow numpy matplotlib
python generate_image.py # ターン!!!

今度こそ動いたようです。無事学習するための、正しく “Amazon ElastiCache” と書いたパターンと、間違った “Amazon ElasticCache” と書いたパターンと、(文字化けしない) システムで持っているすべてのフォントで、ランダムにいろんな場所に書いた 4000 弱の画像と、それを機械学習で扱いやすくした numpy 配列である train_X.npy と、正解なら 0、誤りなら 1 をラベリングした train_y.npy が無事生成できました。 

注) 本記事のコードは乱数を利用したり、コンテナを利用していない場合は実行環境依存の箇所があり、下記結果以外にも結果が変わることがあることにご留意ください。

AmazonElasticCache_ALGER.TTF3.png
(ALGER フォントで正しい綴り、左側にかかれている)

AmazonElasticCache_HGRPRE.TTC2.png
(HGRPRE フォントで誤った綴り、右側にかかれている)

呉 : 「やはりエンジニアたるもの、何事も怠惰でないといけないよね。怠惰にデータを作ってやったぜ。さて、ここからモデルを作るか」


一心不乱にエディタを起動して打ち込みました。

train.py

import numpy as np
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Conv2D, PReLU, Dense, BatchNormalization, MaxPool2D, Flatten
from tensorflow.keras.optimizers import Adam

def main():
    train_X = np.load('./train_X.npy')
    train_y = np.load('./train_y.npy')
    inputs = Input(shape=(50,700,1))
    x = Conv2D(64, (3,3),padding='same')(inputs)
    x = BatchNormalization()(x)
    x = PReLU()(x)
    x = MaxPool2D(pool_size=(2, 2))(x)
    x = Conv2D(64, (3,3),padding='same')(x)
    x = BatchNormalization()(x)
    x = PReLU()(x)
    x = MaxPool2D(pool_size=(2, 2))(x)
    x = Conv2D(64, (3,3),padding='same')(x)
    x = BatchNormalization()(x)
    x = PReLU()(x)
    x = MaxPool2D(pool_size=(2, 2))(x)
    x = Conv2D(64, (3,3),padding='same')(x)
    x = BatchNormalization()(x)
    x = PReLU()(x)
    x = MaxPool2D(pool_size=(2, 2))(x)
    x = Conv2D(64, (3,3),padding='same')(x)
    x = BatchNormalization()(x)
    x = PReLU()(x)
    x = MaxPool2D(pool_size=(2, 2))(x)
    x = Flatten()(x)
    x = Dense(128)(x)
    x = Dense(1, activation='sigmoid')(x)
    model = Model(inputs=inputs, outputs=x)
    model.summary()
    model.compile(optimizer=Adam(learning_rate=0.0001),metrics=['accuracy'],loss="binary_crossentropy")
    model.fit(train_X,train_y,batch_size=16,epochs=50)
    model.save('elasticache_classifier.h5')

if __name__=='__main__':
    main()

またも Enter キーを強めに叩いて、コードを実行します。

python train.py # ターン!!!

すると既視感しかない悲鳴が聞こえました。


呉 : 
「ギャーーーーーーーーーーーーーーーーーッ!!」


よくよく見るとまたも似たようなエラーが発生しています。

No module named from tensorflow.keras.models import Model

あわてて pip コマンドを叩いて、再実行します。

pip install tensorflow-cpu==2.5.0
python generate_image.py # ターン!!!

今度こそ動いたようです。

標準出力抜粋

Epoch 50/50
234/234 [==============================] - 420s 2s/step - loss: 0.0025 - accuracy: 1.0000

accuracy が 1.0000 も出てとても過学習していそうですが、それを確かめるための評価用のデータもテストデータもないことに気づきました。


呉 : 
「まぁ適当でいいや」


世にはいろいろなフォントがあるので、自身の PC にはインストールしていない (= 学習には使用していない) 無料のフォント、
全児童フォント (フェルトペン) の無料版をインストールし、generated_image.py を少しもリファクタリングせずにただ丸パクリ & ハードコーディングして画像を生成して、作成したモデルで予測する predict.py を作成しました。

generate_test_image.py

from random import randint,seed
from PIL import Image,ImageDraw,ImageFont
from os import makedirs
from glob import glob
import numpy as np
import matplotlib.font_manager as fm
seed(1234) # 結果を再現できるようにするために乱数のシードを固定

def main():
    pixel_size = (700,50)
    output_dir = './test/'
    for text in ['Amazon ElastiCache','Amazon ElasticCache']:
        for i in range(1):
            for font_path in ['C:/Users/gokazu/AppData/Local/Microsoft/Windows/Fonts/ZenjidoJP-FeltPenLMT-TTF.ttf']:
                font = ImageFont.truetype(font_path, randint(20,30))
                font = ImageFont.truetype(font_path, 30)
                img = Image.new('L', (pixel_size[0],pixel_size[1]),(255))
                d = ImageDraw.Draw(img)
                d.text((0, 0), text, font=font, fill=(0))

                # 描画部分だけ切り出し
                img_array = np.array(img)
                w,h=0,0
                for j in range(pixel_size[0]-1,-1,-1):
                    if not (np.all(img_array[:,j]==255)):
                        w = j+1
                        break
                for j in range(pixel_size[1]-1,-1,-1):
                    if not (np.all(img_array[j,:]==255)):
                        h = j+1
                        break
                img_array = img_array[0:h,0:w]
                max_pad_h = pixel_size[1] - img_array.shape[0]
                max_pad_w = pixel_size[0] - img_array.shape[1]
                pad_h = randint(0,max_pad_h)
                pad_w = randint(0,max_pad_w)
                # 真っ白なキャンバスを作成
                canvas_array = np.ones((pixel_size[1],pixel_size[0]),dtype=np.uint8)*255
                # 文字をランダムに移植
                canvas_array[pad_h:pad_h+img_array.shape[0],pad_w:pad_w+img_array.shape[1]] = img_array
                img = Image.fromarray(canvas_array)

                file_path = output_dir + text.replace(' ','') + '_' +font_path.split('/')[-1] + str(i) +  '.png'
                img.save(file_path)
if __name__=='__main__':
    main()

さて、実際に実行します。

python generate_test_image.py # ターン!!!

./test/AmazonElastiCache_ZenjidoJP-FeltPenLMT-TTF.ttf0.png

./test/AmazonElasticCache_ZenjidoJP-FeltPenLMT-TTF.ttf0.png

さて、テストもかねて推論コードも作成して実行してみたようです。

predict.py

from tensorflow.keras.models import load_model
from PIL import Image
import numpy as np
import sys
def main(img_file):
    model = load_model('./elasticache_classifier.h5')
    test_X = ((np.array(Image.open(img_file))-127.5)/127.5).reshape(1,50,700,1)
    pred_y = float(model.predict(test_X)[0])
    print('Alert!!!!') if pred_y > 0.5 else print('No Problem')
if __name__=='__main__':
    img_file = sys.argv[1]
    main(img_file)
python predict.py ./test/AmazonElastiCache_ZenjidoJP-FeltPenLMT-TTF.ttf0.png # ターン!!!
python predict.py ./test/AmazonElasticCache_ZenjidoJP-FeltPenLMT-TTF.ttf0.png # ターン!!!

出力結果

No Problem # Amazon ElastiCache という正しい画像の結果
Alert!!!! # Amazon ElasticCache という誤った画像の結果

未知のフォントでも誤字をちゃんと検知できて、呉は悦に浸っています。

そこへ、コンテナスペシャリストの林がやってきました。


第二章 : 林先生によるコンテナ講義~コンテナを使ってはいけない法律下に生きているんですか ?~

林 : 「呉さん・・・今日も一日、 AWS App Mesh を AppMesh とスペースを入れずに使っている画像のパトロール作業で一日が終わってしまいました・・・あー AI に自動判別させたりとか、そういうソリューションがあればなー・・・」


何て良いタイミングでしょうか、呉はさっそく先ほど作成したコードを林に紹介してあげます。


呉 : 
「林さん、ちょうどそういう誤字を見つける ML のコードを書いてたところでしてね・・・とりあえず、このコードを実行してみてくださいよ。」


林が AWS App Mesh と AWS AppMesh の区別を行うと聞いて、こっそり気づかないようにハードコーディングしていた Amazon ElastiCache と Amazon ElasticCache の部分をコマンドライン引数で受け取れるように改善して、プログラマとしてのメンツを保ちます。

generate_image.py

from random import randint,seed
from PIL import Image,ImageDraw,ImageFont
from os import makedirs
from glob import glob
import sys, numpy as np
import matplotlib.font_manager as fm
from matplotlib import pyplot as plt
seed(1234) # 結果を再現できるようにするために乱数のシードを固定
def main(generate_strings):
    system_font_list = fm.findSystemFonts()
    system_font_list = [font.replace('\\','/') for font in system_font_list]
    # 文字化けするfontを除外
    NG_FONT_LIST = ['BSSYM7.TTF','holomdl2.ttf','marlett.ttf','MTEXTRA.TTF','OUTLOOK.TTF','REFSPCL.TTF','segmdl2.ttf','symbol.ttf','TSPECIAL1.TTF','webdings.ttf','wingding.ttf','WINGDNG2.TTF','WINGDNG3.TTF',]
    font_list = []
    for font in system_font_list:
        if font.split('/')[-1] not in NG_FONT_LIST:
            font_list.append(font)
    output_dir = './img/'
    makedirs(output_dir, exist_ok=True)
    seed(1234) # 結果を再現できるようにするために乱数のシードを固定
    pixel_size = (700,50)

    for text in generate_strings:
        for i in range(5):
            for font_path in font_list:
                font = ImageFont.truetype(font_path, randint(20,30))
                font = ImageFont.truetype(font_path, 30)
                img = Image.new('L', (pixel_size[0],pixel_size[1]),(255))
                d = ImageDraw.Draw(img)
                d.text((0, 0), text, font=font, fill=(0))

                # 描画部分だけ切り出し
                img_array = np.array(img)
                w,h=0,0
                for j in range(pixel_size[0]-1,-1,-1):
                    if not (np.all(img_array[:,j]==255)):
                        w = j+1
                        break
                for j in range(pixel_size[1]-1,-1,-1):
                    if not (np.all(img_array[j,:]==255)):
                        h = j+1
                        break
                img_array = img_array[0:h,0:w]
                max_pad_h = pixel_size[1] - img_array.shape[0]
                max_pad_w = pixel_size[0] - img_array.shape[1]
                pad_h = randint(0,max_pad_h)
                pad_w = randint(0,max_pad_w)
                # 真っ白なキャンバスを作成
                canvas_array = np.ones((pixel_size[1],pixel_size[0]),dtype=np.uint8)*255
                # 文字をランダムに移植
                canvas_array[pad_h:pad_h+img_array.shape[0],pad_w:pad_w+img_array.shape[1]] = img_array
                img = Image.fromarray(canvas_array)

                file_path = output_dir + text.replace(' ','') + '_' +font_path.split('/')[-1] + str(i) +  '.png'
                img.save(file_path)
    img_file_list = sorted(glob('./img/*.png'))
    train_X = np.zeros((len(img_file_list),pixel_size[1],pixel_size[0]),dtype=np.uint8)
    train_y = np.zeros((len(img_file_list)),dtype=np.float32)

    for i,img_file in enumerate(img_file_list):
        img = Image.open(img_file)
        img_array = np.array(img)
        train_X[i,:,:] = img_array
        if 'ElasticCache' in img_file:
            train_y[i] = 1
    train_X = ((train_X-127.5)/127.5).astype(np.float32)
    np.save('./train_X.npy',train_X)
    np.save('./train_y.npy',train_y)

if __name__=='__main__':
    print(f'generating {sys.argv[1:]}')
    main(sys.argv[1:])

林はまさかハードコーディングしているとも思っていなかったのでコードが差し替わっていることに気づかず、さすが呉 = 機械学習マン、頼りになる・・・

目をキラキラさせながらもらったコードを実行してみた林ですが・・・

$ python generate_image.py "Amazon ElastiCache" "Amazon ElasticCache"
Traceback (most recent call last):
  File "generate_image.py", line 2, in <module>
    from PIL import Image,ImageDraw,ImageFont
ModuleNotFoundError: No module named 'PIL'

林 : 「あの、」

呉 : 「ああ、これ出ちゃうんですよねー。Python のパッケージを入れれば解決ですよ。」


作業環境を汚すことを異常に恐れる林は怯えた目で呉を見ますが、とりあえず Python の仮想環境で作業すれば精神を安定させることはできそうです。言われるがまま、パッケージをイントールして再度実行してみました。

$ python -m venv venv
$ source venv/bin/activate
$ pip install Pillow numpy matplotlib

$ python generate_image.py

林 : 「ヒィ

$ python -m venv venv
$ source venv/bin/activate
$ pip install Pillow numpy matplotlib

$ python generate_image.py
$ python generate_image.py           
Traceback (most recent call last):
  File "/Users/hayshim/projects/src/github.com/literalice/ml-containers-buildersflash/venv/lib/python3.9/site-packages/PIL/ImageFont.py", line 855, in truetype
    return freetype(font)
  File "/Users/hayshim/projects/src/github.com/literalice/ml-containers-buildersflash/venv/lib/python3.9/site-packages/PIL/ImageFont.py", line 852, in freetype
    return FreeTypeFont(font, size, index, encoding, layout_engine)
  File "/Users/hayshim/projects/src/github.com/literalice/ml-containers-buildersflash/venv/lib/python3.9/site-packages/PIL/ImageFont.py", line 211, in __init__
    self.font = core.getfont(
OSError: invalid pixel size
...

呉 : 「あれ、林さん Mac で実行してます ? フォント周りで環境依存がでてますね、とりあえず print でも入れて、」


『環境依存』という単語を聞いた林は脊髄反射で vim を起動しました。

$ vim Dockerfile

FROM python:3.8-buster

RUN pip install Pillow numpy matplotlib

# 学習データのためにたくさんフォントをインストールする
RUN apt-get -y update && \
    apt-get -y upgrade && \
    apt-get -y install wget cabextract xfonts-utils && \
    wget http://ftp.cn.debian.org/debian/pool/contrib/m/msttcorefonts/ttf-mscorefonts-installer_3.8_all.deb && \
    dpkg -i ttf-mscorefonts-installer_3.8_all.deb && \
    apt-get -y install fonts-takao \
                       fonts-ipafont \
                       fonts-ipaexfont
                       
RUN mkdir workspace
WORKDIR workspace

呉 : 「エラーになっているフォントを確認してリストに・・・あれ、林さん何しているんです ?」

林 : 「え、vim で Dockerfile ファイルを書いているんですけど・・・」

呉 : 「(何もわからなくてどんな質問をすればよいのかわからない・・・。まずわからないのは 2 点だ。vim というアプリ、起動したことはあるが、編集できずにマウススクロールもできない text viewer で、更にアプリの終わらせ方もわからずに最終的に PC の電源を落として終了させた過去のあるク○アプリかと思っていたのだが、どうやら text editor として使用する方法があるようだ・・・。もう 1 つは「どっかーふぁいる (?)」なる知らない単語を持ち出して来たことだ。)」

林 : 「データサイエンティストを名乗っているのに Docker を知らない・・・?」

呉 : 「(惜しい、vim の使い方も知らないんだ)、え、え、え ? あ、はい・・・」


林は builders.flash の「スペシャリストに学ぶコンテナ」シリーズを宣伝しながら、コンテナで隔離した環境でコードを実行すれば Mac でも Windows でも同じ前提で作業できることを紹介しました。


呉 : 
「完全に理解した (キリッ。つまり、コード (Dockerfile) を通じて Docker で隔離した環境を構築さえすれば、どこでも同じ処理を実行できるってことですね !」

林 : 「(うなずきながら常識なんだけどな、という顔をしている)」

林 : 「ま、まぁ動かしましょう。docker build コマンドで環境 (イメージ) を構築して、docker run コマンドで実行です。」

$ docker build -t ml-typocheck .
$ docker run --rm -v $(pwd):/workspace ml-typocheck python generate_image.py "Amazon ElastiCache" "Amazon ElasticCache"
$ ls -1
Dockerfile
generate_image.py
img
train.py
train_X.npy
train_y.npy
venv

林 : 「成功しました ! この Dockerfile をコードと同梱すれば Mac でも Windows でも同じように実行できますよ ! pip のパッケージも自動的に導入できますし。」

呉 : 「なるほど ! フォントのセットも実行環境に左右されないので、教師データの品質も安定させることができますね !」


さっそく、生成したデータで学習も実行してみます。

$ docker run --rm -v $(pwd):/workspace ml-typocheck python train.py
Traceback (most recent call last):
  File "/Users/hayshim/projects/src/github.com/literalice/ml-containers-buildersflash/train.py", line 2, in <module>
    from tensorflow.keras.models import Model
ModuleNotFoundError: No module named 'tensorflow'

何かエラーが出ましたが、動じることなく Dockerfile を更新して再実行です。IaC (Infrastructure as Code) も実践できて最高です。


林 :
「というか、人にコードを渡すとき、pip のモジュール名を手打ちさせるのはどうかと・・・。pip freeze してなかったんですか ?」

呉 : 「え ? (なんの話 ?)」

林 : 「まぁ、今回は僕が唐突に話しかけたんで準備できてなかっただけだと思うので、私が requirements.txt を記載しちゃいますね。」

呉 : 「(requirements.txt ってなんだ ?) あ、は、はい、よ、よろしくお願いします。」

$ vim requirements.txt
tensorflow-cpu==2.5.0
pillow==8.0.1
matplotlib==3.3.2
numpy==1.19.2
$ vim Dockerfile

FROM python:3.8-buster

COPY requirements.txt ./ 
RUN pip install -r requirements.txt

# 学習データのためにたくさんフォントをインストールする
RUN apt-get -y update && \
    apt-get -y upgrade && \
    apt-get -y install wget cabextract xfonts-utils && \
    wget http://ftp.cn.debian.org/debian/pool/contrib/m/msttcorefonts/ttf-mscorefonts-installer_3.8_all.deb && \
    dpkg -i ttf-mscorefonts-installer_3.8_all.deb && \
    apt-get -y install fonts-takao \
                       fonts-ipafont \
                       fonts-ipaexfont
                       
RUN mkdir workspace
WORKDIR workspace

$ docker build -t ml-typocheck .
$ docker run --rm -v $(pwd):/workspace ml-typocheck python train.py

林 : 「問題なく学習が進んでいますね・・・あれ、ちょっと・・・進行が遅い、ですか ?」

2021-07-22 18:55:08.251199: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:176] None of the MLIR Optimization Passes are enabled (registered 2)
Epoch 1/50
51/51 [==============================] - 141s 3s/step - loss: 0.7439 - accuracy: 0.5481
Epoch 2/50
51/51 [==============================] - 142s 3s/step - loss: 0.5425 - accuracy: 0.7321
Epoch 3/50
51/51 [==============================] - 147s 3s/step - loss: 0.4180 - accuracy: 0.8395
Epoch 4/50
51/51 [==============================] - 145s 3s/step - loss: 0.3317 - accuracy: 0.9000
Epoch 5/50
51/51 [==============================] - 152s 3s/step - loss: 0.2459 - accuracy: 0.9506
...
Epoch 47/50
51/51 [==============================] - 148s 3s/step - loss: 7.7153e-04 - accuracy: 1.0000
Epoch 48/50
51/51 [==============================] - 148s 3s/step - loss: 7.8474e-04 - accuracy: 1.0000
Epoch 49/50
51/51 [==============================] - 152s 3s/step - loss: 7.3156e-04 - accuracy: 1.0000
Epoch 50/50
51/51 [==============================] - 165s 3s/step - loss: 6.1298e-04 - accuracy: 1.0000

呉 : 「そうですね、1 step あたり 3 秒、 1 epoch あたり 150 秒かかってしまって、全部で 2 時間くらいかかってしまっています。」


林は、先日ゲームに没頭するためにグラフィックボードを買ってパソコンにセットアップしていたことを思い出しました。


林 : 
機械学習って、GPU 使えますよね ? 学習早くなったりしませんか ?」

呉 : 「確かに CNN を利用していますし、GPU を活用して早くなるかもしれませんね。ではまず Ubuntu をインストールしましょう。」

林 : 「いえ、私のマシンは CentOS 7 が入っているのでそれで・・・後で Amazon Linux2 の仮想マシンでも動かしたいですし。」

呉 : 「(CentOS7・・・? 古くね・・・?) な、なるほど、問題ないです。とりあえず必要な Python のパッケージをインストールしますか。」

[centos@centos-ml-1 ~]$ python -m venv venv
/usr/bin/python: No module named venv

林 : 「そういえば CentOS 7 なので標準の Python 2.7 を使っているんでした・・・別に 2.7 でもいいですよね ?

呉 : ダメ、ゼッタイ。Python 3.8 以上を入れてください。Python 3.7 も許しません。」

林 : 「えー・・・」


この辺で既にコンテナを使いたくなってきましたが、ひとまず黙って Python 3 を導入してみます。

[centos@centos-ml-1 ~]$ yum -y install python3
[centos@centos-ml-1 ~]$ python3 -m venv venv

呉 : 「さて、NVIDIA の GPU を使うので、ドライバーと CUDA が必要ですね。やってみますか。」

# https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#runfile-nouveau-rhel

$ sudo su -

$ cat <<EOF > /etc/modprobe.d/blacklist-nouveau.conf
blacklist nouveau
options nouveau modeset=0
EOF
$ dracut --force
$ sudo reboot

# https://docs.nvidia.com/datacenter/tesla/tesla-installation-notes/index.html#centos7
$ sudo yum install -y tar bzip2 make automake gcc gcc-c++ pciutils elfutils-libelf-devel libglvnd-devel iptables firewalld vim bind-utils wget
$ sudo yum install -y kernel-devel kernel-headers

# カーネルとカーネルソースのバージョンを揃える
$ sudo yum update
$ sudo reboot

# 以下からGPUに合わせたrunfileをダウンロードして利用する
# https://www.nvidia.com/Download/index.aspx?lang=en-us

$ sudo bash NVIDIA-Linux-x86_64-470.57.02.run
$ sudo reboot

# GPUが認識されていることを確認
$ nvidia-smi

呉 : 「次に、Tensorflow から GPU を使うために CUDA をセットアップします。」

林 : 「ふむふむ。」

呉 : 「そのとき、Tensorflow のバージョンに対応した CUDA でないと結構動かなくなるので、この辺を見て 慎重にバージョンを選びましょう」

林 : 「なるほど・・・あれ、古い Tensorflow 使って動かしてるアプリケーションが別にある場合はどうするんです ?」

呉 : 「その場合は・・・古い Tensorflow と古い CUDA 使う・・・いや、新しいマシン用意するのが安全ですね。」

林 : 「・・・でもまあ、今回は新しく CUDA セットアップするので問題ないですね、やってみましょう。CUDA のバージョンは・・・11.2 ですね。CUDA Toolkit のページ にいって、と・・・」

# https://developer.nvidia.com/cuda-11.2.2-download-archive

$ wget https://developer.download.nvidia.com/compute/cuda/11.2.2/local_installers/cuda-repo-rhel7-11-2-local-11.2.2_460.32.03-1.x86_64.rpm
$ sudo rpm -i cuda-repo-rhel7-11-2-local-11.2.2_460.32.03-1.x86_64.rpm
$ sudo yum clean all

# NVIDIA Driver導入済みなのでCUDAパッケージのみインストール
$ sudo yum -y install cuda-toolkit-11-2.x86_64

$ cat <<EOF >> ~/.bashrc
export PATH=/usr/local/cuda/bin${PATH:+:${PATH}}
export LD_LIBRARY_PATH=/usr/local/cuda/lib64\
                       ${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
EOF
$ source ~/.bashrc

# インストール確認
# https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#install-libraries
$ sudo yum install freeglut-devel libXi-devel libXmu-devel mesa-libGLU-devel

$ cuda-install-samples-11.2.sh ~
$ cd NVIDIA_CUDA-11.2_Samples/5_Simulations/nbody
$ make

$ ./nbody
> Windowed mode
> Simulation data stored in video memory
> Single precision floating point simulation
> 1 Devices used for simulation

いろいろな資料を見ながらインストールするパッケージなどを調べ、ときどき間違いつつも、何とか CUDA のセットアップに完了したようです。

> Compute 7.5 CUDA device: [NVIDIA GeForce GTX 1650]
14336 bodies, total time for 10 iterations: 21.151 ms
= 97.169 billion interactions per second
= 1943.376 single-precision GFLOP/s at 20 flops per interaction

林 : 「ハァハァ・・・やりました !」

呉 : 「やりましたね ! 次は、cuDNN をインストール します。」

林 : 「まだあるんですか・・・」

呉 : 「まず NVDIA のサイト に行ってユーザー登録をしてなければ登録し・・・あ、cuDNN もインストールするバージョンが決まっているのでこの場合は 8.1 をインストールするよう気を付けて・・・」


(インストール作業中・・・)


林 : 
「お、終わりました ! これで、後は pip で Tensorflow をインストール すれば・・・!」

$ source venv/bin/activate
$ pip install tensorflow
$ python3 -c "import tensorflow as tf; print(\"Num GPUs Available: \", len(tf.config.experimental.list_physical_devices('GPU')))"

...
2021-07-24 02:56:58.284169: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-07-24 02:56:58.284396: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1871] Adding visible gpu devices: 0
Num GPUs Available:  1

呉 : 「GPU も認識しているし、これで実行できそうですね !」

$ python3 train.py
...
2021-07-24 03:25:10.545814: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1418] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 2655 MB memory) -> physical GPU (device: 0, name: NVIDIA GeForce GTX 1650, pci bus id: 0000:05:00.0, compute capability: 7.5)
Model: "model"
...
51/51 [==============================] - 6s 122ms/step - loss: 7.5559e-04 - accuracy: 1.0000
Epoch 48/50
51/51 [==============================] - 6s 122ms/step - loss: 7.4944e-04 - accuracy: 1.0000
Epoch 49/50
51/51 [==============================] - 6s 122ms/step - loss: 6.7342e-04 - accuracy: 1.0000
Epoch 50/50
51/51 [==============================] - 6s 122ms/step - loss: 6.9390e-04 - accuracy: 1.0000

呉 : 「1 step あたり 1 秒~ 2 秒かかっていたのが、120 ~ 130ミリ秒と文字通り桁違いの速度が出ましたね !」

林 : 「実行できました ! いやー、結構手間ではないですか ? コンポーネントのバージョンなんかにも気を遣いますし・・・こういう、依存関係の制約が厳しくて手間が掛かる環境の構築はやっぱりコンテナを使うのがいいと思います !」


とはいえ、コンテナはホストのカーネルを利用するので、カーネルモジュールである NVIDIA のドライバーはホスト OS の CentOS にインストールする必要があります。それ以降の手順を Docker を使ってコンテナで実行してみることにしました。


林 : 
「まず、もちろん Docker をインストールしますね。」

# https://docs.docker.com/engine/install/centos/

 $ sudo yum install -y yum-utils
 $ sudo yum-config-manager \
   --add-repo \
   https://download.docker.com/linux/centos/docker-ce.repo
   
 $ sudo yum install docker-ce docker-ce-cli containerd.io
 $ sudo systemctl start docker &&  sudo systemctl enable docker
 
 $ sudo usermod -aG docker centos

次に、コンテナから NVIDIA の GPU を使えるようにしてくれる、NVIDIA Container Toolkit をインストールします。

# https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#id2
 
 $ distribution=$(. /etc/os-release;echo $ID$VERSION_ID) \
   && curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.repo | sudo tee /etc/yum.repos.d/nvidia-docker.repo
   
 $ sudo yum clean expire-cache
 $ sudo yum install -y nvidia-docker2
 $ sudo systemctl restart docker
 $ docker run --rm --gpus all nvidia/cuda:11.0-base nvidia-smi

林 : 「で、Tensorflow を使ってみますね。」

docker run --gpus all -it --rm tensorflow/tensorflow:latest-gpu python -c "import tensorflow as tf; print(\"Num GPUs Available: \", len(tf.config.experimental.list_physical_devices('GPU')))"

林 : 「もう Tensorflow の GPU サポートがセットアップできてしまった・・・簡単すぎる・・・やはり時代はコンテナ・・・」

呉 : 「(悦に浸ってる林さんもカッコイイ・・・)」


と、言うわけで、あっさり Tensorflow で GPU が使える環境を準備できてしまいました。

Tensorflow の公式コンテナイメージは Ubuntu ベースのもので、CUDA もセットアップ済み、Python も Tensorflow も構築済みの環境を固めたものなので、ほとんど環境構築作業の手間無く、サポート済みの環境で機械学習のコードを実行できます。機械学習のコードが利用している Tensorflow のコンテナイメージを使えば、複数のバージョンの Tensorflow を使い分けることもできますね。

林はコンテナマンであることに感謝しながら、呉の学習コードをコンテナで実行しました。

docker run --gpus all -it --rm  -v $(pwd):/workspace \
  tensorflow/tensorflow:latest-gpu \
  bash -c "cd workspace && python train.py"
  
...
Epoch 46/50
51/51 [==============================] - 6s 122ms/step - loss: 9.2492e-04 - accuracy: 1.0000
Epoch 47/50
51/51 [==============================] - 6s 122ms/step - loss: 9.5722e-04 - accuracy: 1.0000
Epoch 48/50
51/51 [==============================] - 6s 122ms/step - loss: 8.7050e-04 - accuracy: 1.0000
Epoch 49/50
51/51 [==============================] - 6s 122ms/step - loss: 8.1851e-04 - accuracy: 1.0000
Epoch 50/50
51/51 [==============================] - 6s 122ms/step - loss: 7.7020e-04 - accuracy: 1.0000

林 : 「はやい (満足)」


おわりに〜作者対談〜

呉 : 「さて、第一部では呉さん (同姓の他人) がコンテナを覚えました。」

林 : 「データサイエンティストでコンテナを知らないという設定は無理がありませんか ?

呉 : 「いやいや、IT からじゃなくて、統計からこの界隈に入り込んだ人ってコンテナを知らない人が多いんですよ・・・(統計から入っていないけどコンテナを長らく知らなかった自分のことは棚上げ)」

林 : 「へぇへぇへぇ、私、コンテナネイティブな人だからその辺よく分からなかったですね (上から目線)。」

呉 : 「そ、そ、それはそれとして、データサイエンスの領域でもコンテナを使う利点が読者にもわかってもらえたんじゃないですかね ?」

林 : 「まぁそうですね。題材が適切かはさておき、フォントや他の人の環境を通じて再現性の高さや可搬性の良さについて言いたいことは言えました。」

呉 : オレオレ環境Jupyter をいじっている私には響きました。」

林 : 「」

呉 : 「ML Engineer や Data Scientist の領域、というかやることは、企業の中の分業体制によっていろいろ変わりますが、少なくとも “学習コードだけでなく Dockerfile を書いて build して動作の確認まで” は少なくとも受け持ったほうがよさそう、というのが今回の学びでした。」

林 : 「こうすれば動く、の保証になりますし、その先はまた違うスキルセットが必要ですので、その領域の考え方はいいと思います。」

呉 : 「その先の違うスキルセットとは ??」

林 : 機械学習の実行環境、インフラを構築することでしょうか。例えば今回、GPU を利用して学習を実行しましたが、そのような環境をセットアップしたり、利用できる GPU リソースを見て学習ワークロードを配置する仕組みを作ったりする必要があります。また、推論に関してもモデルをデプロイしたり、エンドポイントをスケーリングしたりといったインフラ作りが求められますね。が、今回は紙面の都合上ここまでで次回にでも語りましょう。」

呉 : 「気になるトピックが出てきましたね。しかし紙面の都合上仕方有りません。AWS のサービスの話が本筋と全く関係ない話ばかりで、申し訳程度に Amazon ElastiCache と AWS App Mesh、そしてかろうじて OS の Amazon Linux が出ただけで社内で怒られないのかというのが気になりますが、また次回もぜひ読んでいただけれたらと存じます。ありがとうございました。」

林 : 「ありがとうございました。というわけで、次回予告という形を借りてもう少しだけ無理やり AWS のサービスをねじこみます。」


次回予告

おっす ! オラ Amazon ECS !
いや~まいったまいった。パソコンでコンテナを勝手に動かされちゃこまるんだよね。コンテナの動かし方のしきたりってのを教えてやらねば。

次回、「ゆけ!Amazon ElastiCache 警察~第二部 ECS で実行するの巻~」

コンテナをマネジメントサービスで動かして管理の手間を省力化 ! Amazon ECS でオンプレとクラウドを瞬間移動 !
お楽しみに !


builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます

筆者プロフィール

呉 和仁 (Go Kazuhito / @kazuneet
アマゾン ウェブ サービス ジャパン合同会社
機械学習ソリューションアーキテクト。

IoT の DWH 開発、データサイエンティスト兼業務コンサルタントを経て現職。
プログラマの三大美徳である怠惰だけを極めてしまい、モデル構築を怠けられる AWS の AI サービスをこよなく愛す。

林 政利 (@literalice)
アマゾン ウェブ サービス ジャパン合同会社
コンテナスペシャリスト ソリューションアーキテクト

フリーランスや Web 系企業で業務システムや Web サービスの開発、インフラ運用に従事。近年はベンダーでコンテナ技術の普及に努めており、現在、AWS Japan で Amazon ECS や Amazon EKS でのコンテナ運用や開発プロセス構築を中心にソリューションアーキテクトとして活動中。

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する