DUICUO

ディープラーニングを用いたマラリア検出

​​

[[266327]]

​​

人工知能とオープンソースのハードウェアツールを組み合わせることで、深刻な感染症であるマラリアの診断精度を向上させることができます。

人工知能(AI)とオープンソースのツール、テクノロジー、フレームワークは、社会の発展に大きく貢献する強力な組み合わせです。「健康は財産」という言葉は陳腐に聞こえるかもしれませんが、実に的を射ています。この記事では、低コストで効果的かつ高精度なオープンソースのディープラーニング手法とAIを組み合わせ、致死的な感染症であるマラリアの検出に成功する方法を探ります。

私は医師でも医療研究者でもなく、彼らほどの資格も持ち合わせていません。ただAIを医療研究に応用することに興味があるだけです。この記事では、AIとオープンソースソリューションがマラリア検出にどのように役立ち、手作業の軽減につながるかを示すことを目指しています。

​​

​​

PythonとTensorFlow:オープンソースのディープラーニング手法を構築するための素晴らしい組み合わせ

PythonとTensorFlowのようなディープラーニングフレームワークの力のおかげで、堅牢で大規模かつ効率的なディープラーニング手法を構築できます。これらのツールは無料でオープンソースなので、誰でも簡単に導入・使用できる、非常に費用対効果の高いソリューションを構築できます。さあ、始めましょう!

プロジェクトの動機

マラリアは、マラリア原虫によって引き起こされる致死的な感染症で、蚊媒介性です。主に感染した雌のハマダラカに刺されることで感染します。マラリアを引き起こす原虫は5種類ありますが、ほとんどの症例は熱帯熱マラリア(Plasmodium falciparum)と三日熱マラリア(Plasmodium v​​ivax)の2種類によって引き起こされます。

この地図は、特に熱帯地域におけるマラリアの世界的分布を示していますが、このプロジェクトの主な目的は、この病気の性質と致死率を調べることです。

感染した雌の蚊に刺されると、蚊が媒介する寄生虫が血流に入り込み、酸素を運ぶ赤血球(RBC)を破壊し始めます。マラリアの初期症状はインフルエンザウイルスに似ていることが多く、通常は蚊に刺されてから数日から数週間以内に現れます。しかし、これらの致命的な寄生虫は、症状を引き起こさずに最大1年間体内で生存することがあり、治療が遅れると合併症や死に至ることもあります。そのため、早期発見は命を救うことができます。

世界保健機関(WHO)は、世界人口のほぼ半数がマラリアのリスクにさらされており、年間2億人以上がマラリアに感染し、約40万人が死亡していると述べています。これが、マラリアの検出と診断を迅速、簡便、かつ効果的にする必要性を生み出しています。

マラリア検出方法

マラリアの検出と診断にはいくつかの方法があります。本論文で説明するプロジェクトは、Rajaramanらの論文「薄層血液塗抹標本画像におけるマラリア原虫検出の精度向上のための特徴抽出器としての事前学習済み畳み込みニューラルネットワーク」で紹介されている手法(ポリメラーゼ連鎖反応(PCR)と迅速診断検査(RDT)など)に基づいて構築されています。これらの2つの検査は、高品質の顕微鏡検査サービスが利用できない場所で一般的に使用されています。

標準的なマラリア診断は、通常、血液塗抹標本を用いたワークフローに基づいています。Carlos Ariza氏の記事「Malaria Hero:マラリア原虫の迅速な診断のためのウェブアプリケーション」に続き、Adrian Rosebrock氏の「Kerasを用いたディープラーニングと医用画像解析」を知りました。マラリアの予防、診断、治療に関する多くのアイデアを与えてくれた、これらの優れたリソースの著者の方々に感謝します。

​​

​​

マラリア原虫の血液塗抹標本検査のワークフロー

WHOのガイドラインによると、診断には通常、血液塗抹標本を100倍に拡大した集中検査が必要です。訓練を受けた専門家が、5000個の赤血球のうち、マラリア原虫を含む赤血球の数を手作業で計算します。上記のRajaramanらの論文の説明では、以下のように引用されています。

濃厚血液塗抹標本は寄生虫の存在を検出するのに有用であり、一方、薄層血液塗抹標本は感染の原因となる寄生虫の種類を特定するのに役立ちます(米国疾病予防管理センター、2012年)。診断精度は診断医の専門知識に大きく依存し、観察者間のばらつきや、疾患が風土病となっている地域や資源が限られた地域での大規模な診断によって悪影響を受ける可能性があります(Mitiku、Mengistu、Gelaw、2003年)。代替技術としては、ポリメラーゼ連鎖反応(PCR)や迅速診断検査(RDT)などがありますが、PCR分析は性能に限界があり(Hommelsheimら、2014年)、RDTは疾患が風土病となっている地域では費用対効果が高くありません(Hawkes、Katsuva、Masumbuko、2009年)。

したがって、マラリア検出は機械学習を使用した自動化の恩恵を受ける可能性があります。

マラリア検出のためのディープラーニング

