福岡人データサイエンティストの部屋

データサイエンスを極めるため、日々の学習を綴っています。

【医療画像AI】X線胸部画像から年齢を予測してみた#004



こんにちは!こーたろーです。


本日は、CNNを使ってX線画像から年齢を推定していきます。


日本放射線技術学会の画像部会のサンプルページで得られるデータから、正解ラベルがあるものを次々と形にしております。


今回は、「年齢推定(回帰)」のところをやっていきます。



必要データのダウンロード

前回同様に日本放射線技術学会 画像部会のページからtestデータをダウンロードします。
http://imgcom.jsrt.or.jp/imgcom/wp-content/uploads/2019/07/XPAge01_RGB.zip
こちらのリンクを開くと、ダウンロードが開始します。


「XPAge01_RGB.zip」という、ファイルがダウンロードされたら、解凍してください。


解凍すると「XP」というフォルダができますので、そちらを新しく作成するpythonファイルと同じレイヤーに保存しておきます。


場合によっては、データをロードする際にパスを指定して使ってください。





中身を見てみると、X線画像が入っているフォルダに加えて、画像ファイル名+正解ラベル(年齢)が入ったCSVファイルがあることが分かります。
また、CSVファイルは、学習用とテスト用に分かれています。

ライブラリ・オブジェクトのインポート

早速プログラムを始めていきます。
先ずはライブラリ・オブジェクトのインポートです。
私は初めに必要なものを全て定義する方が好きです。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from keras.preprocessing.image import load_img, img_to_array
from tensorflow.python.keras.models import Sequential
from tensorflow.python.keras.layers import Conv2D
from tensorflow.python.keras.layers import MaxPooling2D
from tensorflow.python.keras.layers import Dropout
from tensorflow.python.keras.layers import Flatten
from tensorflow.python.keras.layers import Dense
from keras.models import load_model



殆ど決まったものしか使っていないですが、前回と異なるのはPandasが入っていることです。


前回は正解ラベルを自分で作成したこともあって、numpyのみでいけましたが、今回はCSVファイルを読み込んでそこから、ファイル名と正解ラベルのリストを作成するため、pandasを使っていきます。



データの前処理その1(画像データ)

まず、トレーニングデータとテストデータを取得していきます。

traindata = pd.read_csv("./XP/trainingdata.csv")
testdata = pd.read_csv("./XP/testdata.csv")

ファイル名と年齢が入った配列データテーブルとして読み込みができます。

次に、画像データの取得を行っていきます。
画像データも、学習用とテスト用に分けて取り出します。

【学習用データ】

trainImg = np.zeros((len(traindata["filenames"]), 128, 128, 1))

for i in range(0, len(traindata["filenames"])):
    fname = './XP/JPGs/%s' %traindata["filenames"][i]
    trainImg[i] = np.array(img_to_array(load_img(fname, color_mode = "grayscale", target_size=(128,128))))/255.0

trainImg.shape


(80, 128, 128, 1)の4次元配列データになります。

【テスト用データ】

testImg = np.zeros((len(testdata["filenames"]), 128, 128, 1))

for i in range(0, len(testdata["filenames"])):
    fname = './XP/JPGs/%s' %testdata["filenames"][i]
    testImg[i] = np.array(img_to_array(load_img(fname, color_mode = "grayscale", target_size=(128,128))))/255.0

testImg.shape

(165, 128, 128, 1)の4次元配列データになります。

今回の画像は、JPEG形式でありRGB形式(3チャネル)で構成されている。画像自体はグレースケールのため、1チャネルあれば十分です。
そこで、4次元データとする際に、チャネルを1として作成しました
また、Shapeの結果からも分かる通り、128(ピクセル)×128(ピクセル)の画像であることが分かります。





データの前処理その2(正解ラベル)

CSVファイルをpandasのデータフレームに格納したあとは、そこから正解ラベルを取り出します。

trainAge = traindata["age"].values
trainAge = trainAge.reshape(len(trainAge),1)
trainAge = trainAge/100.0

testAge = testdata["age"].values
testAge = testAge.reshape(len(testAge),1)
testAge = testAge / 100.0

回帰を行う際のCNNの出力では、0から1の値として出力するため、全体の数値を100で除算して、CNNで予測後の値については100倍して結果とする。


モデルの構築・学習



続いて、CNNのモデルを作成していきます。inputデータとしては、画像サイズとチャネルから決定されます。
今回は前述のとおり128ピクセル×128ピクセル×1チャネル

model = Sequential()

model.add(Conv2D(filters = 32, input_shape=(128, 128, 1), 
                 kernel_size=(3, 3), strides=(1, 1), 
                 padding='same', activation='relu'))

model.add(Conv2D(filters = 32,kernel_size =(3, 3),strides =(1, 1),
                 padding = 'same', activation = 'relu'))

model.add(MaxPooling2D(pool_size =(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(filters = 64,
                 kernel_size=(3, 3), strides=(1, 1),
                 padding='same', activation='relu'))

model.add(Conv2D(filters = 64,
                 kernel_size =(3, 3),strides =(1, 1),
                 padding = 'same',activation = 'relu'))

model.add(MaxPooling2D(pool_size =(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())

model.add(Dense(units=512, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(units= 1, activation='linear'))



モデルをコンパイルして、サマリーを確認します。
なお、今回の出力は回帰ということで、出力は1つで、損失関数はMSEを選択しています。

model.compile(loss='mse', optimizer='adam', metrics=["mse"])
model.summary()





hist = model.fit(trainImg,trainAge, 
                 batch_size=8,epochs=50,
                 validation_split=0.2,verbose=1)






私のPCでは50エポックで5分ぐらいかかりました。

再代入法での評価



再代入法で一度評価をしていきます。

valage = model.predict(trainImg)

valage = np.ravel(valage*100)
trainAge = np.ravel(trainAge*100)

valdiff = abs(trainAge - valage)
print(np.mean(valdiff))
print(np.corrcoef(trainAge, valage))





誤差絶対値の平均及び相関係数はご覧の通り。
19歳程度の誤差があり、相関係数も0.645・・・・これは失敗ですね。

テストデータでの評価



再代入法で誤差がこれだけあるということは、テストデータではより大きい誤差となるのが一般的ですが、誤差を求めていきます。

valage2 = model.predict(testImg)

valage2 = np.ravel(valage2*100)
testAge = np.ravel(testAge*100)

valdiff2 = abs(testAge - valage2)
print(np.mean(valdiff2))
print("numpy corrcoef =>\n  ",np.corrcoef(testAge, valage2))







こうなると、画像から年齢は予測できないことになりそうですね。

なお、エポック数を変更した場合は

 50エポック ⇒ △25.8歳  相関係数:0.008
100エポック ⇒ △22.1歳  相関係数:0.046
200エポック ⇒ △17.3歳  相関係数:0.114

なお、各フィルタを倍にしたら
 50エポック ⇒ △21.5歳  相関係数:0.108
100エポック ⇒ △22.5歳  相関係数:0.158
200エポック ⇒ △21.74歳  相関係数:0.156



個人的には、年齢を特定できるほどの学習データがなかったことが大きな要因として考えられます。


また、人間で置き換えたとき、人工知能はいうなれば画像をまじまじと年齢と比較して覚えていっただけであり、人間でも何も知識がない状態からX線画像をまじまじとしただけで年齢が厳密に当てられるはずはないものだと思います。
この25歳弱の誤差って実際には普通の精度なのかもしれませんね。


ではでは。


プロフェッショナルPython ソフトウェアデザインの原則と実践 impress top gearシリーズ