放射線技術×データサイエンス

放射線技術×データサイエンス

診療放射線技師がPythonとRとImageJを使って色々します

第7回岡山県西部医用画像研究会深層学習部会 –父と母どちらに似てる?–

はじめに

本記事は第7回岡山県西部医用画像研究会深層学習部会の参考資料となります.
これまで岡山県西部医用画像研究会深層学習部会では「ゼロから作るDeep Learning」を利用して深層学習の勉強をしました.前回の第6回では「ゼロから~」の第7章『畳み込みニューラルネットワーク(Convolutional Neural Network:CNN)』を勉強しました.約1年かけて当初の目標であったCNNの内容をとりあえず知ることができました(完璧に理解したとは言っていない・・・).そこで今回,代表世話人である私杉本が現時点まで勉強したことを応用し,実際にプログラムを組み画像認識を行ってみました.せっかくここまで勉強したんだから,なにかアウトプットがしたかったのです.
ということで以下に第7回岡山県西部医用画像研究会深層学習部会として記事の内容を書いています.なるべく正確にしたつもりではありますが,初心者であるため間違いがあるかもしれません.ご容赦くださいm(__)m

目次

何をしたのか

子どもの顔が両親のどちらの顔に似ているかを客観的に判断できるAIを作成しました.ここでは私の子どもが私と妻,どちらのほうに似ているかを判断するAIを作りました.親となった私は,わが子を他人に見せた時に私に似てるといわれることが多いです.しかしこれがお世辞なのか,あるいは本当にそうなのか,それを客観的に知ることはできません.科学的にこの事実を判断したいという情熱からこのAIは作られました.またくだらないことをしてしまった.ほんとは医用画像を使って何かできればよかったのですが,なかなか面白そうな内容を思いつかなかったのでできませんでした.

その1:データ収集

第1回深層学習部会で少しお話したのですが,私の最近の趣味はライフハック.私は生活をより効率化することに喜びをおぼえる人です.私はGoogle photoを使ってスマホで撮った写真を自動的でバックアップしています.

photos.google.com

これがまたすごいんです.特にスマホの操作をせずに写真のバックアップをしてくれて,さらにそのバックアップした写真の顔を認識してその人ごとにアルバムを自動で作ってくれます.しかも無料.今回はその機能を利用して私と妻と子どものアルバムをダウンロードしました.

その2:顔のみを切り出す

ここからプログラムを書いていきます.実際にAIで人の顔の判断をさせるのに最も効率が良いのは顔のみの写真で学習させることです.今回はPythonOpenCVを使い顔の切り出しを行いました.以下,コードを書いています.

import sys, os
import glob
import cv2
import numpy as np
import matplotlib.pyplot as plt
import shutil
from PIL import Image

def filelist(path):
    return [p for p in glob.glob(path + '\\*')]
    
dir = '..\\rawdata'
file = filelist(dir)

data_path = '..\\data'
cropped_path = '..\\cropped'

for f in file:
    base, ext = os.path.splitext(f)
    if ext == '.jpg':
        shutil.copy(f, data_path)
    else:
        pass

#入力ファイルのパスを指定
in_jpg = data_path
out_jpg = cropped_path
 
#リストで結果を返す関数
def get_file(dir_path):
    filenames = os.listdir(dir_path)
    return filenames
 
pic = get_file(in_jpg)
 
for i in pic:
    # 画像の読み込み 
    image_gs = cv2.imread(in_jpg + '\\' +i)
 
    # 顔認識用特徴量ファイルを読み込む --- (カスケードファイルのパスを指定)
    cascade = cv2.CascadeClassifier("..\\opencv-master\\data\\haarcascades\\haarcascade_frontalface_alt.xml")
    
    # 顔認識の実行
    face_list = cascade.detectMultiScale(image_gs,scaleFactor=1.1,minNeighbors=1,minSize=(1,1))
    
    # 顔だけ切り出して保存
    no = 1;
    for rect in face_list:
        x = rect[0]
        y = rect[1]
        width = rect[2]
        height = rect[3]
        dst = image_gs[y:y + height, x:x + width]
        save_path = out_jpg + '/' + 'out_('  + str(i) +')' + str(no) + '.jpg'
        
        #認識結果の保存
        a = cv2.imwrite(save_path, dst)
        plt.show(plt.imshow(np.asarray(Image.open(save_path))))
        print(no)
        no += 1

上のプログラムが動くと顔だけ切り出されて保存されます.ここからが1番大変な作業でした.大量に切り出された顔をそれぞれフォルダ分けしていきました.もちろん手動.顔以外が認識されて保存されたりしていました.機械学習は前処理が8割と聞いたことがありましたが,まさにこのことかと思い知らされました.一週間くらいかかりました.なおデータは私の顔:785枚,妻の顔:421枚です.