血液塗抹標本の手作業による診断は、寄生虫に感染した細胞と感染していない細胞を分類・計数する専門知識を必要とする、手間のかかる手作業です。このプロセスは、特に専門家が不足している地域では、スケールアウトが困難になる可能性があります。最先端の画像処理・解析技術を用いて、手動で選択した特徴を抽出し、機械学習ベースの分類モデルを構築する技術は、ある程度の進歩を遂げてきました。しかし、学習用のデータが不足していること、そして手動で特徴を選択するプロセスに時間がかかることから、これらのモデルは大幅なスケールアップが困難です。

ディープラーニングモデル、より具体的には畳み込みニューラルネットワーク(CNN)は、幅広いコンピュータービジョンタスクにおいて非常に効果的であることが実証されています。(CNNの背景について詳しく知りたい方は、*Convolutional Neural Networks in Visual Recognition*(CS2331n)を読むことをお勧めします。)簡単に言うと、CNNモデルの主要な層は、下の図に示すように、畳み込み層とプーリング層で構成されています。

​​

​​

典型的なCNNアーキテクチャ

畳み込み層はデータから空間的な階層パターンを学習します。畳み込み層は並進不変であるため、画像のさまざまな側面を学習できます。例えば、最初の畳み込み層はエッジやコーナーといった小さく局所的なパターンを学習し、2番目の層は最初の層の特徴に基づいてより大きなパターンを学習する、といった具合です。これにより、CNNは特徴を自動的に抽出し、新しいデータポイントに一般化できる効果的な特徴を学習できます。プーリング層は、ダウンサンプリングとデータサイズの削減に役立ちます。

そのため、CNNは自動化されたスケーラブルな特徴量エンジニアリングを可能にします。同様に、モデルの最後に高密度レイヤーを追加することで、画像分類などのタスクを実行できます。CNNのようなディープラーニングモデルを用いたマラリア自動検出は、非常に効率的で低コスト、そしてスケーラブルであり、特に転移学習と事前学習済みモデルを用いることで、限られたデータ量でも非常に優れたパフォーマンスを発揮します。

Rajaramanらの論文では、6つの事前学習済みモデルを用いたデータセットにおいて、未感染サンプルに対するマラリア検出において驚異的な95.9%の精度を達成しました。本研究では、いくつかのシンプルなCNNモデルをゼロから試用し、事前学習済みモデルを用いた転移学習によって、同じデータセットからどのような結果が得られるかを検証します。モデル構築には、PythonやTensorFlowなどのオープンソースツールとフレームワークを使用します。

データセット

我々が分析したデータは、国立医学図書館(NLM)傘下のリスターヒル国立生物医学交流センター(LHNCBC)の研究者らが提供したもので、彼らは健康な血液塗抹標本の公開画像のデータセットを綿密に収集し、ラベル付けした。研究者らは、従来の光学顕微鏡に接続したAndroidスマートフォンで動作するマラリア検出モバイルアプリケーションを開発した。彼らは、熱帯熱マラリア原虫(Plasmodium falciparum)に感染した患者から採取した150枚の薄い血液塗抹標本と、健康な患者から採取した50枚の薄い血液塗抹標本をギムザ染色で染色した。これらの塗抹標本はバングラデシュのチッタゴン医科大学病院で収集され、撮影された。各顕微鏡ウィンドウ内の画像は、スマートフォンの内蔵カメラを使用して撮影された。これらの画像は、タイのバンコクにあるマヒドン・オックスフォード熱帯医学研究所の専門家がスライドリーダーを使用してラベル付けした。

データセットの構造を簡単に見てみましょう。まず、使用しているオペレーティングシステムに応じて、いくつかの基本的な依存関係をインストールします。

​​

​​

依存関係のインストール

モデルをより高速に実行するために、クラウドでGPUを搭載したDebianベースのオペレーティングシステムを使用しています。ディレクトリ構造を確認するには、 ​sudo apt install tree​を使用して​tree​とその依存関係(まだインストールされていない場合)をインストールする必要があります。

​​

​​

ツリー依存関係のインストール

感染細胞と健常細胞を含む血液細胞の画像が保存されたフォルダが2つあります。以下のコマンドを入力すると、画像の総数に関する詳細情報を取得できます。

インポートOS
インポートグロブ

base_dir = os.path.join('./cell_images')
infected_dir = os.path.join(base_dir,'寄生')
healthy_dir = os.path.join(base_dir,'感染していない')

感染ファイル = glob.glob(感染ディレクトリ+'/*.png')
healthy_files = glob.glob(healthy_dir+'/*.png')
len(感染ファイル数), len(正常なファイル数)

# 出力
(13779, 13779)

マラリアの画像13,779枚とマラリアではない(健康な)血液細胞の画像13,779枚を含むバランスの取れたデータセットができたようです。これらに基づいてデータフレームを作成し、データセットの構築に使用しましょう。

 numpyをnpとしてインポートする
pandasをpdとしてインポートする

np.ランダムシード(42)

files_df = pd.DataFrame({
'ファイル名': 感染ファイル + 正常なファイル、
'ラベル': ['マラリア'] * len(感染ファイル数) + ['健康'] * len(健康なファイル数)
}).sample(frac=1, random_state=42).reset_index(drop=True)

