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

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

【医療画像AI】X線胸部画像から性別を予測してみた#005



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


今回もまた、日本放射線技術学会 画像部会のページから、正解ラベルのあるX線胸部画像データから性別を予測する分類問題を解いていきます。


今回は、正解データのファイルがtxtファイルになっていることに注意します。


久しぶりにPythonをやっているのですが、CNNの勉強をし出すと、結構奥が深いことが分かってきます。


あと言えることは、ローカルでやっていると、私の現在のPCでは火が噴きそうなぐらいファンが回っていることですかね。


そろそろ買い替え時かもしれません。



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



いつもの通り、サンプルとなるデータ及び正解ラベルの入ったファイルを準備していきます。


サンプルデータが日本放射線技術学会 画像部会のページからダウンロードできます。


http://imgcom.jsrt.or.jp/imgcom/wp-content/uploads/2018/12/Gender01_Index.zip


ダウンロードしたら、解凍を行ってください。


すると、「train」「test」「list_train.txt」「list_test.txt」と2つのフォルダと2つのテキストファイルができますので、pythonファイルのレイヤーに保存してください。

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



最近はCNNとその前処理ばかりですので、使うライブラリもおなじみとなってきました。
以下のライブラリ・オブジェクトをインポートしてください。

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.utils import to_categorical
from sklearn.metrics import accuracy_score, confusion_matrix

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



今回は2値分類ですが、せっかくなのでCNNで出力で男性・女性をそれぞれ測定してみます。

データの前処理その1(データ情報のロード)



今回は、正解ラベルデータがtextファイルですので、pd.read_tableを使用します。


この時、headerが今回のファイルに存在しないことに注意し、読み込みます。
データフレームになっているので、カラムだけわかりやすいように変更しておきます。

train_data = pd.read_table("./list_train.txt",header = None, delimiter =',')
train_data = train_data.rename(columns={0:'FPass', 1:'Sex'})

test_data = pd.read_table("./list_test.txt", header = None, delimiter =',')
test_data = test_data.rename(columns={0:'FPass', 1:'Sex'})


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



データ情報をロードしたら、その情報からファイル名(ファイルパス)が分かるので、ファイルごとの画像データを取得していきます。


【学習用データ】

trainImg = np.zeros((len(train_data["FPass"]), 128, 128, 1))

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

trainImg.shape


今回は、128×128ピクセル×1チャネル(グレースケール)画像を取り扱っていくことが分かります。
また、トレーニングデータは154枚の画像であることも確認できます。

【テスト用データ】

testImg = np.zeros((len(test_data["FPass"]), 128, 128, 1))

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

testImg.shape


テストデータは、93枚で、その他の次元は同じとなっています。

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

正解ラベルを作成していきます。こちらは初めにロードしたデータから取得します。
データを確認すると、「male」と「female」の表記になっていますので、「male」=「0」、「female」=「1」と置き換えを実施します。

trainSex = train_data["Sex"].values
trainSex = trainSex.reshape(len(trainSex),1)
trainSex = np.where(trainSex == "male", 0, 1)
Onehot_label_train = to_categorical(trainSex, 2)

testSex = test_data["Sex"].values
testSex = testSex.reshape(len(testSex),1)
testSex = testSex 
testSex = np.where(testSex == "male", 0, 1)
Onehot_label_test = to_categorical(testSex, 2)



最後に、学習用ラベル・テスト用のラベル共にone-hotベクトル化しておきます。

モデルの構築・学習



モデルは、いつものようにCNNを構築していきます。特徴抽出は
「畳み込み層」×「畳み込み層」×「プーリング層」×「ドロップアウト」×「畳み込み層」×「畳み込み層」となっています。


また、予測部分については、「全結合層」×「ドロップアウト」×「全結合層」としています。
出力の活性化関数は、分類ということもあり、softmax関数を用いています。

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= 2, activation='softmax'))



モデルの構築はこのような形にしています。
出力のノードが2であるのは、男性か女性かの出力を出すためです。
なお、1でも大丈夫ですが、前処理を少し変えないといけません。(ワンホットしたのが悪い?)
よく考えたら、One-hotベクトル化した時点で二つのカラムのvalueは全て同じになりますね。。。以後気を付けます。

model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',metrics=['accuracy'])

model.summary()



モデルをコンパイルしてサマリを見てみます。
なお、損失関数は、loss='sparse_categorical_crossentropy'を利用していますが、CNNの出力を1としている場合は、binary_crossentropyを使ってください。





それでは、実際に学習をさせていきます。

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






validationの正答率で93.5%でていますね。少し期待できそうです。


テストデータでの評価


valsex = model.predict(testImg)
valsex = np.where(valsex >0.5,1,0)



作成したモデルにテストデータを入れていきます。
予測した結果は、確率ででてくるためしきい値を0.5として0、1の2値に直します。

results = list(model.predict_classes(testImg, verbose=1))

cmatrix = confusion_matrix(testSex, results)
print(cmatrix)

score = accuracy_score(testSex, results)
print(score)







学習用のデータのvalidation正解率は95%程度でしたが、テストデータにおいては、85%と10ポイント下がっていました。
この精度がいいのか悪いのかよく分かりません・・・今度、検定をやってみて有効かどうかを確認してみたいと思います。


ではでは。


図解即戦力 機械学習&ディープラーニングのしくみと技術がこれ1冊でしっかりわかる教科書