その3:学習

私と妻の顔を切り出し,それらをCNNで学習させます.ちなみにGoogle colabを使っています.

colab.research.google.com

以下コードです.

# Google driveをマウント
from google.colab import drive 
drive.mount('/content/drive')

# ディレクトリの移動
%cd 'drive/My Drive/目的のフォルダ'

import os
import glob
import shutil
import numpy as np
import matplotlib.pyplot as plt

from tensorflow.python.keras import layers
from tensorflow.python.keras import models
from tensorflow.python.keras import optimizers
from tensorflow.python.keras.preprocessing.image import ImageDataGenerator


dir_train1 = './data/train/私'
dir_train2 = './data/train/妻'
dir_validation1 = './data/validation/私'
dir_validation2 = './data/validation/妻'

d_train1 = glob.glob(dir_train1+'/*')
d_train2 = glob.glob(dir_train2+'/*')
d_validation1 = glob.glob(dir_validation1+'/*')
d_validation2 = glob.glob(dir_validation2+'/*')

base_dir = './data'

train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')

train_datagen = ImageDataGenerator(rescale=1./255)
valid_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
    train_dir,
    target_size=(256, 256),  # モデルの入力サイズ
    color_mode='rgb',  # 読み込み形式
    batch_size=20,  # バッチサイズ
    class_mode='binary')
valid_generator = valid_datagen.flow_from_directory(
    validation_dir,
    target_size=(256, 256),  # モデルの入力サイズ
    color_mode='rgb',  # 読み込み形式
    batch_size=20,  # バッチサイズ
    class_mode='binary')

# モデルの定義
np.random.seed(0)
model = models.Sequential()
model.add(layers.Conv2D(16, (3, 3), activation='relu', input_shape=(256, 256, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(32, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))       

# モデルのコンパイル
model.compile(loss='binary_crossentropy',
              optimizer=optimizers.adam(),
              metrics=['acc'])

model.summary()

history = model.fit_generator(train_generator,
                              steps_per_epoch=100,
                              epochs=10,
                              validation_data=valid_generator,
                              validation_steps=50)

# モデルの保存
model.save('./models/model_01.h5')

今回作ったネットワークのモデルは以下になります. f:id:radmodel:20200105140248j:plain 畳み込み層プーリング層×2と全結合層×2のCNNになります.データを訓練用とテスト用に分割し,10epoch計算させました. 学習は以下のように行われました. f:id:radmodel:20200105140219j:plainこれで学習ができました.訓練用データに対しては100%,テストデータに対しては93%の精度がでています. 以下のコードで可視化できます.

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

f:id:radmodel:20200105140625p:plainf:id:radmodel:20200105140637p:plain

その4:父と母どちらに似てる?

ここからが本番です.その3までで私と妻を判断するネットワークを作ることができました.このネットワークの中に子どもの写真を入力すると,私と妻のどちらかだとネットワークが判断します.

model = load_model('./models/model_01.h5')

img_predict = []

for image_name in os.listdir( './子どもの画像のフォルダ'):
    try:
        img = Image.open(os.path.join( './子どもの画像のフォルダ', image_name))
        img = img.convert("RGB")
        img = img.resize((256, 256))
        img_array = np.asarray(img)
        img_predict.append(img_array)
    except Exception as e:
        pass

img_predict = np.asarray(img_predict)

result_predict_classes = model.predict_classes(img_predict)

father = 0
mother =0

predict = result_predict.flatten()

for i in predict:
    if i ==0:
        mother += 1
    else :
        father += 1

print(father ,mother)

ここでは100枚の子どもの写真を入力しました.

結果

子どもの画像を入力した結果、
私:71,妻:29
に分類されました.つまり,93%の精度で私と妻を分類できるAIは,2人の子どもの顔を約70%の割合で私だと判断したという結果を導き出しました.やはり父親似でした.

まとめ

以上,くだらないモデルを作成することができました.1年間深層学習について勉強してきてとりあえずここまでたどり着くことができました.このモデルを作成する過程で本には書いていないわからないことがたくさん出てきて,深層学習の難しさをより味わうことができました.

最後に

プログラミングも深層学習もわからないことだらけですが,やらなければわからないことはわからないままです.昨今の人工知能ブームで医療の世界も変わりつつあります.さらにこれからはプログラミング教育も始まり,ますますこの分野から逃れることは難しくなると考えられます.診療放射線技師の業務に直接関わるところは少ないかもしれないですが,アプリケーションとして触れる機会は今後必ずやってくるでしょう.私はそれを使いこなせるように今後も深層学習をはじめ,機械学習についてボチボチ勉強し続けていきたいと思います.

ということで以上となります.明日からまた仕事頑張りましょう.