files_df.head()

​​

​​

データセット

画像データセットの構築と理解

ディープラーニングモデルを構築するには、学習データが必要ですが、同時に未知のデータを用いてモデルのパフォーマンスをテストする必要があります。そこで、学習用データセット、検証用データセット、テスト用データセットを60:10:30の割合で分けます。学習時には学習用データセットと検証用データセットを適用し、テスト用データセットはモデルのパフォーマンスを確認するために使用します。

 sklearn.model_selectionからtrain_test_splitをインポートする
コレクションからカウンターをインポート

train_files、test_files、train_labels、test_labels = train_test_split(files_df['filename'].values、
files_df['label'].values、
テストサイズ=0.3、ランダム状態=42)
train_files、val_files、train_labels、val_labels = train_test_split(train_files、
train_labels、
テストサイズ=0.1、ランダム状態=42)

print(train_files.shape, val_files.shape, test_files.shape)
print('Train:', Counter(train_labels), '\nVal:', Counter(val_labels), '\nTest:', Counter(test_labels))

# 出力
(17361,)(1929,)(8268,)
電車: カウンター({'healthy': 8734, 'malaria': 8627})
値: Counter({'healthy': 970, 'malaria': 959})
テスト: Counter({'malaria': 4193, 'healthy': 4075})

これらの画像は同じサイズではありません。血液塗抹標本と細胞画像は、人、検査方法、画像の向きによって異なるためです。最適な画像サイズを決定するために、トレーニングデータセットの統計情報をまとめてみましょう(テストデータセットには一切手を加えないことに留意してください)。

 cv2をインポート
同時輸入先物から
インポートスレッド

get_img_shape_parallel(idx, img, total_imgs) を定義します。
idx % 5000 == 0 または idx == (total_imgs - 1) の場合:
print('{}: 画像番号: {}'.format(threading.current_thread().name,
idx))
cv2.imread(img).shape を返す

例 = futures.ThreadPoolExecutor(max_workers=なし)
data_inp = [(idx, img, len(train_files)) idx、img が enumerate(train_files)] の場合
print('Img形状の計算を開始:')
train_img_dims_map = ex.map(get_img_shape_parallel,
[data_inpのレコードのレコード[0]]、
[data_inpのレコードのレコード[1]]、
[data_inpのレコードのレコード[2]])
train_img_dims = リスト(train_img_dims_map)
print('最小寸法:', np.min(train_img_dims, axis=0))
print('平均寸法:', np.mean(train_img_dims, axis=0))
print('中央値寸法:', np.median(train_img_dims, axis=0))
print('最大次元:', np.max(train_img_dims, axis=0))


# 出力
Img シェイプの計算を開始します:
ThreadPoolExecutor-0_0: 画像番号 0 で作業中
ThreadPoolExecutor-0_17: 画像番号 5000 を処理中
ThreadPoolExecutor-0_15: 画像番号 10000 で作業中
ThreadPoolExecutor-0_1: 画像番号 15000 で作業中
ThreadPoolExecutor-0_7: 画像番号 17360 で作業中
最小寸法: [46 46 3]
平均寸法: [132.77311215 132.45757733 3.]
平均寸法: [130. 130. 3.]
最大寸法: [385 394 3]

画像の読み込みを高速化するために並列処理を適用し、集計された統計情報に基づいて各画像を125x125ピクセルにリサイズします。すべての画像を読み込み、この固定サイズにリサイズしてみましょう。

 IMG_DIMS = (125, 125)

get_img_data_parallel(idx, img, total_imgs) を定義します。
idx % 5000 == 0 または idx == (total_imgs - 1) の場合:
print('{}: 画像番号: {}'.format(threading.current_thread().name,
idx))
img = cv2.imread(img)
img = cv2.resize(img, dsize=IMG_DIMS,
補間=cv2.INTER_CUBIC)
img = np.array(img, dtype=np.float32)
画像を返す

例 = futures.ThreadPoolExecutor(max_workers=なし)
train_data_inp = [(idx, img, len(train_files)) idx、img が enumerate(train_files)] の場合
val_data_inp = [(idx, img, len(val_files)) idx、img が enumerate(val_files)]
test_data_inp = [(idx, img, len(test_files)) idx、img が enumerate(test_files)] の場合

print('列車の画像を読み込んでいます:')
train_data_map = ex.map(get_img_data_parallel,
[train_data_inpのレコードのレコード[0]]、
[train_data_inpのレコードのレコード[1]]、
[train_data_inpのレコードのレコード[2]])
train_data = np.array(リスト(train_data_map))

print('\n検証画像を読み込んでいます:')
val_data_map = ex.map(get_img_data_parallel,
[val_data_inpのレコードのレコード[0]]、
[val_data_inpのレコードのレコード[1]]、
[val_data_inpのレコードのレコード[2]])
val_data = np.array(リスト(val_data_map))

print('\nテスト画像を読み込んでいます:')
test_data_map = ex.map(get_img_data_parallel,
[test_data_inpのレコードのレコード[0]]、
[test_data_inpのレコードのレコード[1]]、
[test_data_inpのレコードのレコード[2]])
test_data = np.array(リスト(test_data_map))

