{"cells":[{"metadata":{},"cell_type":"markdown","source":"# =====概要=====\nこんな人が対象：機械学習初心者。モデル構築が良くわからない人。英語ばかりで困っている人。\n\nコンペの内容からモデル作成までを解説します。"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ライブラリをインポートします。\n\npandas：表計算\n\nnumpy：各種計算\n\nmatplotlib.pyplot：グラフ化\n\nこれらはほぼ確実に使うライブラリなのでとりあえずインポートしてもいいくらいです。"},{"metadata":{"trusted":true},"cell_type":"code","source":"train = pd.read_csv(\"../input/ranzcr-clip-catheter-line-classification/train.csv\")\nprint(train.shape)\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"pandasの.read_csvでCSVファイルを読み込みます。\n\n編集画面右端の”Data”というところからパスをコピーできるので（）に入れましょう。\n\nデータは以下の通り。\n\nStudyInstanceUID：学習や予測に使う画像のID\n\nETT - Abnormal ～ Swan Ganz...：今回予測するラベル（０か１かを予測）\n\nPatientID：患者のID。同じ患者の画像が何枚かある。"},{"metadata":{"trusted":true},"cell_type":"code","source":"import cv2\nstudyuid = train[\"StudyInstanceUID\"][0]\npath = \"../input/ranzcr-clip-catheter-line-classification/train\" + \"/\" + studyuid + \".jpg\"\nimage = cv2.imread(path)\nprint(image.shape)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"画像を読み込みますのでcv2をインポートしましょう。\n\n画像のIDを１つだけ取り出してパスを作ります。trainのフォルダに画像が入っています。\n\nこれをcv2の.imreadに入れると画像データとして吐き出します。縦2048横2500色が3チャネルの画像です。"},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.imshow(image)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"matplotlibの.imshowで画像を表示できます。レントゲン写真です。"},{"metadata":{"trusted":true},"cell_type":"code","source":"train.loc[train[\"StudyInstanceUID\"] == studyuid]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ここでもう一度学習データをみてみると、\"NGT - Normal\"に１が入っています。\n\n\"NGT\"という種類のカテーテルが\"Normal\"つまり正常な位置にある画像ということです。"},{"metadata":{"trusted":true},"cell_type":"code","source":"studyuid = train[\"StudyInstanceUID\"][3]\ntrain.loc[train[\"StudyInstanceUID\"] == studyuid]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"３枚目の画像では\"CVC - Abnormal\"に１が入っています。\n\nこれはつまり\"CVC\"がアカン位置にあるという意味です。"},{"metadata":{"trusted":true},"cell_type":"code","source":"path = \"../input/ranzcr-clip-catheter-line-classification/train\" + \"/\" + studyuid + \".jpg\"\nimage = cv2.imread(path)\nplt.imshow(image)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"さっきと同じように画像を読み込みました。アカン位置にあるみたいですがよくわかりません。"},{"metadata":{"trusted":true},"cell_type":"code","source":"annotations = pd.read_csv(\"../input/ranzcr-clip-catheter-line-classification/train_annotations.csv\")\nprint(annotations.shape)\nannotations.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ここで使えるのがもう１つのデータである\"train_annotation.csv\"です。\n\n画像のIDとラベル、そして画像上のxy座標データがあります。"},{"metadata":{"trusted":true},"cell_type":"code","source":"studyuid = annotations[\"StudyInstanceUID\"][0]\nannotations.loc[annotations[\"StudyInstanceUID\"] == studyuid]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"annotationの中で最初の画像IDを取り出しました。\n\n同じ画像を取り扱う行が２つあります。"},{"metadata":{"trusted":true},"cell_type":"code","source":"annotations.loc[annotations[\"StudyInstanceUID\"] == studyuid][\"data\"][0]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ここでxy座標を表すdataを見てみます。[x, y]の座標がいくつか入っていますが、これらは実は文字列です。"},{"metadata":{"trusted":true},"cell_type":"code","source":"import ast\npoints = annotations.loc[annotations[\"StudyInstanceUID\"] == studyuid][\"data\"][0]\npoints = ast.literal_eval(points)\npoints = np.array(points)\npoints","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"astというライブラリをインポートして文字列をリストに変換します。\n\nそのあとnp.array()でnumpy配列に変換しましょう。"},{"metadata":{"trusted":true},"cell_type":"code","source":"path = \"../input/ranzcr-clip-catheter-line-classification/train\" + \"/\" + studyuid + \".jpg\"\nimage = cv2.imread(path)\nplt.imshow(image)\nplt.scatter(points[:, 0], points[:, 1])\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"対象の画像はimreadとimshowで表示できます。\n\n.scatterでdataの座標を散布図としてプロットしましょう。[:, 0]は全行１列目を意味します。\n\nすると青点でカテーテルの位置が出ました。"},{"metadata":{"trusted":true},"cell_type":"code","source":"annotations.loc[annotations[\"StudyInstanceUID\"] == studyuid]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"この画像は２つの正常な位置にあるカテーテルを取り扱っているのでどちらもプロットしましょう。"},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_points(studyuid, num):\n    points = annotations.loc[annotations[\"StudyInstanceUID\"] == studyuid][\"data\"].values[num]\n    points = ast.literal_eval(points)\n    points = np.array(points)\n    return points\n\npath = \"../input/ranzcr-clip-catheter-line-classification/train\" + \"/\" + studyuid + \".jpg\"\nimage = cv2.imread(path)\nplt.imshow(image)\nfor i in range(annotations.loc[annotations[\"StudyInstanceUID\"] == studyuid].shape[0]):\n    points = get_points(studyuid, i)\n    plt.scatter(points[:, 0], points[:, 1])\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"座標を読み取る処理は関数にまとめました。やっていることはさっきと同じです。\n\n２つのカテーテルをプロットしました。\n\nどちらも\"CVC - Normal\"つまり\"CVC\"というカテーテルが正常な位置にあることを意味します。これが正常らしいです。"},{"metadata":{"trusted":true},"cell_type":"code","source":"annotations.loc[annotations[\"label\"] == \"NGT - Abnormal\"]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"じゃあアカン位置にあるカテーテルはどんなのやということで、\"Abnormal\"を見つけます。"},{"metadata":{"trusted":true},"cell_type":"code","source":"studyuid = annotations[\"StudyInstanceUID\"][1287]\nannotations.loc[annotations[\"StudyInstanceUID\"] == studyuid]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"1287行目のデータに\"Abnormal\"があるので取り出しました。"},{"metadata":{"trusted":true},"cell_type":"code","source":"path = \"../input/ranzcr-clip-catheter-line-classification/train\" + \"/\" + studyuid + \".jpg\"\nimage = cv2.imread(path)\nplt.imshow(image)\nfor i in range(annotations.loc[annotations[\"StudyInstanceUID\"] == studyuid].shape[0]):\n    points = get_points(studyuid, i)\n    label = annotations.loc[annotations[\"StudyInstanceUID\"] == studyuid][\"label\"].values[i]\n    plt.scatter(points[:, 0], points[:, 1], label = label)\nplt.legend()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"これまでと同じく画像と座標をプロットしました。\n\n\"Noamal\"と\"Abnormal\"があるのでラベルも読み取ります。\n\n青が\"Abnormal\"なのでアカン位置にあります。\n\n何がダメなのかわかりませんがそれはDiscussionなどで有識者が語ってくれます。"},{"metadata":{},"cell_type":"markdown","source":"## ○総括\n画像の中に正常な位置やボーダーライン、アカン位置にあるカテーテルが写っている\n\n今回はその画像からカテーテルの位置が正しいのか予測する"},{"metadata":{},"cell_type":"markdown","source":"# =====データジェネレーター=====\nモデル作成の前にデータを正しく取り出さなければいけません。\n\nこの点がテーブルデータと画像データで大きな違いです。"},{"metadata":{"trusted":true},"cell_type":"code","source":"train.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"まず確認ですが、\"StudyInstanceUID\"から画像を引っ張ってきて各種ラベルの０１を予測するのが目標です。"},{"metadata":{"trusted":true},"cell_type":"code","source":"DEBUG = True\nif DEBUG:\n    train = train.sample(n = train.shape[0] // 10).reset_index(drop = True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"データ数が多いと学習に時間がかかるので1/10に減らしました。\n\n実際に学習するときはDEBUG = Falseにしましょう。"},{"metadata":{"trusted":true},"cell_type":"code","source":"train[\"path\"] = \"../input/ranzcr-clip-catheter-line-classification/train\" + \"/\" + train[\"StudyInstanceUID\"] + \".jpg\"\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"\"StudyInstanceUID\"から画像のパスを作りました。"},{"metadata":{"trusted":true},"cell_type":"code","source":"path = train[\"path\"].values[0]\nimage = cv2.imread(path)\nplt.imshow(image)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"例えば１つ目のパスから読み込んだ画像は上図です。\n\nこれをモデルに与えて同じ行にあるラベルを正解データとして教えます。"},{"metadata":{"trusted":true},"cell_type":"code","source":"labels = [\n    'ETT - Abnormal', 'ETT - Borderline', 'ETT - Normal',\n    'NGT - Abnormal', 'NGT - Borderline', 'NGT - Incompletely Imaged', 'NGT - Normal',\n    'CVC - Abnormal', 'CVC - Borderline', 'CVC - Normal',\n    'Swan Ganz Catheter Present'\n]\ntrain[labels]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ラベルのリストは頻繁に使うので変数にしましょう。"},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.model_selection import train_test_split\ntrain, valid = train_test_split(train, test_size = 0.1)\nprint(train.shape, valid.shape)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"学習データと評価データに分けます。"},{"metadata":{"trusted":true},"cell_type":"code","source":"import tensorflow as tf\ntrain_ds = tf.data.Dataset.from_tensor_slices((train[\"path\"].values, train[labels].values))\nvalid_ds = tf.data.Dataset.from_tensor_slices((valid[\"path\"].values, valid[labels].values))\nnext(iter(train_ds))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ここからデータを取り出す作業に移ります。\n\n色々な方法がありますが今回はtensorflowのDataset.from_tensor_slicesを使います。\n\n画像のパスとそれに対する正解データを渡しましょう。\n\niterとnextで次のデータが出力されます。"},{"metadata":{"trusted":true},"cell_type":"code","source":"path = next(iter(train_ds))[0]\npath","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"出力されるのはさっき渡した画像のパスと正解データです。[０]がパスです。"},{"metadata":{"trusted":true},"cell_type":"code","source":"image = tf.io.read_file(path)\n#image","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"パスを画像データに変換しないといけないのでio.read_fileで指定したパスのデータを取得します。\n\nしかし出力されるのは謎の形式のデータです。"},{"metadata":{"trusted":true},"cell_type":"code","source":"image = tf.image.decode_jpeg(image, channels = 3)\nimage","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"これをdecode_jpegでjpeg画像のデータに変換しましょう。"},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.imshow(image)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"変換したデータを.imshowに入れると確かに画像として取り出せています。"},{"metadata":{"trusted":true},"cell_type":"code","source":"print(image.shape)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"しかし画像データのサイズはバラバラでモデルに入れるときに不都合が生じます。なので統一しましょう。"},{"metadata":{"trusted":true},"cell_type":"code","source":"image = tf.image.resize(image, [300, 300])\nprint(image.shape)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":".image.resizeで変換できます。今回は縦横300にしました。"},{"metadata":{"trusted":true},"cell_type":"code","source":"def preprocess(path, label):\n    image = tf.io.read_file(path)\n    image = tf.image.decode_jpeg(image, channels = 3)\n    image = tf.image.resize(image, [300, 300])\n    return image, label","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"ここまでの一連の処理を関数にしましょう。"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_ds = train_ds.map(preprocess)\nvalid_ds = valid_ds.map(preprocess)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":".mapに作った関数を渡すとその処理を内側でしてくれます。"},{"metadata":{"trusted":true},"cell_type":"code","source":"next(iter(train_ds))[0].shape","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"このように処理が済んだ状態で出力されます。"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_ds = train_ds.batch(32)\nvalid_ds = valid_ds.batch(32)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":".batchでミニバッチごとにデータを取り出せます。今回は32個ずつにしました。"},{"metadata":{"trusted":true},"cell_type":"code","source":"next(iter(train_ds))[0].shape","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"このように１回で32個のデータを取り出せています。"},{"metadata":{"trusted":true},"cell_type":"code","source":"AUTOTUNE = tf.data.experimental.AUTOTUNE\ntrain_ds = train_ds.prefetch(buffer_size = AUTOTUNE)\n#valid_ds = valid_ds.prefetch(buffer_size = AUTOTUNE)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":".prefetchをするとモデル学習に使えるようになります。\n\nAUTOTUNEは私自身よくわかっていません。おまじないのように書いています。\n\n評価データ(valid)の方には不要みたいです。\n\nここまでで画像とラベルを取り出すジェネレーターが作成できました。"},{"metadata":{},"cell_type":"markdown","source":"# =====モデル作成=====\nこれからモデルを作成しますが、既に学習済みで優秀なEfficientNetというモデルが存在するので、利用しましょう。"},{"metadata":{"trusted":true},"cell_type":"code","source":"from tensorflow.keras.applications import EfficientNetB0\nfrom tensorflow.keras.layers import Input, GlobalAveragePooling2D, Dense\nfrom tensorflow.keras.models import Model","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"tensorflow.keras.applicationsにEfficientNetがあります。\n\nB0～B7までの８種類が存在しますが今回はB0にしました。\n\nその他必要なのはデータを受け取るInputと３次元の画像データを１次元に変換するプーリング層、そして出力に使う全結合層です。\n\nModelは設計した層を実際のモデルに組み立てるために使います。"},{"metadata":{"trusted":true},"cell_type":"code","source":"effnet = EfficientNetB0(weights = \"../input/tfkerasefficientnetimagenetnotop/efficientnetb0_notop.h5\", include_top = False)\neffnet.trainable = False","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"EfficientNetB0を呼び出しました。\n\n学習済みモデルなので\"どんなデータで学習したモデルなのか\"を指定します。今回は\"imagenet\"というデータで学習済みのモデルです。\n\n本来はweightsをネットを通してこのノートブックにダウンロードするのですが、当コンペの性質上ネット接続はOFFにしないといけません。\n\nなのでDatasetにアップロードしてあるweightsをノートブックのData->inputに入れておき、そこから読み取るようにします。\n\ninclude_topは最終出力層も利用するか指定します。今回は11種類のラベルとして出力したい(カスタマイズしたい)のでFalseにしました。\n\n.trainableはこれから学習する際にEfficientNetの部分も学習させるかをしていします。今回はめんどくさいのでFalseです。"},{"metadata":{"trusted":true},"cell_type":"code","source":"def create_model(input_shape, output_shape):\n    inp = Input(input_shape)\n    x = effnet(inp)\n    x = GlobalAveragePooling2D()(x)\n    x = Dense(32, activation = \"relu\")(x)\n    out = Dense(output_shape, activation = \"sigmoid\")(x)\n    model = Model(inputs = inp, outputs = out)\n    model.compile(optimizer = \"Adam\", loss = \"binary_crossentropy\", metrics = [\"AUC\"])\n    return model","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"関数としてモデルを作成しました。\n\nまず最初は画像データを受け取るInputです。サイズは(300, 300, 3)になりますが関数の引数input_shapeを代用しました。\n\n次に先程定義したEfficientNetに入れます。この部分である程度精度良い学習が進みます。\n\nEfficientNetから出力される次元が３次元なのでPoolingで１次元に変換します。\n\n１次元データを全結合層(Dense)に入れて学習させます。EfficientNetは.trainable = Falseにしてますが、この層は別なので学習が進みます。\n\n最後にアウトプットしたい列数として出力します。この時の活性化関数はsigmoidにしましょう。確率分布(０～１)にスケーリングされます。\n\nModel(入力, 出力)と書いてこれまでに定義した流れをモデル化します。\n\n.compileは学習の最適化アルゴリズム(optimizer)と損失関数(loss)、評価指標(metrics)を定義します。"},{"metadata":{"trusted":true},"cell_type":"code","source":"model = create_model(input_shape = (300, 300, 3), output_shape = len(labels))\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"実際にモデルを作成します。入力サイズはresizeしたので全て(300, 300, 3)です。出力はラベルの総数。\n\n.summary()でモデルの中身を確認できます。\n\nEfficientNetからPoolingで次元が減ってDenseに２回通しています。"},{"metadata":{"trusted":true},"cell_type":"code","source":"history = model.fit(train_ds, validation_data = valid_ds, epochs = 5)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":".fitで学習できます。出力を変数で受け取っておくと学習過程を後で可視化できます。\n\n入れるデータは学習データ(train_ds)と評価データ(valid_ds)、学習回数(epochs)です。\n\n他にも条件を入れることがありますが長くなるので簡単なことだけにしました。\n\n学習にはGPUを使うことをおススメします。CPUだと気が狂うほど長くなります。\n\n無料で週に約40時間利用できるのでうまくマネジメントしましょう。"},{"metadata":{"trusted":true},"cell_type":"code","source":"model.evaluate(valid_ds)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":".evaluateでモデル性能を確認できます。\n\n出力されるのは[損失関数, 評価指標]です。\n\n今回はbinary_crossentropy(小さいほど良い)とauc(大きいほど良い)ですね。"},{"metadata":{},"cell_type":"markdown","source":"# =====提出====="},{"metadata":{"trusted":true},"cell_type":"code","source":"test = pd.read_csv(\"../input/ranzcr-clip-catheter-line-classification/sample_submission.csv\")\ntest.shape","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"サンプルの提出データを取り出しました。"},{"metadata":{"trusted":true},"cell_type":"code","source":"test[\"path\"] = \"../input/ranzcr-clip-catheter-line-classification/test\" + \"/\" + test[\"StudyInstanceUID\"] + \".jpg\"\ntest_ds = tf.data.Dataset.from_tensor_slices((test[\"path\"].values, test[labels].values))\ntest_ds = test_ds.map(preprocess)\ntest_ds = test_ds.batch(64)\n#test_ds = test_ds.prefetch(buffer_size = AUTOTUNE)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"パスを作って画像を出力するデータジェネレーターを作りましょう。\n\nこれまでに解説した方法とおなじです。"},{"metadata":{"trusted":true},"cell_type":"code","source":"pred = model.predict(test_ds, verbose = 1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":".predictで予測ができます。"},{"metadata":{"trusted":true},"cell_type":"code","source":"pred","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"pred.shape","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"予測値のサイズを見てみましょう。確かに11列です。"},{"metadata":{"trusted":true},"cell_type":"code","source":"submit = pd.DataFrame(pred, columns = labels)\nsubmit.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"提出用のデータにします。列名はlabelsがそのまま使えます。"},{"metadata":{"trusted":true},"cell_type":"code","source":"submit[\"StudyInstanceUID\"] = test[\"StudyInstanceUID\"]\nsubmit = pd.concat([submit.iloc[:, -1], submit.iloc[:, :-1]], axis = 1)\nsubmit.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"画像IDが１列目に必要なのでtestから引っ張ってきます。\n\nIDを１列目にしないとダメなのでilocでの切り取りとconcatで調整しました。"},{"metadata":{"trusted":true},"cell_type":"code","source":"submit.to_csv(\"submission.csv\", index = False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":".to_csvでCSVファイルとして取り出せます。\n\nindex = Falseにしないと余計な列ができるので注意しましょう。"}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}