【機械学習】皮膚がん判別アプリを作成してみた

# 1.はじめに
学習の成果として，「皮膚がん判別アプリ」を作成しました．
近年の機械学習の進展やニュースを目にすることで，機械学習に興味を持っていましたが， 仕事でその分野を扱う機会はありませんでした．
そこで， 今回プログラミングスクールを活用し，機械学習の勉強を行うことにしました．
その成果として，皮膚がん判別アプリの作成を行いました．
以下，その詳細を報告いたします．


# 2.目的
どのような病気であっても，早期発見は治療の鍵となります．
特に「皮膚がん」は自分で目視できる部位に発生しやすいため，気になった場合には手軽にアプリを使用して確認できると便利です．
また，アプリを利用し万が一異常を検知した場合には，早急に医療機関を受診するよう促すことができれば，早期発見の助けとなります．
本アプリはまだまだ信頼性に欠ける部分がありますが，このようなコンセプトを基に作成しました．
今後はさらなる信頼性の向上やアップデートを行い，より精度が高くなるよう努めてまいります．


# 3.アプリ概要
作成したアプリ自体は非常にシンプルなものになります．
自分で撮影したほくろやシミなどの画像をアップすると，それが悪性か良性かを判定します．

作成したアプリURL:  
https://flask-skincancerdetection-app.onrender.com

コードのURL(Github):  
https://github.com/HSAKAIsan/flask-SkinCancerDetection-app


# 4.開発環境
* Dynabook SZ73/PB (CPU:Intel(R) Core(TM) i7-8550U  RAM:16.0 GB)
* Windows 10 Pro (バージョン 22H2)
* Google Colaborabory（T4 GPU）
* Python 3.9.6


# 5.使用したライブラリ
* TensorFlow 2.15.0
* Numpy 1.25.2
* Matplotlib 3.7.1
* Flask 2.0.1