train_data.shape、val_data.shape、test_data.shape


# 出力
列車の画像を読み込んでいます:
ThreadPoolExecutor-1_0: 画像番号 0 で作業中
ThreadPoolExecutor-1_12: 画像番号 5000 を処理中
ThreadPoolExecutor-1_6: 画像番号 10000 で作業中
ThreadPoolExecutor-1_10: 画像番号 15000 を処理中
ThreadPoolExecutor-1_3: 画像番号 17360 で作業中

検証画像を読み込んでいます:
ThreadPoolExecutor-1_13: 画像番号 0 で作業中
ThreadPoolExecutor-1_18: 画像番号 1928 で作業中

テスト画像の読み込み:
ThreadPoolExecutor-1_5: 画像番号 0 で作業中
ThreadPoolExecutor-1_19: 画像番号 5000 を処理中
ThreadPoolExecutor-1_8: 画像番号 8267 で作業中
((17361, 125, 125, 3), (1929, 125, 125, 3), (8268, 125, 125, 3))

画像の読み込みとサイズ変更に関連する計算を高速化するために、再び並列処理を適用します。最終的に、前の出力に示されているように、目的のサイズの画像テンソルが得られます。ここで、データの全体像を把握するために、いくつかの血球画像サンプルを調べてみましょう。

 matplotlib.pyplot を plt としてインポートします。
%matplotlib インライン

plt.figure(1, 図のサイズ = (8, 8))
0
iが範囲(16)内にある場合:
n += 1
r = np.random.randint(0, train_data.shape[0], 1)
plt.subplot(4, 4, n)
plt.subplots_adjust(hspace = 0.5、wspace = 0.5)
plt.imshow(train_data[r[0]]/255.)
plt.title('{}'.format(train_labels[r[0]]))
plt.xticks([]) 、 plt.yticks([])

​​

​​

マラリア細胞サンプル

これらのサンプル画像に基づいて、マラリア細胞と健康な細胞の画像の間に微妙な違いがあることを観察しました。これらのパターンを、ディープラーニングモデルを用いたモデルトレーニング中に学習させていきます。

モデルのトレーニングを始める前に、いくつかの基本的な構成設定を設定する必要があります。

バッチサイズ = 64
クラス数 = 2
エポック = 25
入力形状 = (125, 125, 3)

train_imgs_scaled = train_data / 255 です。
val_imgs_scaled = val_data / 255。

# テキストカテゴリラベルをエンコードする
sklearn.preprocessing から LabelEncoder をインポートします

le = ラベルエンコーダ()
le.fit(train_labels)
train_labels_enc = le.transform(train_labels)
val_labels_enc = le.transform(val_labels)

印刷(train_labels[:6], train_labels_enc[:6])


# 出力
['マラリア' 'マラリア' 'マラリア' '健康' '健康' 'マラリア'] [1 1 1 0 0 1]

画像サイズ、バッチサイズ、エポックを固定し、分類用のクラスラベルをエンコードします。TensorFlow 2.0は2019年3月にリリースされたため、この演習はTensorFlowを試す絶好の機会です。

 TensorflowをTFとしてインポートする

# TensorBoard ノートブック拡張機能をロードする(オプション)
%load_ext テンソルボードノートブック

tf.random.set_seed(42)
tf.__バージョン__

# 出力
'2.0.0-alpha0'

ディープラーニングトレーニング

モデルのトレーニングフェーズでは、3つのディープラーニングモデルを構築し、トレーニングセットを使用してトレーニングを行い、検証データを使用してパフォーマンスを比較します。その後、これらのモデルを保存し、後続のモデル評価フェーズで使用します。

モデル1: ゼロからのCNN

最初のマラリア検出モデルは、基本的なCNNを用いてゼロから構築・学習します。まず、モデルのアーキテクチャを定義しましょう。

 inp = tf.keras.layers.Input(shape=INPUT_SHAPE)

conv1 = tf.keras.layers.Conv2D(32, カーネルサイズ=(3, 3),
アクティベーション='relu'、パディング='same')(inp)
プール1 = tf.keras.layers.MaxPooling2D(プールサイズ=(2, 2))(conv1)
conv2 = tf.keras.layers.Conv2D(64, カーネルサイズ=(3, 3),
アクティベーション='relu'、パディング='same')(プール1)
pool2 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(conv2)
conv3 = tf.keras.layers.Conv2D(128, カーネルサイズ=(3, 3),
アクティベーション='relu'、パディング='same')(プール2)
pool3 = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))(conv3)

フラット = tf.keras.layers.Flatten()(pool3)

隠し1 = tf.keras.layers.Dense(512, アクティベーション='relu')(フラット)
drop1 = tf.keras.layers.Dropout(rate=0.3)(hidden1)
隠し2 = tf.keras.layers.Dense(512, アクティベーション='relu')(ドロップ1)
drop2 = tf.keras.layers.Dropout(レート=0.3)(hidden2)

出力 = tf.keras.layers.Dense(1, アクティベーション='シグモイド')(drop2)

モデル = tf.keras.Model(入力=inp、出力=out)
model.compile(optimizer='adam',
損失='binary_crossentropy'、
メトリック=['精度'])
モデル.サマリー()


# 出力
モデル:「モデル」
_________________________________________________________________
レイヤー(タイプ)出力形状パラメータ番号
=================================================================
input_1 (入力レイヤー) [(なし, 125, 125, 3)] 0
_________________________________________________________________
conv2d (Conv2D) (なし, 125, 125, 32) 896
_________________________________________________________________
max_pooling2d (MaxPooling2D) (なし, 62, 62, 32) 0
_________________________________________________________________
conv2d_1 (Conv2D) (なし, 62, 62, 64) 18496
_________________________________________________________________
...
...
_________________________________________________________________
dense_1 (密) (なし, 512) 262656
_________________________________________________________________
dropout_1 (ドロップアウト) (なし, 512) 0
_________________________________________________________________
dense_2 (密) (なし, 1) 513
=================================================================
合計パラメータ: 15,102,529
トレーニング可能なパラメータ: 15,102,529
トレーニング不可能なパラメータ: 0
_________________________________________________________________

このコードのアーキテクチャに基づくと、CNNモデルは3つの畳み込み層と1つのプーリング層、そして2つの密層と正則化のための非活性化層で構成されています。それでは、モデルを学習してみましょう。

日時をインポート