# 6.学習用およびテスト用画像の準備
今回の作成において，Kaggleで公開されている「[Skin Cancer: Malignant vs. Benign](https://www.kaggle.com/datasets/fanconic/skin-cancer-malignant-vs-benign)」のデータセットを使用しました．
このデータセットには，良性（benign）および悪性（malignant）の皮膚腫瘍の画像が含まれています．
以下は各クラスの画像数です．

* 学習用良性腫瘍画像 : 1440枚
* 学習用悪性腫瘍画像 : 1197枚
* テスト用良性腫瘍画像 : 360枚
* テスト用悪性腫瘍画像 : 300枚

これらの画像は，ダウンロードしたzipファイルを解凍後，Google Colaboratoryで使用するため, Googleドライブ上に保存しました．


# 7.悪性腫瘍の特徴
実際の悪性腫瘍には以下のような特徴があるとされています.
([大阪梅田形成外科粉瘤クリニック](https://www.umeda-keisei.jp/boil/malignancy/))
* ぼやけてはっきりしない
* 色の混在
* 大きさが6mm以上
* 表面が隆起
* 左右非対称

これらの特徴は，皮膚がんを判別する際の参考になります．

# 8.学習モデルの構築
Google Colaboratoryを使用し，モデルを構築します．
作成したモデルの重みは「model.h5」ファイルとして保存し，アプリで使用します．


### Googleドライブのマウント
Google ColaboratoryからGoogleドライブ上に保存した学習用・テスト用画像が使用できるように，Googleドライブをマウントします．
```python
from google.colab import drive # Googleドライブをマウントし、学習用・テスト用データへアクセス
drive.mount('/content/drive')
```

### 関連ライブラリのインストール
機械学習のフレームワークとしてTensorflowを使用します．
また，今回はVGG16モデルを使用し，転移学習を行いました．
```python
import os
import cv2
import numpy             as np
import matplotlib.pyplot as plt

from tensorflow.keras.utils              import to_categorical
from tensorflow.keras.layers             import Dense, Dropout, Flatten, Input
from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.models             import Model, Sequential
from tensorflow.keras                    import optimizers

from datetime                            import datetime
```


### 学習用およびテスト用画像のリストを取得
Googleドライブに保存した学習用・テスト用画像のフォルダ構成は下記のとおりです．
Kaggleからダウンロードした時点で既にtrain・testフォルダに分割されていました．
train/benign, train/malignant, test/benign, test/malignantの各フォルダ内の画像名から，画像リストを作成していきます．
作成されたリストは下記のとおりです．
* list_train_benign ：学習用良性腫瘍の画像リスト
* list_train_malignant ：学習用悪性腫瘍の画像リスト
* list_test_benign ： テスト用良性腫瘍の画像リスト
* list_test_malignant ：テスト用悪性腫瘍の画像リスト

![フォルダ構成.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3792372/c6050e1c-50ad-5b7e-2155-99beeaf34f8a.png)
```python
# 学習用ファイルパスの設定
path = '/content/drive/MyDrive/Colab Notebooks/Skin_Cancer_Detection_Data/train/'
path_train_benign    = path + 'benign/'    # 良性腫瘍
path_train_malignant = path + 'malignant/' # 悪性腫瘍
# 画像をリストで取得
list_train_benign    = os.listdir(path_train_benign)    # 学習用良性腫瘍の画像リスト
list_train_malignant = os.listdir(path_train_malignant) # 学習用悪性腫瘍の画像リスト

# テスト用ファイルパスの設定
path = '/content/drive/MyDrive/Colab Notebooks/Skin_Cancer_Detection_Data/test/'
path_test_benign    = path + 'benign/'    # 良性腫瘍
path_test_malignant = path + 'malignant/' # 悪性腫瘍
# 画像をリストで取得
list_test_benign    = os.listdir(path_test_benign)    # テスト用良性腫瘍の画像リスト
list_test_malignant = os.listdir(path_test_malignant) # テスト用悪性腫瘍の画像リスト
```


### 画像のリサイズ
機械学習をしやすいように，すべての画像を50x50の配列に変換します．
またその際，BGR色空間からRGB色空間に変換します．
下記変数にリサイズ後のリストが格納されています．
* img_train_benign : リサイズ後の学習用良性腫瘍の画像リスト
* img_train_malignant : リサイズ後の学習用悪性腫瘍の画像リスト
* img_test_benign : リサイズ後のテスト用良性腫瘍の画像リスト
* img_test_malignant : リサイズ後のテスト用悪性腫瘍の画像リスト
```python
img_train_benign    = []
img_train_malignant = []
img_test_benign     = []
img_test_malignant  = []

target_size = (50, 50)

# 画像のリサイズおよびBGRからRGBに変換するための関数
def resize_img(base_path, image_list, target_size):
    processed_images = []
    for img_name in image_list:
        img     = cv2.imread(base_path + img_name) # 画像を読み込み
        b, g, r = cv2.split(img)                   # 色チャネルを分割
        img     = cv2.merge([r, g, b])             # RGBに再マージ
        img     = cv2.resize(img, target_size)     # 画像をリサイズ
        processed_images.append(img)               # リサイズした画像をリストに追加
    return processed_images

img_train_benign    = resize_img(path_train_benign,    list_train_benign,    target_size)
img_train_malignant = resize_img(path_train_malignant, list_train_malignant, target_size)
img_test_benign     = resize_img(path_test_benign,     list_test_benign,     target_size)
img_test_malignant  = resize_img(path_test_malignant,  list_test_malignant,  target_size)
```


### 学習用・テスト用にデータを加工
下記変数を作成するために，学習用・テスト用のデータを加工します．
* X_train : 学習用画像データ(良性および悪性腫瘍混在)がランダムで格納された配列
* y_train : 学習用画像データの良性腫瘍か悪性腫瘍かを示すラベル
* X_test : テスト用画像データ(良性および悪性腫瘍混在)がランダムで格納された配列
* y_test : テスト用画像データの良性腫瘍か悪性腫瘍かを示すラベル
```python
# 学習用・テスト用のデータにおいて良性と悪性腫瘍の画像リストを結合して並べ替える  [0]:悪性腫瘍 [1]:良性腫瘍
X_train = np.array(img_train_benign + img_train_malignant)
y_train = np.array([0]*len(img_train_benign) + [1]*len(img_train_malignant))

X_test  = np.array(img_test_benign + img_test_malignant)
y_test  = np.array([0]*len(img_test_benign) + [1]*len(img_test_malignant))

# 画像の順番をシャッフル
rand_index = np.random.permutation(np.arange(len(X_train)))
X_train    = X_train[rand_index]
y_train    = y_train[rand_index]

rand_index = np.random.permutation(np.arange(len(X_test)))
X_test     = X_test[rand_index]
y_test     = y_test[rand_index]

# 正解ラベルのワンホットコーディング (良性：[1, 0]  悪性:[0, 1])
y_train = to_categorical(y_train)
y_test  = to_categorical(y_test)
```


### VGG16を使用した転移学習のためのモデル構築
事前学習済みのモデルVGG16を使用し，それに追加する形でモデルをカスタマイズしました．
最後にmodel.summary()において，モデルの構造を確認しています．
「モデル構造の出力結果」を見ると，"input_1(InputLayer)" ~ "block5_pool (MaxPooling2D)"までの19層(畳み込み層:16層, 全結合層:3層で構成)が，VGG16になります．
そして，最後の "sequential (Sequential)"が今回追加した層になります．
下記変数に作成されたモデルが格納されています．
* model : VGG16を使用し，転移学習したモデル
```python
# input_tensorの定義をして、vgg16のImageNetによる学習済みモデルを作成
# 入力画像のサイズを指定（50x50ピクセルのRGB画像）
input_tensor = Input(shape=(50,50,3))
# 事前学習済みのVGG16モデルを読み込み(全結合層), 重みはImageNetで事前学習されたものを使用
vgg16        = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor)

# モデルの特徴抽出部分に接続するためのトップモデルの定義
top_model = Sequential()                                   # 新しいSequentialモデルを作成
top_model.add(Flatten(input_shape=vgg16.output_shape[1:])) # Flatten層の追加．VGG16の出力をフラット化
top_model.add(Dense(256, activation='sigmoid'))            # 256ユニットの全結合層を追加
top_model.add(Dropout(0.5))                                # 過学習防止のため50%ドロップアウト．モデルの汎化性能を向上
top_model.add(Dense(2, activation='softmax'))              # 2クラス分類(良性・悪性)のための出力層を追加

# VGG16とtop_modelを連結
model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output))

# VGG16の畳み込み層の重みを保持
# 19層目までの重みを固定(VGG16は畳み込み層:16層, 全結合層:3層で構成)
for layer in model.layers[:19]:
    layer.trainable =  False

# モデル構造を確認
model.summary()
```
```text:モデル構造の出力結果
Model: "model"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_1 (InputLayer)        [(None, 50, 50, 3)]       0         
                                                                 
 block1_conv1 (Conv2D)       (None, 50, 50, 64)        1792      
                                                                 
 block1_conv2 (Conv2D)       (None, 50, 50, 64)        36928     
                                                                 
 block1_pool (MaxPooling2D)  (None, 25, 25, 64)        0         
                                                                 
 block2_conv1 (Conv2D)       (None, 25, 25, 128)       73856     
                                                                 
 block2_conv2 (Conv2D)       (None, 25, 25, 128)       147584    
                                                                 
 block2_pool (MaxPooling2D)  (None, 12, 12, 128)       0         
                                                                 
 block3_conv1 (Conv2D)       (None, 12, 12, 256)       295168    
                                                                 
 block3_conv2 (Conv2D)       (None, 12, 12, 256)       590080    
                                                                 
 block3_conv3 (Conv2D)       (None, 12, 12, 256)       590080    
                                                                 
 block3_pool (MaxPooling2D)  (None, 6, 6, 256)         0         
                                                                 
 block4_conv1 (Conv2D)       (None, 6, 6, 512)         1180160   
                                                                 
 block4_conv2 (Conv2D)       (None, 6, 6, 512)         2359808   
                                                                 
 block4_conv3 (Conv2D)       (None, 6, 6, 512)         2359808   
                                                                 
 block4_pool (MaxPooling2D)  (None, 3, 3, 512)         0         
                                                                 
 block5_conv1 (Conv2D)       (None, 3, 3, 512)         2359808   
                                                                 
 block5_conv2 (Conv2D)       (None, 3, 3, 512)         2359808   
                                                                 
 block5_conv3 (Conv2D)       (None, 3, 3, 512)         2359808   
                                                                 
 block5_pool (MaxPooling2D)  (None, 1, 1, 512)         0         
                                                                 
 sequential (Sequential)     (None, 2)                 131842    
                                                                 
=================================================================
Total params: 14846530 (56.64 MB)
Trainable params: 131842 (515.01 KB)
Non-trainable params: 14714688 (56.13 MB)
_________________________________________________________________

```


### モデルのコンパイル
モデルのコンパイルを実行し，学習プロセスを定義します．
```python
# モデルのコンパイル
model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
              metrics=['accuracy'])
```


###  モデルの学習と評価
学習用データを用いてモデルを学習させます．
目標精度などを考慮し，トライ＆エラーでバッチサイズやエポック数は決定していきます．
また，学習が完了したモデルの重みを"model{時間}.h5"として保存します．
このファイルは，作成したflaskのアプリにおいて，入力された画像が良性か悪性かを推測する際に使用しています．
下記グラフ「学習精度の可視化」を参照すると，学習用・テスト用データ双方において，80%以上の正解率であることがわかります．
```python
# モデルの学習
history = model.fit(X_train, y_train, validation_data=(X_test, y_test), batch_size=100, epochs=10)

# モデルの保存  -> ファイル名 model{時間}.h5
current_time  = datetime.now()                            # 現在時刻を取得
time_string   = current_time.strftime("%Y-%m-%dT%H:%M:%S")# 現在時刻を文字列に変換
path_model_h5 = f'/content/drive/MyDrive/Colab Notebooks/Skin_Cancer_Detection_Data/model{time_string}.h5'
model.save(path_model_h5)

# テストデータによるモデルの精度の評価
scores = model.evaluate(X_test, y_test, verbose=1)
print('Test loss:', scores[0])
print('Test accuracy:', scores[1])

# 学習精度の可視化
plt.plot(history.history["accuracy"],     label="accuracy",     ls="-", marker="o")  # 学習用データにおける正解率
plt.plot(history.history["val_accuracy"], label="val_accuracy", ls="-", marker="x")  # テスト用データにおける正解率
plt.ylabel("accuracy")
plt.xlabel("epoch")
plt.legend(loc="best")
plt.show()
```
```text:学習履歴の出力結果
Epoch 1/10
27/27 [==============================] - 139s 5s/step - loss: 0.6403 - accuracy: 0.6898 - val_loss: 0.4573 - val_accuracy: 0.7894
Epoch 2/10
27/27 [==============================] - 126s 5s/step - loss: 0.4723 - accuracy: 0.7725 - val_loss: 0.4874 - val_accuracy: 0.7682
Epoch 3/10
27/27 [==============================] - 99s 4s/step - loss: 0.4104 - accuracy: 0.8096 - val_loss: 0.3991 - val_accuracy: 0.8136
Epoch 4/10
27/27 [==============================] - 100s 4s/step - loss: 0.3916 - accuracy: 0.8161 - val_loss: 0.3898 - val_accuracy: 0.8212
Epoch 5/10
27/27 [==============================] - 116s 4s/step - loss: 0.3632 - accuracy: 0.8316 - val_loss: 0.3884 - val_accuracy: 0.8227
Epoch 6/10
27/27 [==============================] - 96s 4s/step - loss: 0.3673 - accuracy: 0.8282 - val_loss: 0.3867 - val_accuracy: 0.8273
Epoch 7/10
27/27 [==============================] - 100s 4s/step - loss: 0.3414 - accuracy: 0.8419 - val_loss: 0.3812 - val_accuracy: 0.8212
Epoch 8/10
27/27 [==============================] - 99s 4s/step - loss: 0.3289 - accuracy: 0.8498 - val_loss: 0.3779 - val_accuracy: 0.8258
Epoch 9/10
27/27 [==============================] - 102s 4s/step - loss: 0.3144 - accuracy: 0.8612 - val_loss: 0.3714 - val_accuracy: 0.8333
Epoch 10/10
27/27 [==============================] - 120s 4s/step - loss: 0.3069 - accuracy: 0.8635 - val_loss: 0.3792 - val_accuracy: 0.8212
/usr/local/lib/python3.10/dist-packages/keras/src/engine/training.py:3103: UserWarning: You are saving your model as an HDF5 file via `model.save()`. This file format is considered legacy. We recommend using instead the native Keras format, e.g. `model.save('my_model.keras')`.
  saving_api.save_model(
21/21 [==============================] - 19s 905ms/step - loss: 0.3792 - accuracy: 0.8212
Test loss: 0.3792479336261749
Test accuracy: 0.821212112903595
```
学習精度の可視化
accuracy : 学習用データにおける正解率
val_accuracy : テスト用データにおける正解率
![accuracy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3792372/189efdd9-dcc2-5f56-dee7-49a5fefc7e45.png)


### 画像の予測結果の表示
作成したモデルを使用し，腫瘍画像を読み込ませ，画像と共に予測結果を表示させます．
今回は20枚の画像を順次読み込ませます．
0 ～ 9番目の画像が良性腫瘍の画像であり，10 ～ 19番目の画像が悪性腫瘍の画像になります．
```python
# 画像を一枚受け取り、皮膚がんの有無を判定して返す関数
def pred_skin_cancer(img):
    # 50 x 50にリサイズ
    resized_img = cv2.resize(img, (50,50))
    # model.predictの結果が1であれば悪性と予測され，0であれば良性と予測される
    pred = np.argmax(model.predict(np.array([resized_img])))
    if pred == 1:
      return "malignant"
    else:
      return "benign"

# pred_skin_cancer関数に画像を渡して画像と予測結果を表示
plot_num = 10
for i in range(plot_num*2):
    if i < plot_num:
      path = path_test_benign    + list_test_benign[i]
    else:
      path = path_test_malignant + list_test_malignant[i-plot_num]
      
    img   = cv2.imread(path)
    b,g,r = cv2.split(img)
    img   = cv2.merge([r,g,b])
    plt.imshow(img)
    plt.show()
    print(str(i)+': '+pred_skin_cancer(img))
    print()
    print()
```
出力例
![出力イメージ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3792372/d74e755d-2509-0e1a-4352-995b154a0652.png)


# 9. Grad-CAMの実装
Grad-CAMを使用し，作成したモデルによる注目領域の可視化を行いました．
モデルは「8.学習モデルの構築」で作成したmodel{時間}.h5ファイルを使用し，実行します．
このセクションは「8.学習モデルの構築」とは別々に作成したため，一部重複するコードがありますが，ご容赦ください．

### Grad-CAMとは
Grad-CAMは機械学習が特定の領域に注目して予測を行う際に，その領域の予測がどの程度寄与しているか可視化する手法です．
畳み込みニューラルネットワーク(CNN)の「最後の畳み込み層」により抽出された特徴量に着目し計算し導出します．


### Googleドライブをマウント
保存されている"model{時間}.h5"ファイルにアクセスするため, Googleドライブをマウントします．
```python
from google.colab import drive # Googleドライブをマウントし、model.h5ファイルへアクセスできるようにする
drive.mount('/content/drive')
```


### 関連ライブラリのインストール
```python
import os
import cv2
import numpy             as np
import matplotlib.pyplot as plt
import tensorflow        as tf

from tensorflow.keras.models             import load_model
from tensorflow.keras                    import backend          as K
from tensorflow.keras.applications.vgg16 import preprocess_input
```


### モデルのロード
「8.学習モデルの構築」で作成した"model{時間}.h5"ファイルを読み込みます．
必要に応じてmodel.summary()を実行し，モデルの構造を確認します．
Grad-CAMでは，最後の畳み込み層の名前が必要になりますので，ここで確認しておくとよいです．
```python
# 作成した学習済みのモデルをロードする.
path_model_h5 = f'/content/drive/MyDrive/Colab Notebooks/Skin_Cancer_Detection_Data/model2024-05-16T08:32:37.h5'
model         = load_model(path_model_h5)
#model.summary()
```

### Grad-CAM関数
入力モデルと画像に対してGrad-CAMを計算します．
Grad-CAMは，特定のクラス（cls）に対する予測の信頼度に基き, 指定された畳み込み層（今回は最後の畳み込み層）の重要度を可視化します.
Grad-CAMの計算後はGrad-CAMマップを返します．
```python
# Grad-CAM関数
# 指定された入力モデル（input_model）と画像（image）に対してGrad-CAMを計算します
# Grad-CAMは，特定のクラス（cls）に対する予測の信頼度に基き, 指定された畳み込み層（layer_name）の重要度を可視化します.
# 入力画像に対してGrad-CAMマップを返します．
def grad_cam(input_model, image, cls, layer_name):
    grad_model = tf.keras.models.Model(
        [input_model.inputs],
        [input_model.get_layer(layer_name).output, input_model.output]
    )

    with tf.GradientTape() as tape:
        conv_outputs, predictions = grad_model(image)
        loss = predictions[:, cls]

    grads  = tape.gradient(loss, conv_outputs)[0]
    output = conv_outputs[0]

    weights = tf.reduce_mean(grads, axis=(0, 1)).numpy()
    cam     = np.dot(output.numpy(), weights)

    cam = cv2.resize(cam, (image.shape[1], image.shape[2]), cv2.INTER_LINEAR)
    cam = np.maximum(cam, 0)
    cam = cam / cam.max()
    return cam
```

### 腫瘍が良性か悪性かを予測する関数
腫瘍の画像を渡すと，モデルに基づきそれが良性か悪性かを予測します．
```python
# 画像を一枚受け取り、皮膚がんの有無を判定して返す関数
def pred_skin_cancer(img):
    # 50 x 50にリサイズ
    resized_img = cv2.resize(img, (50,50))
    # model.predictの結果が1であれば悪性と予測され，0であれば良性と予測される
    pred        = np.argmax(model.predict(np.array([resized_img])))
    if pred == 1:
      return "malignant"
    else:
      return "benign"
```

### 画像データをGrad-CAMで使用するための前処理
腫瘍画像をGrad-CAM関数で使用できる形に変換します．
画像情報にバッチ次元を追加して返します．
```python
# 画像データをGrad-CAMで使用するための前処理
def preprocess_image(img):
    # 画像をモデルが期待するサイズにリサイズ
    resized_image      = cv2.resize(img, (50, 50))
    # 前処理を適用
    preprocessed_image = preprocess_input(resized_image)
    # バッチ次元を追加
    preprocessed_image = np.expand_dims(preprocessed_image, axis=0)
    return preprocessed_image
```


### テスト用画像のリストを取得
Grad-CAMで可視化するファイルを指定する際に使用します．
```python
# testファイルパスの設定
path = '/content/drive/MyDrive/Colab Notebooks/Skin_Cancer_Detection_Data/test/'
path_test_benign    = path + 'benign/'    # 良性腫瘍
path_test_malignant = path + 'malignant/' # 悪性腫瘍
# 画像をリストで取得
list_test_benign    = os.listdir(path_test_benign)    # テスト用良性腫瘍の画像リスト
list_test_malignant = os.listdir(path_test_malignant) # テスト用悪性腫瘍の画像リスト
```


### Grad-CAMによる可視化
作成したモデルを使用し，腫瘍画像を読み込ませ，画像と共にGra-CAMの画像および予測結果を表示させます．
「8.学習モデルの構築」の時と同様，20枚の画像を順次読み込ませます．
0 ～ 9番目の画像が良性腫瘍の画像であり，10 ～ 19番目の画像が悪性腫瘍の画像になります．
Grad-CAMで使用する最後の畳み込み層の名前は'block5_conv3'です．

「出力結果 例1, 2」を見るとわかるとおり，Grad-CAMはカラーマップを使用しています．
赤色の部分ほどモデルが注目している部分です．
また逆に，青色の部分ほどモデルが着目していない部分になります．
```python
# pred_skin_cancer関数に画像を渡して腫瘍有無を予測
plot_num = 10
for i in range(plot_num*2):
    if i < plot_num:
      path = path_test_benign    + list_test_benign[i]
    else:
      path = path_test_malignant + list_test_malignant[i-plot_num]
      
    img   = cv2.imread(path)
    b,g,r = cv2.split(img)
    img   = cv2.merge([r,g,b])

    # オリジナル画像の表示
    plt.subplot(121)
    plt.title("Original Image")
    plt.imshow(img)

    # Grad-CAMの表示
    plt.subplot(122)
    plt.title("Grad-CAM")
    preprocessed_input = preprocess_image(img)                                # 画像の前処理
    predictions        = model.predict(preprocessed_input)                    # 予測結果の取得
    cls                = np.argmax(predictions)                               # 予測結果の中で最も高い確率を持つクラスのインデックスを取得
    layer_name         = 'block5_conv3'                                       # 最後の畳み込み層を指定 model.summary()で確認
    gradcam            = grad_cam(model, preprocessed_input, cls, layer_name) # Grad-CAMの計算を実行
    plt.imshow(preprocessed_input[0])                                         # 前処理された画像を表示
    plt.imshow(gradcam, cmap='jet', alpha=0.5)                                # Grad-CAMのヒートマップを表示し,オーバーレイする

    #良性/悪性の予測結果を表示
    plt.show()
    print(str(i)+': '+pred_skin_cancer(img))
    print()
    print()
```
出力結果 例1
![Grad-CAM-ex1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3792372/63e251f4-f8b5-bd7c-825a-9fd3439a33ff.png)

出力結果 例2
![Grad-CAM-ex2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3792372/816e4ab9-2307-01fc-525b-2acff7e75cc9.png)


# 10.まとめ
今回，腫瘍が悪性か良性かを判定するモデルの作成を行いました．
モデルの正解率は80%程度でした．
しかし，Grad-CAMを使用してモデルの注目領域を確認したところ，腫瘍でない部分にかなり影響を受けていることが分かりました．
この結果から、前処理の重要性を再確認しました．
今後も勉強を継続し，これらの判定精度が上がるよう努力していきます．


# 11.参考文献等
成果物作成にあたり，下記サイトを主に参考にさせていただきました．

[1]. 脳のMRI画像から腫瘍があるか判定するアプリを作ってみた
https://qiita.com/buonoatsushi/items/06b57aaac250aff2d828
[2]. 生殖補助医療におけるXAIの活用（画像分類編）
https://qiita.com/Tds_ST/items/7694ffd0375de126d212
[3]. よっしーの私的空間 Tensorflow-EfficientnetでGradCAMを実装してみた
https://book-read-yoshi.hatenablog.com/entry/2021/09/23/grad-cam/efficientnet/tensorflow
[4].【機械学習】grad-CAMをpythonで実装し、予測の根拠を視覚化する。
https://qiita.com/senbe/items/ee96acb8d3a49d6a98d2
[5]. Keras公式
https://keras.io/examples/vision/grad_cam/


# 12.謝辞
この度の最終成果物の作成にあたり, 様々なウェブサイトを参考にさせていただいたり，データを使用させていただきました.
また, チューターの皆様によるサポートも大変助けられました.
本成果物の作成に御協力いただいた皆様に心から感謝いたします.
大変ありがとうございました.