logdir = os.path.join('/home/dipanzan_sarkar/projects/tensorboard_logs',
datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
tensorboard_callback = tf.keras.callbacks.TensorBoard(logdir, histogram_freq=1)
reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(モニター='val_loss'、係数=0.5、
忍耐力=2、min_lr=0.000001)
コールバック = [reduce_lr, tensorboard_callback]

履歴 = model.fit(x=train_imgs_scaled, y=train_labels_enc,
バッチサイズ=BATCH_SIZE、
エポック=EPOCHS,
検証データ=(val_imgs_scaled, val_labels_enc),
コールバック=コールバック、
詳細=1)


# 出力
17361 個のサンプルでトレーニングし、1929 個のサンプルで検証
エポック 1/25
17361/17361 [====] - 32秒 2ミリ秒/サンプル - 損失: 0.4373 - 精度: 0.7814 - val_loss: 0.1834 - val_accuracy: 0.9393
エポック2/25
17361/17361 [====] - 30秒 2ミリ秒/サンプル - 損失: 0.1725 - 精度: 0.9434 - val_loss: 0.1567 - val_accuracy: 0.9513
...
...
エポック24/25
17361/17361 [====] - 30秒 2ミリ秒/サンプル - 損失: 0.0036 - 精度: 0.9993 - val_loss: 0.3693 - val_accuracy: 0.9565
エポック 25/25
17361/17361 [====] - 30秒 2ミリ秒/サンプル - 損失: 0.0034 - 精度: 0.9994 - val_loss: 0.3699 - val_accuracy: 0.9559

検証精度は95.6%と良好ですが、モデルはやや過剰適合しているように見えます(トレーニング精度は99.9%です)。トレーニングと検証の精度と損失曲線をプロットすると、このことがはっきりと分かります。

 f, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
t = f.suptitle('基本的なCNNパフォーマンス', フォントサイズ=12)
f.subplots_adjust(トップ=0.85、wspace=0.3)

max_epoch = len(history.history['accuracy'])+1
epoch_list = list(範囲(1,最大epoch))
ax1.plot(epoch_list, history.history['accuracy'], label='トレーニング精度')
ax1.plot(epoch_list, history.history['val_accuracy'], label='検証精度')
ax1.set_xticks(np.arange(1, max_epoch, 5))
ax1.set_ylabel('精度値')
ax1.set_xlabel('エポック')
ax1.set_title('精度')
l1 = ax1.legend(loc="best")

ax2.plot(epoch_list, history.history['loss'], label='列車損失')
ax2.plot(epoch_list, history.history['val_loss'], label='検証損失')
ax2.set_xticks(np.arange(1, max_epoch, 5))
ax2.set_ylabel('損失値')
ax2.set_xlabel('エポック')
ax2.set_title('損失')
l2 = ax2.legend(loc="best")

​​

​​

基本的なCNNの学習曲線

第5期になっても状況はあまり改善されていないことがわかります。このモデルは将来の評価のために保存しておきましょう。

モデルを保存('basic_cnn.h5')

深層転移学習

人間が生来、異なるタスク間で知識を転移する能力を持っているように、転移学習は、機械学習やディープラーニングの文脈においても、以前のタスクで学んだ知識を新しい関連タスクに適用することを可能にします。転移学習についてさらに深く知りたい方は、私の記事「ディープラーニングにおける転移学習の分かりやすいガイド:実世界アプリケーション」と著書『Pythonによる転移学習の実践』をお読みください。

​​

​​

深層転移学習の考え方

この実践で探求したいアイデアは次のとおりです。

私たちの問題の文脈では、事前トレーニング済みのディープラーニング モデル (ImageNet などの大規模なデータセットでトレーニング済み) を活用し、知識を適用および転送することで、マラリア検出の問題を解決できるでしょうか。

最も人気のある 2 つのディープ トランスファー ラーニング戦略を適用します。

  • 特徴抽出器としての事前学習済みモデル
  • 微調整された事前学習済みモデル

実験には、ケンブリッジ大学のVisual Geometry Group (VGG) が開発した事前学習済みのVGG-19ディープラーニングモデルを使用します。VGG-19のような事前学習済みモデルは、大規模なデータセット(ImageNet)を用いて、様々な画像分類手法を用いて学習されます。そのため、このモデルは、CNNモデルによって学習された特徴と比較して、空間不変、回転不変、並進不変の堅牢な特徴階層を学習しているはずです。したがって、数百万枚の画像から優れた特徴セットを学習したこのモデルは、マラリア検出などのコンピュータービジョンの問題に適した新しい画像を抽出するための優れた特徴抽出器として機能します。転移学習機能を本稿の問題に適用する前に、まずVGG-19モデルについて説明しましょう。

VGG-19モデルの理解

VGG-19モデルは、画像認識と分類のために開発されたImageNetデータベース上に構築された、19層(畳み込み層と全結合層)のディープラーニングネットワークです。このモデルはKaren SimonyanとAndrew Zissermanによって構築され、論文「大規模画像認識のための超深層畳み込みネットワーク」で説明されています。VGG-19のアーキテクチャは以下のとおりです。

​​

​​

VGG-19 モデルアーキテクチャ

3x3畳み込みフィルタを用いた合計16層の畳み込み層、ダウンサンプリング用の最大プーリング層、そしてそれぞれ4096ユニットの全結合型隠れ層2層、そしてImageNetデータセットのカテゴリを表すそれぞれ1000ユニットの密層が続いていることがわかります。マラリア予測には独自の全結合型密層を使用するため、最後の3層は必要ありません。私たちがより重視するのは最初の5つのブロックなので、VGGモデルを効率的な特徴抽出器として活用できます。

モデルの1つを単純な特徴抽出器として使用し、5つの畳み込みブロックを凍結することで、各エポック後に重みが更新されないようにします。最後のモデルについては、VGGモデルを微調整し、最後の2つのブロック(4番目と5番目)を解凍して、モデルを学習する際に各エポック(データバッチごと)で重みが更新されるようにします。

モデル2: 特徴抽出器としての事前学習済みモデル

このモデルを構築するには、TensorFlowを使用してVGG-19モデルを読み込み、畳み込みブロックを固定して特徴抽出器として使用できるようにします。最後に、分類タスクを実行するために独自の密層を挿入します。

 vgg = tf.keras.applications.vgg19.VGG19(include_top=False, weights='imagenet',
input_shape=入力シェイプ)
vgg.trainable = False
# レイヤーをフリーズする
vgg.layers のレイヤーの場合:
レイヤー.trainable = False

ベース_vgg = vgg
base_out = base_vgg.出力
pool_out = tf.keras.layers.Flatten()(base_out)
隠し1 = tf.keras.layers.Dense(512, アクティベーション='relu')(pool_out)
drop1 = tf.keras.layers.Dropout(rate=0.3)(hidden1)
隠し2 = tf.keras.layers.Dense(512, アクティベーション='relu')(ドロップ1)
drop2 = tf.keras.layers.Dropout(レート=0.3)(hidden2)

出力 = tf.keras.layers.Dense(1, アクティベーション='シグモイド')(drop2)

モデル = tf.keras.Model(入力 = base_vgg.input、出力 = out)
モデル.コンパイル(オプティマイザー=tf.keras.optimizers.RMSprop(lr=1e-4)、
損失='binary_crossentropy'、
メトリック=['精度'])
モデル.サマリー()


# 出力
モデル: "model_1"
_________________________________________________________________
レイヤー(タイプ)出力形状パラメータ番号
=================================================================
input_2 (入力レイヤー) [(なし, 125, 125, 3)] 0
_________________________________________________________________
block1_conv1 (Conv2D) (なし, 125, 125, 64) 1792
_________________________________________________________________
block1_conv2 (Conv2D) (なし, 125, 125, 64) 36928
_________________________________________________________________
...
...
_________________________________________________________________
block5_pool (MaxPooling2D) (なし, 3, 3, 512) 0
_________________________________________________________________
flatten_1 (平坦化) (なし、4608) 0
_________________________________________________________________
dense_3 (密) (なし, 512) 2359808
_________________________________________________________________
dropout_2 (ドロップアウト) (なし, 512) 0
_________________________________________________________________
dense_4 (密) (なし, 512) 262656
_________________________________________________________________
dropout_3 (ドロップアウト) (なし, 512) 0
_________________________________________________________________
dense_5 (密) (なし, 1) 513
=================================================================
合計パラメータ: 22,647,361
トレーニング可能なパラメータ: 2,622,977
トレーニング不可能なパラメータ: 20,024,384
_________________________________________________________________

全体的な出力から明らかなように、モデルには多くの層があり、特徴抽出器としてVGG-19モデルの固定層のみを使用します。以下のコードを使用して、モデルの実際に学習可能な層の数と、ネットワーク全体に存在する層の数を確認できます。

 print("合計レイヤー数:", len(model.layers))
print("トレーニング可能なレイヤーの合計数:",
sum([model.layers内のlがl.trainableの場合1]))

# 出力
合計レイヤー数: 28
トレーニング可能なレイヤーの総数: 6

以前のモデルと同様の設定とコールバックを使用して、モデルをトレーニングします。モデルのトレーニングに必要な完全なコードについては、私のGitHubリポジトリを参照してください。モデルの精度と損失曲線を示すグラフを以下に示します。

​​

​​

凍結された事前学習済みCNNの学習曲線

これは、モデルがベースCNNモデルほど過学習していないものの、パフォーマンスはわずかに劣っていることを示しています。このモデルは将来の評価のために保存しておきましょう。

モデルを保存('vgg_frozen.h5')

モデル3: 画像拡張を使用して事前学習済みモデルを微調整する

最終モデルでは、定義済みのVGG-19モデルの最後の2つのブロックのレイヤーの重みを微調整します。また、画像拡張の概念も紹介します。画像拡張の背後にある考え方は、その名前が示す通りです。トレーニングデータセットから既存の画像を読み込み、回転、トリミング、変形、スケーリングなどの変換操作を適用して、新しい、変更されたバージョンを作成します。これらのランダムな変換により、取得される画像は毎回異なります。画像拡張ツールの構築には、tf.kerasのImageDataGeneratorという優れたツールを使用します。

 train_datagen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255,
ズーム範囲=0.05、
回転範囲=25、
幅シフト範囲=0.05、
高さシフト範囲=0.05、
shear_range=0.05、horizo​​ntal_flip=True、
fill_mode='nearest')

val_datagen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255)

# 画像拡張ジェネレータを構築する
train_generator = train_datagen.flow(train_data, train_labels_enc, batch_size=BATCH_SIZE, shuffle=True)
val_generator = val_datagen.flow(val_data, val_labels_enc, batch_size=BATCH_SIZE, shuffle=False)

検証データセットには、各エポックにおけるモデル性能を評価するために使用するため、いかなる変換も行いません(サイズ変更は必須です)。輸送学習環境における画像拡張の詳細な説明については、上記で引用した記事を参照してください。画像拡張変換をバッチ処理した結果のサンプルを見てみましょう。

画像ID = 0
sample_generator = train_datagen.flow(train_data[img_id:img_id+1], train_labels[img_id:img_id+1],
バッチサイズ=1)
サンプル = [next(sample_generator)、i が範囲(0,5)]
図、ax = plt.subplots(1,5, figsize=(16, 6))
print('ラベル:', [サンプル内のアイテムのアイテム[1][0]])
l = [ax[i].imshow(sample[i][0][0])、iが範囲(0,5)]

​​

​​

拡張画像のサンプル

前回の出力と比べて、画像にわずかな変化がはっきりと確認できます。次に、VGG-19モデルの最後の2つのブロックが学習可能であることを確認しながら、学習モデルを構築します。

 vgg = tf.keras.applications.vgg19.VGG19(include_top=False, weights='imagenet',
input_shape=入力シェイプ)
# レイヤーをフリーズする
vgg.trainable = True

set_trainable = False
vgg.layers のレイヤーの場合:
['block5_conv1', 'block4_conv1'] 内の layer.name の場合:
set_trainable = True
set_trainable の場合:
レイヤー.trainable = True
それ以外:
レイヤー.trainable = False

ベース_vgg = vgg
base_out = base_vgg.出力
pool_out = tf.keras.layers.Flatten()(base_out)
隠し1 = tf.keras.layers.Dense(512, アクティベーション='relu')(pool_out)
drop1 = tf.keras.layers.Dropout(rate=0.3)(hidden1)
隠し2 = tf.keras.layers.Dense(512, アクティベーション='relu')(ドロップ1)
drop2 = tf.keras.layers.Dropout(レート=0.3)(hidden2)

出力 = tf.keras.layers.Dense(1, アクティベーション='シグモイド')(drop2)

モデル = tf.keras.Model(入力 = base_vgg.input、出力 = out)
モデル.コンパイル(オプティマイザー=tf.keras.optimizers.RMSprop(lr=1e-5),
損失='binary_crossentropy'、
メトリック=['精度'])

print("合計レイヤー数:", len(model.layers))
print("トレーニング可能なレイヤーの合計数:", sum([1 for l in model.layers if l.trainable]))


# 出力
合計レイヤー数: 28
トレーニング可能なレイヤーの総数: 16

このモデルでは、ファインチューニング中に事前学習済みレイヤーの位置重みの大幅な更新を避けたいため、学習率を下げました。データジェネレータを使用しているため、モデルの学習プロセスは若干異なる可能性があります。そのため、 ​fit_generator(...)​関数を適用します。

 tensorboard_callback = tf.keras.callbacks.TensorBoard(logdir, histogram_freq=1)
reduce_lr = tf.keras.callbacks.ReduceLROnPlateau(モニター='val_loss'、係数=0.5、
忍耐力=2、min_lr=0.000001)

コールバック = [reduce_lr, tensorboard_callback]
train_steps_per_epoch = train_generator.n // train_generator.batch_size
val_steps_per_epoch = val_generator.n // val_generator.batch_size
history = model.fit_generator(train_generator, steps_per_epoch=train_steps_per_epoch, epochs=EPOCHS,
検証データ=val_generator、検証ステップ=val_steps_per_epoch、
詳細=1)


# 出力
エポック 1/25
271/271 [====] - 133秒 489ミリ秒/ステップ - 損失: 0.2267 - 精度: 0.9117 - val_loss: 0.1414 - val_accuracy: 0.9531
エポック2/25
271/271 [====] - 129秒 475ミリ秒/ステップ - 損失: 0.1399 - 精度: 0.9552 - val_loss: 0.1292 - val_accuracy: 0.9589
...
...
エポック24/25
271/271 [====] - 128秒 473ミリ秒/ステップ - 損失: 0.0815 - 精度: 0.9727 - val_loss: 0.1466 - val_accuracy: 0.9682
エポック 25/25
271/271 [====] - 128秒 473ミリ秒/ステップ - 損失: 0.0792 - 精度: 0.9729 - val_loss: 0.1127 - val_accuracy: 0.9641

これは我々にとって最良のモデルのようです。検証精度はほぼ96.5%で、学習精度に基づくと、最初のモデルのように過学習しているようには見えません。これは以下の学習曲線で確認できます。

​​

​​

細かく調整された事前学習済みCNNの学習曲線

このモデルを保存して、テスト セットで使用できるようにします。

モデルを保存('vgg_finetuned.h5')

これでモデルのトレーニングフェーズは完了です。テストセットでモデルのパフォーマンスをテストする準備が整いました。

ディープラーニングモデルの性能評価

我们将通过在我们的测试集上做预测来评估我们在训练阶段构建的三个模型,因为仅仅验证是不够的!我们同样构建了一个检测工具模块叫做​​model_evaluation_utils​ ​,我们可以使用相关分类指标用来评估使用我们深度学习模型的性能。第一步是扩展我们的数据集。

 test_imgs_scaled = test_data / 255.
test_imgs_scaled.shape, test_labels.shape

# 出力
((8268, 125, 125, 3), (8268,))

下一步包括载入我们保存的深度学习模型,在测试集上预测。

 # Load Saved Deep Learning Models
basic_cnn = tf.keras.models.load_model('./basic_cnn.h5')
vgg_frz = tf.keras.models.load_model('./vgg_frozen.h5')
vgg_ft = tf.keras.models.load_model('./vgg_finetuned.h5')

# Make Predictions on Test Data
basic_cnn_preds = basic_cnn.predict(test_imgs_scaled, batch_size=512)
vgg_frz_preds = vgg_frz.predict(test_imgs_scaled, batch_size=512)
vgg_ft_preds = vgg_ft.predict(test_imgs_scaled, batch_size=512)

basic_cnn_pred_labels = le.inverse_transform([1 if pred > 0.5 else 0
for pred in basic_cnn_preds.ravel()])
vgg_frz_pred_labels = le.inverse_transform([1 if pred > 0.5 else 0
for pred in vgg_frz_preds.ravel()])
vgg_ft_pred_labels = le.inverse_transform([1 if pred > 0.5 else 0
for pred in vgg_ft_preds.ravel()])

下一步是应用我们的​​model_evaluation_utils​​ 模块根据相应分类指标来检查每个模块的性能。

 import model_evaluation_utils as meu
pandasをpdとしてインポートする

basic_cnn_metrics = meu.get_metrics(true_labels=test_labels, predicted_labels=basic_cnn_pred_labels)
vgg_frz_metrics = meu.get_metrics(true_labels=test_labels, predicted_labels=vgg_frz_pred_labels)
vgg_ft_metrics = meu.get_metrics(true_labels=test_labels, predicted_labels=vgg_ft_pred_labels)

pd.DataFrame([basic_cnn_metrics, vgg_frz_metrics, vgg_ft_metrics],
index=['Basic CNN', 'VGG-19 Frozen', 'VGG-19 Fine-tuned'])

​​

​​

Model accuracy

看起来我们的第三个模型在我们的测试集上执行的最好,给出了一个模型精确性为96% 的F1 得分,这非常好,与我们之前提到的研究论文和文章中的更复杂的模型相当。

要約

疟疾检测不是一个简单的过程,全球的合格人员的不足在病例诊断和治疗当中是一个严重的问题。我们研究了一个关于疟疾的有趣的真实世界的医学影像案例。利用AI 的、易于构建的、开源的技术在检测疟疾方面可以为我们提供最先进的精确性,因此使AI 具有社会效益。

我鼓励你查看这篇文章中提到的文章和研究论文,没有它们,我就不能形成概念并写出来。如果你对运行和采纳这些技术感兴趣,本篇文章所有的代码都可以在​​我的GitHub 仓库​​​获得。记得从​​官方网站​​下载数据。

让我们希望在健康医疗方面更多的采纳开源的AI 能力,使它在世界范围内变得更便宜、更易用。