├── .gitignore ├── README.md └── hands-on ├── 01_image_classification └── 01_image_classification.ipynb ├── 01ex_pruning └── 01ex_pruning.ipynb ├── 02_deploy_and_serving └── 02_deploy_and_serving.ipynb ├── 03_parameter_tuning ├── 03_parameter_tuning.ipynb ├── mfashion_estimator │ ├── __init__.py │ ├── model.py │ └── task.py ├── mfashion_keras │ ├── __init__.py │ ├── callback.py │ ├── model.py │ └── task.py └── setup.py ├── 04_transfer_learning └── 04_transfer_learning.ipynb └── 05_predict_structured_data └── 05_predict_structured_data.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | dataset 3 | images 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # machine-learning-notebooks 2 | 3 | AI研修のハンズオン用リポジトリです。 4 | スライド: https://speakerdeck.com/mixi_engineers 5 | 6 | ## 注意事項 7 | このリポジトリは`hands-on`ディレクトリの各チャプター内にある`*.ipynb`ファイルに対して、GCPのJupyterLab環境内で動作させることを想定しています。 8 | Vertex AIやGCS等を使用している部分は、研修以外の環境でやる場合、適宜変更する必要があるので注意してください。 9 | また、05のハンズオンは社外秘のデータセットのため、研修以外では動作しません。 10 | 11 | ## ブランチについて 12 | ブランチは、`master`と`solutions`に分かれています。 13 | ブランチを`solutions`に切り替えると、各ハンズオンの答えがあるので、どうしても困った時は参考にしてください。 14 | 15 | ## ハンズオン目次 16 | - 01_image_classification 17 | - 01ex_pruning 18 | - 02_deploy_and_serving 19 | - 03_parameter_tuning 20 | - 04_transfer_learning 21 | - 05_predict_structured_data (データセットが社外秘のため研修での利用限定) 22 | -------------------------------------------------------------------------------- /hands-on/01_image_classification/01_image_classification.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Fashion-MNIST を使ったClassification" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "fashion-MNIST データセットを使って画像のclassificationを行います" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "## dataset の確認" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "今回はオープンデータのfashion-MNISTを使うので、中身を確認します\n", 29 | "\n", 30 | "データの大元はここにあります。\n", 31 | "https://github.com/zalandoresearch/fashion-mnist" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "metadata": {}, 38 | "outputs": [], 39 | "source": [ 40 | "import tensorflow as tf\n", 41 | "from tensorflow.keras.datasets import fashion_mnist\n", 42 | "\n", 43 | "(X_train_orig, y_train_orig), (X_test_orig, y_test_orig) = fashion_mnist.load_data()\n", 44 | "\n", 45 | "## それぞれのデータ形を表示してみましょう\n", 46 | "## ヒント: データの型は'numpy.ndarray'です \n", 47 | "## https://numpy.org/doc/stable/reference/routines.array-manipulation.html でarrayの形を取得するメソッドを探してみてください。\n", 48 | "print(\"train feature shape\", X_train_orig.___)\n", 49 | "print(\"train label shape\", y_train_orig.___)\n", 50 | "print(\"test feature shape\", X_test_orig.___)\n", 51 | "print(\"test label shape\", y_test_orig.___)" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": [ 58 | "訓練データは60,000件、テストデータは10,000件で、画像のshapeは(28, 28)であることがわかります。\n", 59 | "また、縦横の2次元しかないため、色は白黒のグレースケール画像であることがわかります。\n", 60 | "\n", 61 | "featureとなる画像データを見てみると様々なファッションアイテムの画像が入っています。" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": null, 67 | "metadata": {}, 68 | "outputs": [], 69 | "source": [ 70 | "import matplotlib.pyplot as plt\n", 71 | "%matplotlib inline\n", 72 | "\n", 73 | "def show(n_cols, n_rows, train_orig):\n", 74 | " fig, axs = plt.subplots(n_rows, n_cols, figsize=(16,4))\n", 75 | " for ax, pixels in zip(axs.flat, train_orig):\n", 76 | " ax.imshow(pixels, cmap=\"gray\")\n", 77 | " ax.set_xticks([])\n", 78 | " ax.set_yticks([])\n", 79 | " plt.show()\n", 80 | "\n", 81 | "show(20, 5, X_train_orig)" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "labelの意味は https://github.com/zalandoresearch/fashion-mnist#labels ここにあります。\n", 89 | "\n", 90 | "次に、ラベル毎の画像データの数をグラフで表示してみましょう。" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "import numpy as np\n", 100 | "\n", 101 | "labels = [\n", 102 | " 'T-shirt/top',\n", 103 | " 'Trouser',\n", 104 | " 'Pullover',\n", 105 | " 'Dress',\n", 106 | " 'Coat',\n", 107 | " 'Sandal',\n", 108 | " 'Shirt',\n", 109 | " 'Sneaker',\n", 110 | " 'Bag',\n", 111 | " 'Ankle boot',\n", 112 | "]\n", 113 | "left = range(0, 10)\n", 114 | "height = np.zeros(10)\n", 115 | "for v in y_train_orig:\n", 116 | " height[v] += 1\n", 117 | " \n", 118 | "plt.xticks(rotation=45)\n", 119 | "plt.bar(left, height, tick_label=labels, align=\"center\")" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": {}, 125 | "source": [ 126 | "結果から、データはそれぞれのカテゴリごとに6,000件ずつ、均等にはいっているようです。\n", 127 | "\n", 128 | "これでデータについてはわかったので、モデルを作成するための準備に取り掛かります。" 129 | ] 130 | }, 131 | { 132 | "cell_type": "markdown", 133 | "metadata": {}, 134 | "source": [ 135 | "## data preprocessing" 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "metadata": {}, 141 | "source": [ 142 | "モデルを作成するにあたって、データの前処理を行います。\n", 143 | "ここでは3つの処理を行っています。\n", 144 | "- kerasのcnnで使用するメソッドであるConv2Dは入力のshapeとして(batch_size, rows, cols, channels)を取るため、データをexpandします。(channelsはカラーモードに相当)\n", 145 | "- データの正規化を行います。 (値の範囲を[0-255]から[0-1]にします。)\n", 146 | "- ラベルをone hot表現に変換します。('Trouser'が正解の場合、[0,1,0,0,0,0,0,0,0,0]になります。)" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": null, 152 | "metadata": {}, 153 | "outputs": [], 154 | "source": [ 155 | "## shapeを(batch_size, rows, cols)から(batch_size, rows, cols, channels)にexpandする。 (batch_sizeはtrainning時に指定するため、現時点では全データ数を指定)\n", 156 | "## ヒント: https://numpy.org/doc/stable/reference/routines.array-manipulation.html でexpandするメソッドを探してみてください\n", 157 | "X_train = np.___(___, -1)\n", 158 | "X_test = np.___(___, -1)\n", 159 | "\n", 160 | "print(\"X_train shape\", X_train.shape)\n", 161 | "print(\"X_test shape\", X_test.shape)\n", 162 | "\n", 163 | "## グレースケールの 0-255 の値を 正規化して 0-1 の浮動小数にする\n", 164 | "## ヒント: ここは関数などを使わずシンプルに計算式で0-1の範囲になればokです\n", 165 | "X_train = ___\n", 166 | "X_test = ___\n", 167 | "\n", 168 | "## one hot vectorにする\n", 169 | "## ヒント: https://keras.io/ja/utils/ を参考にしてみてください\n", 170 | "y_train = tf.keras.utils.___(___, _)\n", 171 | "y_test = tf.keras.utils.___(___, _)\n", 172 | "\n", 173 | "print(\"one hot label shape\", y_train.shape)" 174 | ] 175 | }, 176 | { 177 | "cell_type": "markdown", 178 | "metadata": {}, 179 | "source": [ 180 | "問題なく前処理が行えたら、モデルを構築していきます。" 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "metadata": {}, 186 | "source": [ 187 | "## model" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "kerasでmodelを作成する場合には2つの方法があります。 Sequential API を使う方法と、Funcional API を使う方法です。\n", 195 | "\n", 196 | "Sequential APIはシンプルで単純な構造のmodelを作る際に便利で、Functional APIは複数の入力やアウトプットをもったり、内部で分岐処理があるような複雑なモデルを作成する際に向いています。\n", 197 | "今回はSequential APIを用いてmodelを作ってみましょう\n", 198 | "\n", 199 | "スライドや https://www.tensorflow.org/api_docs/python/tf/keras/layers または  https://keras.io/examples/vision/mnist_convnet/ の`Build the model`などを参考にcnnを使ってmodelを組んでみましょう" 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": null, 205 | "metadata": {}, 206 | "outputs": [], 207 | "source": [ 208 | "from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dropout, Flatten, Dense\n", 209 | "\n", 210 | "def cnn():\n", 211 | " ## 資料を参考にモデルを組んでみましょう。\n", 212 | " return model" 213 | ] 214 | }, 215 | { 216 | "cell_type": "markdown", 217 | "metadata": {}, 218 | "source": [ 219 | "モデルができたら、いよいよモデルの学習に取り掛かっていきます。" 220 | ] 221 | }, 222 | { 223 | "cell_type": "markdown", 224 | "metadata": {}, 225 | "source": [ 226 | "## training: 訓練\n", 227 | "\n", 228 | "では、モデルを学習させて、ついでにモデルの中身を見てみましょう" 229 | ] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "execution_count": null, 234 | "metadata": {}, 235 | "outputs": [], 236 | "source": [ 237 | "%rm -rf ./logs\n", 238 | "\n", 239 | "model = cnn()\n", 240 | "tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=\"./logs\")\n", 241 | "\n", 242 | "## モデルをコンパイルする。\n", 243 | "## ヒント: https://keras.io/ja/models/model/ や資料を参考にしてみてください\n", 244 | "model.___(optimizer=___, loss=___, metrics=[tf.keras.metrics.CategoricalAccuracy()])\n", 245 | "## 学習を開始する。\n", 246 | "## バッチサイズを32,エポック数を5, 10%を検証に使うようにしてください\n", 247 | "## ヒント: https://keras.io/ja/models/model/ を参考にしてみてください\n", 248 | "model.___(x=___, y=___, ___, ___, ___, callbacks=[tensorboard_callback])" 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": null, 254 | "metadata": {}, 255 | "outputs": [], 256 | "source": [ 257 | "## modelの全体像を確認する。\n", 258 | "## ヒント 資料を参考にしてみてください\n", 259 | "model.___()" 260 | ] 261 | }, 262 | { 263 | "cell_type": "markdown", 264 | "metadata": {}, 265 | "source": [ 266 | "訓練の経過や中身はどうだったでしょうか? 意図した通りのモデルが組まれており、学習も問題なく進んでいっていたでしょうか?" 267 | ] 268 | }, 269 | { 270 | "cell_type": "markdown", 271 | "metadata": {}, 272 | "source": [ 273 | "## test: 検証\n", 274 | "\n", 275 | "訓練が終わったので、訓練データには存在しないデータで検証をしていきます" 276 | ] 277 | }, 278 | { 279 | "cell_type": "code", 280 | "execution_count": null, 281 | "metadata": {}, 282 | "outputs": [], 283 | "source": [ 284 | "## modelの検証を行う。\n", 285 | "## ヒント https://keras.io/ja/models/model/ を参考にしてみてください\n", 286 | "model.___(x=___, y=___)" 287 | ] 288 | }, 289 | { 290 | "cell_type": "markdown", 291 | "metadata": {}, 292 | "source": [ 293 | "表示されている情報はテスト時のlossとcategorical_accuracyです。訓練の結果とどの程度違いがあったでしょうか? overfitはしていないでしょうか?" 294 | ] 295 | }, 296 | { 297 | "cell_type": "markdown", 298 | "metadata": {}, 299 | "source": [ 300 | "## tensorboardでの可視化\n", 301 | "また、tensorboardを使うことによって訓練結果の可視化をしてみましょう" 302 | ] 303 | }, 304 | { 305 | "cell_type": "code", 306 | "execution_count": null, 307 | "metadata": {}, 308 | "outputs": [], 309 | "source": [ 310 | "%load_ext tensorboard" 311 | ] 312 | }, 313 | { 314 | "cell_type": "code", 315 | "execution_count": null, 316 | "metadata": {}, 317 | "outputs": [], 318 | "source": [ 319 | "%tensorboard --logdir logs" 320 | ] 321 | }, 322 | { 323 | "cell_type": "markdown", 324 | "metadata": {}, 325 | "source": [ 326 | "学習推移がtensorboardで表示されたでしょうか。" 327 | ] 328 | }, 329 | { 330 | "cell_type": "markdown", 331 | "metadata": {}, 332 | "source": [ 333 | "## prediction: 推論\n", 334 | "\n", 335 | "では出来上がったmodelにリクエストを投げて実際に処理を行ってみましょう" 336 | ] 337 | }, 338 | { 339 | "cell_type": "code", 340 | "execution_count": null, 341 | "metadata": {}, 342 | "outputs": [], 343 | "source": [ 344 | "## modelから予測結果を受け取る。\n", 345 | "## ヒント https://keras.io/ja/models/model/ で予測を受け取るメソッドを探してみてください。\n", 346 | "predictions = model.___(x=X_test[10:20])\n", 347 | "\n", 348 | "for i, p in enumerate(predictions):\n", 349 | " print(i, labels[np.argmax(p)], \"{}%\".format(p[np.argmax(p)]*100))\n", 350 | "show(10, 1, X_test_orig[10:20])" 351 | ] 352 | }, 353 | { 354 | "cell_type": "markdown", 355 | "metadata": {}, 356 | "source": [ 357 | "## 保存\n", 358 | "\n", 359 | "出来上がったモデルは現時点では、notebookのメモリ上にしかありません。これを保存します。 \n", 360 | "save_formatはtensorflow2.0以降のデフォルトの保存形式になっているsaved_model形式で保存を行うための指定です。" 361 | ] 362 | }, 363 | { 364 | "cell_type": "code", 365 | "execution_count": null, 366 | "metadata": {}, 367 | "outputs": [], 368 | "source": [ 369 | "USER = \"username\" ## 自分の名前\n", 370 | "BUCKET = \"mixi-ml-handson-2022\"\n", 371 | "VERSION = \"001\"\n", 372 | "\n", 373 | "## modelを保存する。\n", 374 | "## ヒント https://keras.io/api/models/ からsaveに関する記載を探してみてください\n", 375 | "model.___(\"gs://{}/{}/{}\".format(BUCKET, USER, VERSION), save_format=\"tf\")" 376 | ] 377 | }, 378 | { 379 | "cell_type": "markdown", 380 | "metadata": {}, 381 | "source": [ 382 | "# 追加課題" 383 | ] 384 | }, 385 | { 386 | "cell_type": "markdown", 387 | "metadata": {}, 388 | "source": [ 389 | "+ modelの構成を変更して、どのくらい性能が変わるか確認してみよう\n", 390 | " + Dropoutの有無、正規化の有無、CNNからDNNにした場合\n", 391 | "+ 時間に余裕がある場合は 01ex_pruningに進もう" 392 | ] 393 | } 394 | ], 395 | "metadata": { 396 | "environment": { 397 | "kernel": "python3", 398 | "name": "tf2-gpu.2-8.m91", 399 | "type": "gcloud", 400 | "uri": "gcr.io/deeplearning-platform-release/tf2-gpu.2-8:m91" 401 | }, 402 | "kernelspec": { 403 | "display_name": "Python 3", 404 | "language": "python", 405 | "name": "python3" 406 | }, 407 | "language_info": { 408 | "codemirror_mode": { 409 | "name": "ipython", 410 | "version": 3 411 | }, 412 | "file_extension": ".py", 413 | "mimetype": "text/x-python", 414 | "name": "python", 415 | "nbconvert_exporter": "python", 416 | "pygments_lexer": "ipython3", 417 | "version": "3.7.12" 418 | } 419 | }, 420 | "nbformat": 4, 421 | "nbformat_minor": 4 422 | } 423 | -------------------------------------------------------------------------------- /hands-on/01ex_pruning/01ex_pruning.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "minus-beijing", 6 | "metadata": {}, 7 | "source": [ 8 | "# Pruning" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "responsible-horse", 14 | "metadata": {}, 15 | "source": [ 16 | "このチャプターでは、modelのpruningを行っていきます。" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "id": "proved-neighborhood", 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "%pip install -q tensorflow-model-optimization" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "id": "tracked-paint", 33 | "metadata": {}, 34 | "outputs": [], 35 | "source": [ 36 | "import tempfile\n", 37 | "import tensorflow as tf\n", 38 | "from tensorflow.keras.datasets import fashion_mnist\n", 39 | "import numpy as np\n", 40 | "import os\n", 41 | "import zipfile" 42 | ] 43 | }, 44 | { 45 | "cell_type": "markdown", 46 | "id": "another-empire", 47 | "metadata": {}, 48 | "source": [ 49 | "### データセットの準備\n", 50 | "評価で使用するため、再度Fashion-MNISTデータセットをロードして、\n", 51 | "前処理も行なっておきます。" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": null, 57 | "id": "authentic-hepatitis", 58 | "metadata": {}, 59 | "outputs": [], 60 | "source": [ 61 | "(X_train_orig, y_train_orig), (X_test_orig, y_test_orig) = fashion_mnist.load_data()\n", 62 | "\n", 63 | "## shapeを(batch_size, rows, cols, channels)にexpandする\n", 64 | "X_train = np.expand_dims(X_train_orig, -1)\n", 65 | "X_test = np.expand_dims(X_test_orig, -1)\n", 66 | "\n", 67 | "print(\"X_train shape\", X_train.shape)\n", 68 | "print(\"X_test shape\", X_test.shape)\n", 69 | "\n", 70 | "## グレースケールの 0-255 の値を 正規化して 0-1 の浮動小数にする\n", 71 | "X_train = X_train / 255.0\n", 72 | "X_test = X_test / 255.0\n", 73 | "\n", 74 | "## one hot vectorにする\n", 75 | "y_train = tf.keras.utils.to_categorical(y_train_orig, 10)\n", 76 | "y_test = tf.keras.utils.to_categorical(y_test_orig, 10)\n", 77 | "\n", 78 | "print(\"one hot label shape\", y_train.shape)" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "id": "diagnostic-layer", 84 | "metadata": {}, 85 | "source": [ 86 | "### モデルのロード\n", 87 | "01で保存したFashion-MNISTモデルをロードします" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": null, 93 | "id": "usual-renaissance", 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "USER = \"username\" # 自分の名前\n", 98 | "BUCKET = \"mixi-ml-handson-2022\"\n", 99 | "VERSION = \"001\"\n", 100 | "\n", 101 | "base_model = tf.keras.models.load_model(\"gs://{}/{}/{}\".format(BUCKET, USER, VERSION))\n", 102 | "\n", 103 | "# ベースモデルを一時保存しておく\n", 104 | "_, base_model_file = tempfile.mkstemp('.h5')\n", 105 | "tf.keras.models.save_model(base_model, base_model_file, include_optimizer=False)" 106 | ] 107 | }, 108 | { 109 | "cell_type": "markdown", 110 | "id": "proved-china", 111 | "metadata": {}, 112 | "source": [ 113 | "### ベースモデルの精度確認\n", 114 | "再度、ベースモデルの評価を確認してみます。" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "id": "comic-kazakhstan", 121 | "metadata": {}, 122 | "outputs": [], 123 | "source": [ 124 | "base_model.evaluate(X_test, y_test, batch_size=32)" 125 | ] 126 | }, 127 | { 128 | "cell_type": "markdown", 129 | "id": "shared-pocket", 130 | "metadata": {}, 131 | "source": [ 132 | "### 重みの確認\n", 133 | "\n", 134 | "pruningとは、重みが小さいエッジを取り去って、パラメータを削減する手法になります。 \n", 135 | "パラメータが少なくなれば、その分モデルのサイズは小さくなり、高速化されます。 \n", 136 | "しかし、今回のモデルの重みに削減する余地はあるでしょうか。\n", 137 | "\n", 138 | "実際に重みの値を確認してみましょう。" 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "id": "olympic-disease", 144 | "metadata": {}, 145 | "source": [ 146 | "まず、再度モデルの構成を確認します。" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": null, 152 | "id": "close-warehouse", 153 | "metadata": {}, 154 | "outputs": [], 155 | "source": [ 156 | "base_model.summary()" 157 | ] 158 | }, 159 | { 160 | "cell_type": "markdown", 161 | "id": "completed-receipt", 162 | "metadata": {}, 163 | "source": [ 164 | "この中のうち、`conv2d`と`dense`が層を構成しています。 \n", 165 | "これらの層の重みからヒストグラムを作成してみましょう。" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "id": "excellent-breach", 172 | "metadata": {}, 173 | "outputs": [], 174 | "source": [ 175 | "import matplotlib.pyplot as plt\n", 176 | "\n", 177 | "def draw_weights_histgram(model, layers_index, bins=1000):\n", 178 | " ## ___を埋めて指定したindexのweightsを渡せるようにしましょう\n", 179 | " weight_list = model.layers[___].weights[0].numpy().flatten()\n", 180 | " plt.hist(weight_list, bins=bins)\n" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": null, 186 | "id": "grateful-teddy", 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "## 引数 layers_indexの部分にconv2dまたはdense層のindexを入れて、それぞれの重みをplotしてみましょう\n", 191 | "## ヒント: モデルの構成を参考にしてみてください\n", 192 | "## weightsの総数が少ない場合は、binsの値を小さくしてplotしてみてください\n", 193 | "draw_weights_histgram(base_model, layers_index=___)" 194 | ] 195 | }, 196 | { 197 | "cell_type": "markdown", 198 | "id": "written-insight", 199 | "metadata": {}, 200 | "source": [ 201 | "だいたいどの層をplotしてみても、0.0付近に値が集中していたのではないでしょうか。 \n", 202 | "0.0付近のweightは、消去しても精度に大きな影響を与えないはずなので、このモデルにはpruningする余地が十分あるといえそうです。" 203 | ] 204 | }, 205 | { 206 | "cell_type": "markdown", 207 | "id": "similar-entrance", 208 | "metadata": {}, 209 | "source": [ 210 | "### pruningモデルを定義\n", 211 | "公式の[Pruning in Keras example](https://www.tensorflow.org/model_optimization/guide/pruning/pruning_with_keras)を参考にpruningモデルを定義します。" 212 | ] 213 | }, 214 | { 215 | "cell_type": "code", 216 | "execution_count": null, 217 | "id": "military-evening", 218 | "metadata": {}, 219 | "outputs": [], 220 | "source": [ 221 | "import tensorflow_model_optimization as tfmot\n", 222 | "\n", 223 | "def compute_necessary_steps(batch_size, epochs):\n", 224 | " return np.ceil(X_train.shape[0] / batch_size).astype(np.int32) * epochs\n", 225 | "\n", 226 | "prune_low_magnitude = tfmot.sparsity.keras.prune_low_magnitude\n", 227 | "bigin_step = 0\n", 228 | "end_step = compute_necessary_steps(batch_size=32, epochs=5)\n", 229 | "\n", 230 | "## Pruning in Keras exampleを参考に'pruning_shcedule'を定義してみましょう\n", 231 | "## 最初に10%をpruning、最終的には70%をpruningする様にスケジューリングしてみてください\n", 232 | "pruning_params = {\n", 233 | "\n", 234 | "}\n", 235 | "\n", 236 | "model_for_pruning = prune_low_magnitude(base_model, **pruning_params)" 237 | ] 238 | }, 239 | { 240 | "cell_type": "code", 241 | "execution_count": null, 242 | "id": "prompt-blanket", 243 | "metadata": {}, 244 | "outputs": [], 245 | "source": [ 246 | "model_for_pruning.compile(\n", 247 | " optimizer='adam',\n", 248 | " loss=\"categorical_crossentropy\", \n", 249 | " metrics=[tf.keras.metrics.CategoricalAccuracy()]\n", 250 | ")" 251 | ] 252 | }, 253 | { 254 | "cell_type": "markdown", 255 | "id": "satellite-denial", 256 | "metadata": {}, 257 | "source": [ 258 | "### 学習\n", 259 | "pruningモデルが定義できたので、再学習させます。" 260 | ] 261 | }, 262 | { 263 | "cell_type": "code", 264 | "execution_count": null, 265 | "id": "fifty-organic", 266 | "metadata": {}, 267 | "outputs": [], 268 | "source": [ 269 | "%rm -rf ./pruning_logs\n", 270 | "\n", 271 | "callbacks = [\n", 272 | " tfmot.sparsity.keras.UpdatePruningStep(),\n", 273 | " tfmot.sparsity.keras.PruningSummaries(log_dir='pruning_logs'),\n", 274 | "]\n", 275 | "model_for_pruning.fit(X_train, y_train, batch_size=32, epochs=5, validation_split=0.1, callbacks=callbacks)" 276 | ] 277 | }, 278 | { 279 | "cell_type": "markdown", 280 | "id": "comfortable-median", 281 | "metadata": {}, 282 | "source": [ 283 | "### 評価\n", 284 | "学習が終わったら、これまでと同じように評価してみましょう。" 285 | ] 286 | }, 287 | { 288 | "cell_type": "code", 289 | "execution_count": null, 290 | "id": "derived-fifty", 291 | "metadata": {}, 292 | "outputs": [], 293 | "source": [ 294 | "model_for_pruning.evaluate(X_test, y_test, batch_size=32)" 295 | ] 296 | }, 297 | { 298 | "cell_type": "markdown", 299 | "id": "elder-steel", 300 | "metadata": {}, 301 | "source": [ 302 | "モデルの精度はベースモデルと比較してどうなっているでしょうか。 \n", 303 | "ほとんど変わってなければ、精度に影響を与えずにpruningされていることになります。" 304 | ] 305 | }, 306 | { 307 | "cell_type": "markdown", 308 | "id": "bacterial-dispatch", 309 | "metadata": {}, 310 | "source": [ 311 | "### 可視化\n", 312 | "01と同じように、学習結果をtensorboardで可視化してみます。" 313 | ] 314 | }, 315 | { 316 | "cell_type": "code", 317 | "execution_count": null, 318 | "id": "eligible-tomorrow", 319 | "metadata": {}, 320 | "outputs": [], 321 | "source": [ 322 | "%load_ext tensorboard" 323 | ] 324 | }, 325 | { 326 | "cell_type": "code", 327 | "execution_count": null, 328 | "id": "proper-asbestos", 329 | "metadata": {}, 330 | "outputs": [], 331 | "source": [ 332 | "%tensorboard --logdir pruning_logs" 333 | ] 334 | }, 335 | { 336 | "cell_type": "markdown", 337 | "id": "musical-theater", 338 | "metadata": {}, 339 | "source": [ 340 | "学習の推移やshcedule通りにpruningされていったかなどを確認してみてください。" 341 | ] 342 | }, 343 | { 344 | "cell_type": "markdown", 345 | "id": "competitive-maple", 346 | "metadata": { 347 | "tags": [] 348 | }, 349 | "source": [ 350 | "### pruningモデルを圧縮\n", 351 | "pruningすることが出来たので、モデルの圧縮を行いましょう。\n", 352 | "\n", 353 | "[公式](https://www.tensorflow.org/model_optimization/guide/pruning/pruning_with_keras#create_3x_smaller_models_from_pruning)によると、圧縮を確認するには`tfmot.sparsity.keras.strip_pruning`と標準の圧縮アルゴリズムの適用(gzipなど)の両方が必要とのことなので、\n", 354 | "その対応をしていきます。" 355 | ] 356 | }, 357 | { 358 | "cell_type": "code", 359 | "execution_count": null, 360 | "id": "variable-cheese", 361 | "metadata": {}, 362 | "outputs": [], 363 | "source": [ 364 | "## 公式を参考に___を埋めてpruningしたmodelにstrip_pruningを適応しましょう\n", 365 | "model_for_export = ___\n", 366 | "\n", 367 | "# pruningしたモデルを一時保存\n", 368 | "_, pruned_model_file = tempfile.mkstemp('.h5')\n", 369 | "tf.keras.models.save_model(model_for_export, pruned_model_file, include_optimizer=False)\n" 370 | ] 371 | }, 372 | { 373 | "cell_type": "code", 374 | "execution_count": null, 375 | "id": "loaded-cambridge", 376 | "metadata": {}, 377 | "outputs": [], 378 | "source": [ 379 | "# gzipを適応した後のsizeをkbで返す関数\n", 380 | "def get_gzipped_model_size_kb(file):\n", 381 | " # Returns size of gzipped model, in bytes.\n", 382 | " _, zipped_file = tempfile.mkstemp('.zip')\n", 383 | " with zipfile.ZipFile(zipped_file, 'w', compression=zipfile.ZIP_DEFLATED) as f:\n", 384 | " f.write(file)\n", 385 | " return int(os.path.getsize(zipped_file) / 1024)" 386 | ] 387 | }, 388 | { 389 | "cell_type": "markdown", 390 | "id": "federal-assignment", 391 | "metadata": {}, 392 | "source": [ 393 | "準備ができたので、各モデルにおける圧縮の効果を確認してみましょう。" 394 | ] 395 | }, 396 | { 397 | "cell_type": "code", 398 | "execution_count": null, 399 | "id": "hispanic-neighborhood", 400 | "metadata": {}, 401 | "outputs": [], 402 | "source": [ 403 | "print(\"base model size : {} kb\".format(get_gzipped_model_size_kb(base_model_file)))\n", 404 | "print(\"pruned model size : {} kb\".format(get_gzipped_model_size_kb(pruned_model_file)))" 405 | ] 406 | }, 407 | { 408 | "cell_type": "markdown", 409 | "id": "infrared-chocolate", 410 | "metadata": {}, 411 | "source": [ 412 | "モデルが1/3ほどに圧縮されたことが確認できているでしょうか。" 413 | ] 414 | }, 415 | { 416 | "cell_type": "code", 417 | "execution_count": null, 418 | "id": "demographic-democrat", 419 | "metadata": {}, 420 | "outputs": [], 421 | "source": [] 422 | } 423 | ], 424 | "metadata": { 425 | "environment": { 426 | "kernel": "python3", 427 | "name": "tf2-gpu.2-8.m91", 428 | "type": "gcloud", 429 | "uri": "gcr.io/deeplearning-platform-release/tf2-gpu.2-8:m91" 430 | }, 431 | "kernelspec": { 432 | "display_name": "Python 3", 433 | "language": "python", 434 | "name": "python3" 435 | }, 436 | "language_info": { 437 | "codemirror_mode": { 438 | "name": "ipython", 439 | "version": 3 440 | }, 441 | "file_extension": ".py", 442 | "mimetype": "text/x-python", 443 | "name": "python", 444 | "nbconvert_exporter": "python", 445 | "pygments_lexer": "ipython3", 446 | "version": "3.7.12" 447 | } 448 | }, 449 | "nbformat": 4, 450 | "nbformat_minor": 5 451 | } 452 | -------------------------------------------------------------------------------- /hands-on/02_deploy_and_serving/02_deploy_and_serving.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 作成したモデルのdeployとserving\n", 8 | "\n", 9 | "01_image_classificationで作ったモデルを、Vertex AIを使ってAPIとして利用できるようにしていきます。\n", 10 | "\n", 11 | "Vertex AIでは、作成済みのモデルをuploadし、作成したendpointにそのモデルをdeployすることで、推論APIとして呼び出すことができるようになります。 \n", 12 | "その手順を確認していきましょう。" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": { 19 | "tags": [] 20 | }, 21 | "outputs": [], 22 | "source": [ 23 | "%%bash\n", 24 | "\n", 25 | "USER=<> # 自分の名前\n", 26 | "BUCKET=mixi-ml-handson-2022\n", 27 | "VERSION=001\n", 28 | "REGION=asia-northeast1\n", 29 | "\n", 30 | "gcloud ai models upload \\\n", 31 | " --region=${REGION} \\\n", 32 | " --display-name=mfashion-${USER} \\\n", 33 | " --container-image-uri=asia-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-8:latest \\\n", 34 | " --artifact-uri=gs://${BUCKET}/${USER}/${VERSION}\n", 35 | "\n", 36 | "gcloud ai models list \\\n", 37 | " --region=${REGION} \\\n", 38 | " --filter=mfashion-${USER}\n", 39 | " \n", 40 | "gcloud ai endpoints create \\\n", 41 | " --region=${REGION} \\\n", 42 | " --display-name=mfashion-${USER}" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": {}, 48 | "source": [ 49 | "実行が終わったら、出力された`MODEL_ID`と、endpointを作成した際に出力された`ENDPOINT_ID` `'・・・/asia-northeast1/endpoints/'`を使って、endpointにmodelをdeployします。" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "%%bash\n", 59 | "\n", 60 | "USER=<> # 自分の名前\n", 61 | "REGION=asia-northeast1\n", 62 | "MODEL_ID=<> # 前のセルで出力されたMODEL_ID\n", 63 | "ENDPOINT_ID=<> # 前のセルで出力されたENDPOINT_ID\n", 64 | "\n", 65 | "gcloud ai endpoints deploy-model ${ENDPOINT_ID} \\\n", 66 | " --region=${REGION} \\\n", 67 | " --model=${MODEL_ID} \\\n", 68 | " --display-name=mfashion-${USER} \\\n", 69 | " --traffic-split=0=100" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "上記コードの実行には数分の時間がかかります。\n", 77 | "\n", 78 | "deployが完了したら、このendpointにリクエストを投げてみましょう。" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "from google.cloud import aiplatform\n", 88 | "\n", 89 | "def predict_json(project, endpoint, instances): \n", 90 | " region = \"asia-northeast1\"\n", 91 | " aiplatform.init(project=project, location=region)\n", 92 | " endpoint = aiplatform.Endpoint(endpoint)\n", 93 | " response = endpoint.predict(instances=instances)\n", 94 | " return response.predictions" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": null, 100 | "metadata": {}, 101 | "outputs": [], 102 | "source": [ 103 | "import matplotlib.pyplot as plt\n", 104 | "%matplotlib inline\n", 105 | "\n", 106 | "def show(n_cols, n_rows, train_orig):\n", 107 | " fig, axs = plt.subplots(n_rows, n_cols, figsize=(16,4))\n", 108 | " for ax, pixels in zip(axs.flat, train_orig):\n", 109 | " ax.imshow(pixels, cmap=\"gray\")\n", 110 | " ax.set_xticks([])\n", 111 | " ax.set_yticks([])\n", 112 | " plt.show()" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": null, 118 | "metadata": {}, 119 | "outputs": [], 120 | "source": [ 121 | "from tensorflow.keras.datasets import fashion_mnist\n", 122 | "import numpy as np\n", 123 | "\n", 124 | "project_name = 'hr-mixi'\n", 125 | "endpoint_id = <> # 最初のセルで出力されたENDPOINT_ID\n", 126 | "labels = [\n", 127 | " 'T-shirt/top',\n", 128 | " 'Trouser',\n", 129 | " 'Pullover',\n", 130 | " 'Dress',\n", 131 | " 'Coat',\n", 132 | " 'Sandal',\n", 133 | " 'Shirt',\n", 134 | " 'Sneaker',\n", 135 | " 'Bag',\n", 136 | " 'Ankle boot',\n", 137 | "]\n", 138 | "\n", 139 | "(X_train_orig, y_train_orig), (X_test_orig, y_test_orig) = fashion_mnist.load_data()\n", 140 | "requests = X_test_orig[0:8]\n", 141 | "\n", 142 | "## requestsをresposeが返せる形に変換してください。\n", 143 | "## ヒント: 学習時と同じデータ形式にするために、01で行った前処理と同じことをする必要があります。\n", 144 | "requests = ___ # expandする\n", 145 | "requests = ___ # 正規化する\n", 146 | "requests = requests.tolist()\n", 147 | "\n", 148 | "predictions = predict_json(project_name, endpoint_id, ___)\n", 149 | "\n", 150 | "for i, p in enumerate(predictions):\n", 151 | " print(\"Label: '{}' \".format(labels[np.argmax(p)]), \"Pred: {}\".format(p))\n", 152 | "show(8, 1, X_test_orig[0:8])" 153 | ] 154 | } 155 | ], 156 | "metadata": { 157 | "environment": { 158 | "kernel": "python3", 159 | "name": "tf2-gpu.2-8.m91", 160 | "type": "gcloud", 161 | "uri": "gcr.io/deeplearning-platform-release/tf2-gpu.2-8:m91" 162 | }, 163 | "kernelspec": { 164 | "display_name": "Python 3", 165 | "language": "python", 166 | "name": "python3" 167 | }, 168 | "language_info": { 169 | "codemirror_mode": { 170 | "name": "ipython", 171 | "version": 3 172 | }, 173 | "file_extension": ".py", 174 | "mimetype": "text/x-python", 175 | "name": "python", 176 | "nbconvert_exporter": "python", 177 | "pygments_lexer": "ipython3", 178 | "version": "3.7.12" 179 | } 180 | }, 181 | "nbformat": 4, 182 | "nbformat_minor": 4 183 | } 184 | -------------------------------------------------------------------------------- /hands-on/03_parameter_tuning/03_parameter_tuning.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## ハイパーパラメータチューニング\n", 8 | "\n", 9 | "ハイパーパラメータチューニングは学習を何度も繰り返す必要があり、非常に時間がかかる作業になります。 \n", 10 | "仮に1回の学習ループが3日かかるとしたら、パラメータを数回変えて試してみるだけで非常に時間がかかってしまいます。\n", 11 | "\n", 12 | "今回はその作業をお金の力で解決してしまう方法を学習します。\n", 13 | "\n", 14 | "## コードのモジュール化\n", 15 | "\n", 16 | "まず、コードを何度も実行するためにはjupyter上で処理するのはあまり向かないため、scriptに落とし込みます。 \n", 17 | "コードのコアの部分を移動したコードが`mfashion_keras/model.py`にあります。 \n", 18 | "そして、jobのkickerとなるコードを`mfashion_keras/task.py`に記載してあります。 \n", 19 | "試しに実行してみましょう" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "# このハンズオンで必要なライブラリのインストール\n", 29 | "!pip install cloudml-hypertune" 30 | ] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "execution_count": null, 35 | "metadata": {}, 36 | "outputs": [], 37 | "source": [ 38 | "## このセルを実行する前に、model.pyのを埋めてください。\n", 39 | "!python3 -m mfashion_keras.task --output_dir=./output --model=cnn --batch_size=64 --batch_norm" 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "## gcloudコマンドを用いたVertex AIでのtraining実行\n", 47 | "\n", 48 | "下記のコマンドをgcloudのVertex AI経由で実行することで、Vertex AI内のリソースを使ってtrainingを実行することができます。 " 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "## 書き換える\n", 58 | "USER = \"<>\"\n", 59 | "\n", 60 | "## 必要に応じて書き換える\n", 61 | "MODEL_TYPE = \"cnn\"\n", 62 | "LEARNING_RATE = 0.01\n", 63 | "BATCH_SIZE = 64\n", 64 | "TRAIN_STEPS = 1000\n", 65 | "\n", 66 | "## 書き換えなくて良い (出力先を変えたい場合はOUTPUT_DIRを変更)\n", 67 | "BUCKET = \"mixi-ml-handson-2022\"\n", 68 | "REGION = \"asia-northeast1\"\n", 69 | "OUTPUT_DIR = \"gs://\" + BUCKET + \"/\" + USER + \"/mfashion/trained_\" + MODEL_TYPE" 70 | ] 71 | }, 72 | { 73 | "cell_type": "code", 74 | "execution_count": null, 75 | "metadata": {}, 76 | "outputs": [], 77 | "source": [ 78 | "%%bash -s \"$USER\" \"$BUCKET\" \"$REGION\" \"$MODEL_TYPE\" \"$LEARNING_RATE\" \"$BATCH_SIZE\" \"$TRAIN_STEPS\" \"$OUTPUT_DIR\"\n", 79 | "\n", 80 | "DATE=`date +%Y%m%d_%H%M%S`\n", 81 | "DISPLAY_NAME=mfashion_$5_$1_$DATE\n", 82 | "\n", 83 | "echo $8\n", 84 | "echo ${DISPLAY_NAME}\n", 85 | "\n", 86 | "gcloud ai custom-jobs create \\\n", 87 | " --region=$3 \\\n", 88 | " --display-name=${DISPLAY_NAME} \\\n", 89 | " --args=\"--output_dir\",$8,\"--train_steps\",$7,\"--model\",$4,\\\n", 90 | "\"--learning_rate\",$5,\"--batch_size\",$6 \\\n", 91 | " --worker-pool-spec=machine-type=n1-standard-4,replica-count=1,accelerator-type=NVIDIA_TESLA_T4,\\\n", 92 | "executor-image-uri=asia-docker.pkg.dev/vertex-ai/training/tf-gpu.2-8:latest,local-package-path=./mfashion_keras,python-module=task\n" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "metadata": {}, 98 | "source": [ 99 | "上記のコードの実行には数分の時間がかかります。" 100 | ] 101 | }, 102 | { 103 | "cell_type": "markdown", 104 | "metadata": {}, 105 | "source": [ 106 | "実行が確認できたら、gcpのコンソールから`vertex AI >> トレーニング >> CUSTOM JOBS`に遷移した後、 \n", 107 | "リージョンを`asia-northeast1`にして作ったjobが表示されているか確認してみてください。 \n", 108 | "(実行ログは該当のjobに遷移した後、`ログを表示`ボタンで確認できます。)" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [ 115 | "これでjobを作成し、自分のマシン以外のリソースを使って実行できました。 \n", 116 | "上記のコマンドはjupyter上で実行する必要もないため、もちろんコマンドラインから実行しても同様に実行が可能です。\n", 117 | "\n", 118 | "これで自分のマシンの計算リソースの制約にとらわれることなくjobが実行可能になりました。 \n", 119 | "パラメータを変えながら大量にjobを並列実行すれば最適なパラメータチューニングをすることが可能になります。" 120 | ] 121 | }, 122 | { 123 | "cell_type": "markdown", 124 | "metadata": {}, 125 | "source": [ 126 | "## Vertex AIでのハイパーパラメータチューニング\n", 127 | " \n", 128 | "各クラウドで似たような仕組みはありますが、今回はgcloudのパラメータチューニングを使用してチューニングします。" 129 | ] 130 | }, 131 | { 132 | "cell_type": "code", 133 | "execution_count": null, 134 | "metadata": {}, 135 | "outputs": [], 136 | "source": [ 137 | "## 必要に応じて書き換える\n", 138 | "MODEL_TYPE = \"cnn\"\n", 139 | "LEARNING_RATE = 0.01\n", 140 | "BATCH_SIZE = 64\n", 141 | "TRAIN_STEPS = 1000" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": null, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "from IPython.core.magic import register_line_cell_magic\n", 151 | "\n", 152 | "## '%%writefile'にglobal変数を読みこませるカスタムマジックコマンド'%%writetemplate'を定義\n", 153 | "@register_line_cell_magic\n", 154 | "def writetemplate(line, cell):\n", 155 | " with open(line, 'w') as f:\n", 156 | " f.write(cell.format(**globals())) " 157 | ] 158 | }, 159 | { 160 | "cell_type": "markdown", 161 | "metadata": {}, 162 | "source": [ 163 | "最適値を探すのに、`Manual`, `Grid Search`, `Random Search`, `Baysean Search`の4つの探索方法が用意されています。 \n", 164 | "[詳しくはここ](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.hyperparameterTuningJobs)や[ここ](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/StudySpec)を確認してください。\n", 165 | "\n", 166 | "以下はGrid Searchを用いた例です。 \n", 167 | "今回はlearning_rateを最初値0.001から最大値0.3までの間を探索してみます。" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": null, 173 | "metadata": {}, 174 | "outputs": [], 175 | "source": [ 176 | "%%writetemplate hyperparam.yaml\n", 177 | "studySpec:\n", 178 | " metrics:\n", 179 | " - metricId: ccentropy\n", 180 | " goal: MINIMIZE\n", 181 | " parameters:\n", 182 | " - parameterId: learning_rate\n", 183 | " discreteValueSpec:\n", 184 | " values: [0.001, 0.005, 0.01, 0.05, 0.1, 0.3]\n", 185 | " algorithm: GRID_SEARCH\n", 186 | " decayCurveStoppingSpec:\n", 187 | " useElapsedDuration: True\n", 188 | "trialJobSpec:\n", 189 | " workerPoolSpecs:\n", 190 | " - machineSpec:\n", 191 | " machineType: n1-standard-4\n", 192 | " replicaCount: 1\n", 193 | " pythonPackageSpec:\n", 194 | " executorImageUri: asia-docker.pkg.dev/vertex-ai/training/tf-cpu.2-8:latest\n", 195 | " packageUris: [gs://{BUCKET}/{USER}/mfashion/src/mfashion_keras-1.0.tar.gz]\n", 196 | " pythonModule: mfashion_keras.task\n", 197 | " args: [--output_dir, {OUTPUT_DIR}, --train_steps, \"{TRAIN_STEPS}\", --model, {MODEL_TYPE}, --batch_size, \"{BATCH_SIZE}\"]" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": null, 203 | "metadata": {}, 204 | "outputs": [], 205 | "source": [ 206 | "## 下のセルを実行する前に、callback.pyの<>を埋めてください" 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": null, 212 | "metadata": {}, 213 | "outputs": [], 214 | "source": [ 215 | "%%bash -s \"$USER\" \"$BUCKET\"\n", 216 | "\n", 217 | "python3 setup.py sdist --formats=gztar \n", 218 | "gsutil cp dist/mfashion_keras-1.0.tar.gz gs://$2/$1/mfashion/src/" 219 | ] 220 | }, 221 | { 222 | "cell_type": "code", 223 | "execution_count": null, 224 | "metadata": {}, 225 | "outputs": [], 226 | "source": [ 227 | "%%bash -s \"$USER\" \"$REGION\" \"$MODEL_TYPE\"\n", 228 | "\n", 229 | "DATE=`date +%Y%m%d_%H%M%S`\n", 230 | "DISPLAY_NAME=mfashion_$3_$1_$DATE\n", 231 | "\n", 232 | "gcloud ai hp-tuning-jobs create \\\n", 233 | " --region=$2 \\\n", 234 | " --config=hyperparam.yaml \\\n", 235 | " --display-name=${DISPLAY_NAME} \\\n", 236 | " --max-trial-count=6 \\\n", 237 | " --parallel-trial-count=6" 238 | ] 239 | }, 240 | { 241 | "cell_type": "markdown", 242 | "metadata": {}, 243 | "source": [ 244 | "実行が確認できたら、先ほどと同じようにgcpのコンソールから`vertex AI >> トレーニング >> HYPERPARAMETER TUNING JOBS`に遷移した後、 \n", 245 | "リージョンを`asia-northeast1`にして作ったjobが表示されているか確認してみてください。\n", 246 | "\n", 247 | "また、今回は`ccentropy`という指標を評価に使いましたが、これを変更するにはどうすればいいのでしょうか?確認してみてください。" 248 | ] 249 | } 250 | ], 251 | "metadata": { 252 | "environment": { 253 | "kernel": "python3", 254 | "name": "tf2-gpu.2-8.m91", 255 | "type": "gcloud", 256 | "uri": "gcr.io/deeplearning-platform-release/tf2-gpu.2-8:m91" 257 | }, 258 | "kernelspec": { 259 | "display_name": "Python 3", 260 | "language": "python", 261 | "name": "python3" 262 | }, 263 | "language_info": { 264 | "codemirror_mode": { 265 | "name": "ipython", 266 | "version": 3 267 | }, 268 | "file_extension": ".py", 269 | "mimetype": "text/x-python", 270 | "name": "python", 271 | "nbconvert_exporter": "python", 272 | "pygments_lexer": "ipython3", 273 | "version": "3.7.12" 274 | } 275 | }, 276 | "nbformat": 4, 277 | "nbformat_minor": 4 278 | } 279 | -------------------------------------------------------------------------------- /hands-on/03_parameter_tuning/mfashion_estimator/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nami73b/machine-learning-notebooks/6ecabd300c44f55c5d57d8e5ea686868aef469f6/hands-on/03_parameter_tuning/mfashion_estimator/__init__.py -------------------------------------------------------------------------------- /hands-on/03_parameter_tuning/mfashion_estimator/model.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | from tensorflow.keras.datasets import fashion_mnist 3 | from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dropout, Flatten, Dense 4 | 5 | NUM_CLASSES=10 6 | 7 | def preprocess(feature, label=None): 8 | feature = tf.expand_dims(feature, axis=-1) 9 | feature = tf.divide(tf.cast(feature, tf.float32), 255.0) 10 | if label is not None: 11 | label = tf.one_hot(label, NUM_CLASSES) 12 | return feature, label 13 | 14 | def make_input_fn(features, labels, mode, hparams): 15 | def _input_fn(): 16 | dataset = tf.data.Dataset.from_tensor_slices((features, labels)).map(map_func = preprocess) 17 | 18 | if mode == tf.estimator.ModeKeys.TRAIN: 19 | num_epochs = None 20 | dataset = dataset.shuffle(buffer_size = hparams["buffer_size"] * hparams["batch_size"]).repeat(None) 21 | else: 22 | num_epochs = 1 23 | 24 | dataset = dataset.batch(batch_size = hparams["batch_size"]) 25 | return dataset 26 | 27 | return _input_fn 28 | 29 | def cnn_model(): 30 | model = tf.keras.Sequential() 31 | model.add(Conv2D(32, kernel_size = (3,3), activation = "relu", input_shape=(28, 28, 1), name="image")) 32 | model.add(Conv2D(64, kernel_size = (3,3), activation = "relu")) 33 | model.add(MaxPooling2D(pool_size=(2,2))) 34 | model.add(Conv2D(64, kernel_size = (3,3), activation = "relu")) 35 | model.add(Dropout(0.25)) 36 | model.add(Flatten()) 37 | model.add(Dense(128, activation="relu")) 38 | model.add(Dropout(0.5)) 39 | model.add(Dense(10, activation = 'softmax', name='output')) 40 | return model 41 | 42 | def train_and_evaluate(output_dir, hparams): 43 | (X_train, y_train), (X_test, y_test) = fashion_mnist.load_data() 44 | 45 | model = cnn_model() 46 | model.compile(optimizer=tf.keras.optimizers.Adam(hparams["learning_rate"]), 47 | loss=tf.keras.losses.CategoricalCrossentropy(), 48 | metrics=[tf.keras.metrics.CategoricalAccuracy()]) 49 | estimator = tf.keras.estimator.model_to_estimator( 50 | keras_model = model, model_dir = output_dir) 51 | train_spec = tf.estimator.TrainSpec(input_fn=make_input_fn(X_train, y_train, tf.estimator.ModeKeys.TRAIN, hparams), 52 | max_steps=hparams["train_steps"]) 53 | 54 | input_column = tf.feature_column.numeric_column("image_input", shape=(28,28,1), dtype=tf.float32) 55 | serving_input_fn = tf.estimator.export.build_parsing_serving_input_receiver_fn(tf.feature_column.make_parse_example_spec([input_column])) 56 | exporter = tf.estimator.LatestExporter( 57 | name = "exporter", 58 | serving_input_receiver_fn = serving_input_fn) 59 | eval_spec = tf.estimator.EvalSpec(input_fn=make_input_fn(X_test, y_test, tf.estimator.ModeKeys.EVAL, hparams), 60 | exporters=exporter, 61 | steps=None) 62 | 63 | tf.estimator.train_and_evaluate(estimator, train_spec, eval_spec) 64 | estimator.saved_model() 65 | -------------------------------------------------------------------------------- /hands-on/03_parameter_tuning/mfashion_estimator/task.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import os 4 | 5 | from . import model 6 | 7 | if __name__ == '__main__': 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument("--output_dir", required=True, help="GCS location to write checkpoints and export models") 10 | parser.add_argument("--batch_size", type=int, default=64) 11 | parser.add_argument("--buffer_size", type=int, default=100) 12 | parser.add_argument("--learning_rate", type=float, default=0.01) 13 | parser.add_argument("--train_steps", type=int, default=10000) 14 | parser.add_argument("--kernel_size", type=int, default=3) 15 | parser.add_argument("--filter_size_1", type=int, default=32) 16 | parser.add_argument("--filter_size_2", type=int, default=64) 17 | parser.add_argument("--dropout_rate", type=float, default=0.2) 18 | parser.add_argument("--dropout_rate_2", type=float, default=0.4) 19 | parser.add_argument("--batch_norm", dest="batch_norm", action="store_true") 20 | parser.set_defaults(batch_norm = False) 21 | 22 | model_names = [name.replace("_model","") for name in dir(model) if name.endswith("_model")] 23 | parser.add_argument("--model", required=True, help="Type of model. Supported types are {}".format(model_names)) 24 | parser.add_argument("--job-dir", type=str, default = "fashion") 25 | 26 | args = parser.parse_args() 27 | hparams = args.__dict__ 28 | 29 | output_dir = hparams.pop("output_dir") 30 | output_dir = os.path.join( 31 | output_dir, 32 | json.loads( 33 | os.environ.get("TF_CONFIG", "{}") 34 | ).get("task", {}).get("trial", "") 35 | ) 36 | 37 | model.train_and_evaluate(output_dir, hparams) 38 | -------------------------------------------------------------------------------- /hands-on/03_parameter_tuning/mfashion_keras/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nami73b/machine-learning-notebooks/6ecabd300c44f55c5d57d8e5ea686868aef469f6/hands-on/03_parameter_tuning/mfashion_keras/__init__.py -------------------------------------------------------------------------------- /hands-on/03_parameter_tuning/mfashion_keras/callback.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import hypertune 3 | 4 | hpt = hypertune.HyperTune() 5 | 6 | 7 | class MyMetricCallback(tf.keras.callbacks.Callback): 8 | def on_epoch_end(self, epoch, logs=None): 9 | ## ___に適切なものを入れてください 10 | ## ヒント: yamlで指定したmetricIdを確認してください 11 | hpt.report_hyperparameter_tuning_metric( 12 | hyperparameter_metric_tag='___', 13 | metric_value=logs['categorical_crossentropy'], 14 | global_step=epoch 15 | ) 16 | -------------------------------------------------------------------------------- /hands-on/03_parameter_tuning/mfashion_keras/model.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import numpy as np 3 | import os 4 | from tensorflow.keras.datasets import fashion_mnist 5 | from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dropout, Flatten, Dense, BatchNormalization, Activation 6 | 7 | try: 8 | import callback 9 | except ImportError: 10 | from . import callback 11 | 12 | NUM_CLASSES=10 13 | 14 | def preprocess(feature, label=None): 15 | feature = tf.expand_dims(feature, axis=-1) 16 | feature = tf.divide(tf.cast(feature, tf.float32), 255.0) 17 | if label is not None: 18 | label = tf.one_hot(label, NUM_CLASSES) 19 | return feature, label 20 | 21 | def dnn_model(hparams): 22 | model = tf.keras.Sequential([ 23 | Flatten(), 24 | Dense(units = 128, activation = tf.nn.relu), 25 | Dense(units = 64, activation = tf.nn.relu), 26 | Dense(units = 32, activation = tf.nn.relu), 27 | Dropout(rate = hparams["dropout_rate"]), 28 | Dense(10, activation = 'softmax', name='output'), 29 | ]) 30 | return model 31 | 32 | def cnn_model(hparams): 33 | model = tf.keras.Sequential() 34 | model.add(Conv2D(hparams["filter_size_1"], kernel_size = hparams["kernel_size"], activation = "relu", input_shape=(28, 28, 1), name="image")) 35 | model.add(MaxPooling2D(pool_size=(2,2))) 36 | model.add(Dropout(hparams["dropout_rate"])) 37 | model.add(Conv2D(hparams["filter_size_2"], kernel_size = hparams["kernel_size"], activation = "relu")) 38 | model.add(MaxPooling2D(pool_size=(2,2))) 39 | model.add(Dropout(hparams["dropout_rate"])) 40 | model.add(Flatten()) 41 | model.add(Dense(128, activation="relu")) 42 | if hparams["batch_norm"]: 43 | model.add(BatchNormalization()) 44 | model.add(Activation(activation = tf.nn.relu)) 45 | model.add(Dropout(hparams["dropout_rate_2"])) 46 | model.add(Dense(10, activation = 'softmax', name='output')) 47 | return model 48 | 49 | def train_and_evaluate(output_dir, hparams): 50 | (X_train, y_train), _ = fashion_mnist.load_data() 51 | X_train = np.expand_dims(X_train, -1) 52 | X_train = X_train / 255.0 53 | y_train = tf.keras.utils.to_categorical(y_train, 10) 54 | 55 | tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=os.path.join(output_dir, "logs")) 56 | ## 検証のlossが2回連続で改善しない場合にEarlyStoppingするようにしよう 57 | ## ヒント: 今日のスライド資料のどこかに記載例があります 58 | earlystopping_callback = tf.keras.callbacks.EarlyStopping(monitor=___, patience=___, verbose=1) 59 | 60 | if hparams["model"] == "dnn": 61 | model = dnn_model(hparams) 62 | else: 63 | model = cnn_model(hparams) 64 | 65 | model.compile( 66 | ## learning_rateをparmeterから受け取れるようにしよう 67 | ## ヒント: hparamsはtask.pyで定義されています 68 | optimizer=tf.keras.optimizers.Adam(hparams["___"]), 69 | loss=tf.keras.losses.CategoricalCrossentropy(), 70 | metrics=[tf.keras.metrics.CategoricalAccuracy(), tf.keras.metrics.CategoricalCrossentropy()] 71 | ) 72 | model.fit( 73 | X_train, y_train, 74 | batch_size=hparams["batch_size"], 75 | epochs=hparams["train_steps"], 76 | validation_split=0.1, 77 | callbacks=[tensorboard_callback, earlystopping_callback, callback.MyMetricCallback()] 78 | ) 79 | model.save(output_dir, save_format="tf") 80 | -------------------------------------------------------------------------------- /hands-on/03_parameter_tuning/mfashion_keras/task.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import os 4 | 5 | try: 6 | import model 7 | except ImportError: 8 | from . import model 9 | 10 | 11 | if __name__ == '__main__': 12 | parser = argparse.ArgumentParser() 13 | parser.add_argument("--output_dir", required=True, help="GCS location to write checkpoints and export models") 14 | parser.add_argument("--batch_size", type=int, default=64) 15 | parser.add_argument("--buffer_size", type=int, default=100) 16 | parser.add_argument("--learning_rate", type=float, default=0.01) 17 | parser.add_argument("--train_steps", type=int, default=10000) 18 | parser.add_argument("--kernel_size", type=int, default=3) 19 | parser.add_argument("--filter_size_1", type=int, default=32) 20 | parser.add_argument("--filter_size_2", type=int, default=64) 21 | parser.add_argument("--dropout_rate", type=float, default=0.2) 22 | parser.add_argument("--dropout_rate_2", type=float, default=0.4) 23 | parser.add_argument("--batch_norm", dest="batch_norm", action="store_true") 24 | parser.set_defaults(batch_norm = False) 25 | 26 | model_names = [name.replace("_model","") for name in dir(model) if name.endswith("_model")] 27 | parser.add_argument("--model", required=True, help="Type of model. Supported types are {}".format(model_names)) 28 | 29 | args = parser.parse_args() 30 | hparams = args.__dict__ 31 | 32 | output_dir = hparams.pop("output_dir") 33 | output_dir = os.path.join( 34 | output_dir, 35 | os.environ.get("CLOUD_ML_TRIAL_ID", "") 36 | ) 37 | model.train_and_evaluate(output_dir, hparams) 38 | -------------------------------------------------------------------------------- /hands-on/03_parameter_tuning/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages 2 | from setuptools import setup 3 | 4 | 5 | REQUIRED_PACKAGES = [ 6 | 'cloudml-hypertune' 7 | ] 8 | 9 | setup( 10 | name='mfashion_keras', 11 | version='1.0', 12 | install_requires=REQUIRED_PACKAGES, 13 | packages=find_packages(), 14 | ) 15 | -------------------------------------------------------------------------------- /hands-on/04_transfer_learning/04_transfer_learning.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Transfer Learning\n", 8 | "\n", 9 | "世の中にはたくさんの学習済みのモデルがあります。それらを使ってTransfer Learningを行います \n", 10 | "今回はtensorflow_hubにある学習済みモデルを使います。\n", 11 | "\n", 12 | "## 学習済みモデルの再利用\n", 13 | "\n", 14 | "まずはモデルを読み込み、使用してみます。" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "import os\n", 24 | "import shutil\n", 25 | "import tensorflow as tf\n", 26 | "import tensorflow_hub as hub" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "https://tfhub.dev/google/tf2-preview/mobilenet_v2/classification/4 \n", 34 | "今回はこのモデルを使用します。mobile_netは軽量で高速なモデルで、精度もそれなりに高く、非常に使いやすいモデルです。" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "classifier_url =\"https://tfhub.dev/google/tf2-preview/mobilenet_v2/classification/4\"\n", 44 | "IMAGE_SHAPE = (224, 224)\n", 45 | "\n", 46 | "classifier = tf.keras.Sequential([\n", 47 | " hub.KerasLayer(classifier_url, input_shape=IMAGE_SHAPE+(3,))\n", 48 | "])" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "!pip install pillow" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "試しに、好きな画像を学習済みモデルでpredictしてみましょう。\n", 65 | "\n", 66 | "image_urlに好きな画像のurlを入れてみてください。" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "import numpy as np\n", 76 | "import PIL.Image as Image\n", 77 | "\n", 78 | "image_url = \"https://dol.ismcdn.jp/mwimgs/7/1/670m/img_71c53c1d81500a1cf73a4f543e72413f27838.jpg\" # 自分で指定\n", 79 | "\n", 80 | "img = tf.keras.utils.get_file('inu.jpg', image_url)\n", 81 | "img = Image.open(img).resize(IMAGE_SHAPE)\n", 82 | "img\n" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": null, 88 | "metadata": {}, 89 | "outputs": [], 90 | "source": [ 91 | "img = np.array(img) / 255.0\n", 92 | "print(img.shape)" 93 | ] 94 | }, 95 | { 96 | "cell_type": "markdown", 97 | "metadata": {}, 98 | "source": [ 99 | "batch_sizeの分だけ次元を増やしてあげてから、predictしてみます。" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": null, 105 | "metadata": {}, 106 | "outputs": [], 107 | "source": [ 108 | "## これまでと同じようにexpandを使って、batch_size部分の次元を合わせます\n", 109 | "## ヒント: expandする次元が今までと違うことに注意しましょう(1, 244, 244, 3)\n", 110 | "img = ___\n", 111 | "## classifierを使ってpredictを行います\n", 112 | "## ヒント: https://keras.io/ja/models/sequential/ でpredictを行うメソッドを確認してください。\n", 113 | "result = ___ \n", 114 | "## 出力のリストから一番確率の高い要素を出力してみましょう\n", 115 | "## ヒント: https://numpy.org/doc/stable/reference/routines.sort.html から一番高い値のindexを返すメソッドを確認してください。\n", 116 | "predicted_class = np.___(___, axis=-1) \n", 117 | "predicted_class" 118 | ] 119 | }, 120 | { 121 | "cell_type": "markdown", 122 | "metadata": {}, 123 | "source": [ 124 | "予測結果が得られたので、このclass_idがなんに紐づいているのか定義から確認してみます。" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": null, 130 | "metadata": {}, 131 | "outputs": [], 132 | "source": [ 133 | "labels_path = tf.keras.utils.get_file('ImageNetLabels.txt','https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')\n", 134 | "imagenet_labels = np.array(open(labels_path).read().splitlines())\n", 135 | "predicted_class_name = imagenet_labels[predicted_class]\n", 136 | "print(predicted_class_name)" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "きちんと学習済みで推論できていることが確認できます。 \n", 144 | "他にも何枚か試してみてください。\n", 145 | "\n", 146 | "ロードしたモデルについても確認しておきます。" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": null, 152 | "metadata": {}, 153 | "outputs": [], 154 | "source": [ 155 | "classifier.summary()" 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "metadata": {}, 161 | "source": [ 162 | "Trainable params: 0 からこのモデルが再トレーニングできないモデルなことがわかります。" 163 | ] 164 | }, 165 | { 166 | "cell_type": "markdown", 167 | "metadata": {}, 168 | "source": [ 169 | "## 最終層を再学習\n", 170 | "\n", 171 | "学習済みモデルを利用することで、様々な物体を高精度に判別することができるモデルを、手軽に用意することが出来ました。 \n", 172 | "しかしこのまま利用するには少し問題があります。再トレーニングができないので、このモデルで学習されていない画像が出た時に判別できないのです。\n", 173 | "\n", 174 | "この問題を解決するために、Transfer Learning(転移学習)を行います。 \n", 175 | "具体的には、最終層やいくつかの層をあえて取り外し、取り外した部分を再学習させるといったことを行います。 \n", 176 | "これによって、学習済みモデルが持つ高精度な判別能力を維持しつつ、自分の問題設定に合わせた判別が可能となります。\n", 177 | "\n", 178 | "では、実際に再学習させてみてTransfer Learningの効果を確認してみましょう。" 179 | ] 180 | }, 181 | { 182 | "cell_type": "markdown", 183 | "metadata": {}, 184 | "source": [ 185 | "### データセットの確認\n", 186 | "\n", 187 | "まず、学習に使うデータセットを用意します。\n", 188 | "\n", 189 | "https://www.tensorflow.org/datasets/catalog/overview \n", 190 | "上記のカタログの中から、 \n", 191 | "https://www.tensorflow.org/datasets/catalog/oxford_iiit_pet \n", 192 | "こちらのデータセットを今回は使います。\n", 193 | "\n", 194 | "では、このデータセットをローカルに持ってきましょう。" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": null, 200 | "metadata": {}, 201 | "outputs": [], 202 | "source": [ 203 | "%%bash\n", 204 | "DATASET_URL=https://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz\n", 205 | "curl -o images.tar.gz ${DATASET_URL}\n", 206 | "tar -xf images.tar.gz" 207 | ] 208 | }, 209 | { 210 | "cell_type": "markdown", 211 | "metadata": {}, 212 | "source": [ 213 | "これから使うdataset apiに合わせるため、datasetフォルダの中にペットの種類毎のフォルダを作ってまとめておきます。" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": null, 219 | "metadata": {}, 220 | "outputs": [], 221 | "source": [ 222 | "DATASET_DIR = \"dataset\"\n", 223 | "for image in os.listdir(\"images\"):\n", 224 | " pet_kind = '_'.join(image.split(\"_\")[:-1])\n", 225 | " os.makedirs(os.path.join(DATASET_DIR, pet_kind), exist_ok=True)\n", 226 | " if image.split(\".\")[-1] == 'jpg':\n", 227 | " shutil.copy(\"images/{}\".format(image), os.path.join(DATASET_DIR, pet_kind))" 228 | ] 229 | }, 230 | { 231 | "cell_type": "markdown", 232 | "metadata": {}, 233 | "source": [ 234 | "dataset apiの一つである[tf.keras.preprocessing.image_dataset_from_directory](https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/image_dataset_from_directory?hl=en)を使ってデータセットを読み込みましょう。" 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "execution_count": null, 240 | "metadata": {}, 241 | "outputs": [], 242 | "source": [ 243 | "image_size = (224, 224)\n", 244 | "batch_size = 32\n", 245 | "DATASET_DIR = \"dataset\"\n", 246 | "\n", 247 | "## ↑のURLを参考に___を埋めてみましょう。\n", 248 | "## URL内のArgsに各引数の詳細が書かれているので、確認しましょう。\n", 249 | "train_data = tf.keras.preprocessing.image_dataset_from_directory(\n", 250 | " directory=___,\n", 251 | " validation_split=0.2,\n", 252 | " subset=___,\n", 253 | " label_mode=___,\n", 254 | " seed=1111,\n", 255 | " image_size=image_size,\n", 256 | " batch_size=batch_size,\n", 257 | ")\n", 258 | "val_data = tf.keras.preprocessing.image_dataset_from_directory(\n", 259 | " directory=___,\n", 260 | " validation_split=0.2,\n", 261 | " subset=___,\n", 262 | " label_mode=___,\n", 263 | " seed=1111,\n", 264 | " image_size=image_size,\n", 265 | " batch_size=batch_size,\n", 266 | ")" 267 | ] 268 | }, 269 | { 270 | "cell_type": "markdown", 271 | "metadata": {}, 272 | "source": [ 273 | "読み込めたら、Fashion-MNISTと同じように画像データがどうなっているかを確認します。 \n", 274 | "まず、データの形と、ラベルに何が存在するのかを確認してみましょう。" 275 | ] 276 | }, 277 | { 278 | "cell_type": "code", 279 | "execution_count": null, 280 | "metadata": {}, 281 | "outputs": [], 282 | "source": [ 283 | "for images, labels in val_data.take(1):\n", 284 | " print(\"Image shape (batch, height, width, channel): \" + str(images.shape))\n", 285 | " print(\"Label shape (batch, classes): \" + str(labels.shape))\n", 286 | " print(\"Pet classes: \\n\" + str(val_data.class_names))" 287 | ] 288 | }, 289 | { 290 | "cell_type": "markdown", 291 | "metadata": {}, 292 | "source": [ 293 | "バッチサイズや画像のサイズが指定した数になっていることが確認できると思います。 \n", 294 | "また、Fashion-MNISTとは違い、今回のshapeには新たに画像のチャンネルが含まれています。 \n", 295 | "ここから、データセットの画像がグレースケールではなく、RGBのカラー画像であることがわかります。\n", 296 | "\n", 297 | "実際に画像を表示してみましょう。" 298 | ] 299 | }, 300 | { 301 | "cell_type": "code", 302 | "execution_count": null, 303 | "metadata": {}, 304 | "outputs": [], 305 | "source": [ 306 | "import matplotlib.pyplot as plt\n", 307 | "\n", 308 | "plt.figure(figsize=(10, 10))\n", 309 | "def view_dataset():\n", 310 | " for images, labels in val_data.take(1):\n", 311 | " for i in range(9):\n", 312 | " ax = plt.subplot(3, 3, i + 1)\n", 313 | " plt.imshow(images[i].numpy().astype(\"uint8\"))\n", 314 | " plt.title(\"label: \" + val_data.class_names[np.argmax(labels[i])])\n", 315 | " plt.axis(\"off\")\n", 316 | " \n", 317 | "view_dataset()" 318 | ] 319 | }, 320 | { 321 | "cell_type": "markdown", 322 | "metadata": {}, 323 | "source": [ 324 | "犬や猫の犬種/猫種のカラー画像が表示できたかと思います。\n", 325 | "\n", 326 | "データセット全体の分布も見てみましょう。" 327 | ] 328 | }, 329 | { 330 | "cell_type": "code", 331 | "execution_count": null, 332 | "metadata": {}, 333 | "outputs": [], 334 | "source": [ 335 | "class_names = val_data.class_names\n", 336 | "left = range(0, len(class_names))\n", 337 | "height = np.zeros(len(class_names))\n", 338 | "for data in [train_data, val_data]:\n", 339 | " for _, y in data:\n", 340 | " height += np.sum(y, axis=0)\n", 341 | "plt.xticks(rotation=90)\n", 342 | "plt.bar(left, height, tick_label=class_names, align=\"center\")" 343 | ] 344 | }, 345 | { 346 | "cell_type": "markdown", 347 | "metadata": {}, 348 | "source": [ 349 | "ほとんどのデータが約200枚であることが確認できました。" 350 | ] 351 | }, 352 | { 353 | "cell_type": "markdown", 354 | "metadata": {}, 355 | "source": [ 356 | "では、試しにこのデータを先ほどの学習済みモデル、classifierで判別してみましょう。" 357 | ] 358 | }, 359 | { 360 | "cell_type": "code", 361 | "execution_count": null, 362 | "metadata": {}, 363 | "outputs": [], 364 | "source": [ 365 | "plt.figure(figsize=(10, 10))\n", 366 | "for images, labels in val_data.take(1):\n", 367 | " for i in range(3):\n", 368 | " ax = plt.subplot(3, 3, i + 1)\n", 369 | " norm_images = images / 255\n", 370 | " result = classifier.predict(norm_images)\n", 371 | " predicted_class = np.argmax(result[i], axis=-1)\n", 372 | " plt.imshow(images[i].numpy().astype(\"uint8\"))\n", 373 | " plt.title(\n", 374 | " \"label: \" + val_data.class_names[np.argmax(labels[i])] +\n", 375 | " ## ___を埋めてImageNetの予測ラベルを表示できるようにしてください\n", 376 | " ## ヒント: ImageNetのlabelのlistはimagenet_labelsで定義されています\n", 377 | " \"\\npredict: \" + ___[___]\n", 378 | " )\n", 379 | " plt.axis(\"off\")" 380 | ] 381 | }, 382 | { 383 | "cell_type": "markdown", 384 | "metadata": {}, 385 | "source": [ 386 | "何回か繰り返して画像を確認してみてください。 \n", 387 | "すると、犬種/猫種を判別できるものと全く判別できないものがあると思います。 \n", 388 | "純粋な判別ミスもありますが、ここで理解してもらいたいことは、`classifier(mobilenet)が学習に使用しているImageNetデータセットのクラスに存在しないものは判別できない`ということです。\n", 389 | "\n", 390 | "今回だと\n", 391 | "- Havanese\n", 392 | "- Wheaten_terrier\n", 393 | "- American_Bulldog\n", 394 | "- American_Pit_Bull_Terrier\n", 395 | "- Bombay \n", 396 | "- Bengal\n", 397 | "- Rusian_Blue\n", 398 | "- Ragdoll\n", 399 | "- British_Shorthair\n", 400 | "- Ragdoll\n", 401 | "- Abysinian\n", 402 | "- Sphynx\n", 403 | "- ...\n", 404 | "\n", 405 | "あたりはImageNetに含まれていないので判別できません。 \n", 406 | "ImageNetにどういったクラスのものが存在するか興味がある方は、https://starpentagon.net/analytics/ilsvrc2012_class_image/\n", 407 | "でチェックしてみると良いです。(公式では現在検索できなくなっているようです。)\n", 408 | "\n", 409 | "こういったものも含めて犬種/猫種を判別できるようにするというのが、Transfer Learningの主目的となります。" 410 | ] 411 | }, 412 | { 413 | "cell_type": "markdown", 414 | "metadata": {}, 415 | "source": [ 416 | "### Preprocessing\n", 417 | "dataset apiを使ったおかげで、今回はデータのshapeを変える必要はなさそうです。\n", 418 | "\n", 419 | "[tf.keras.layers.experimental.preprocessing.Rescaling](https://www.tensorflow.org/api_docs/python/tf/keras/layers/experimental/preprocessing/Rescaling)を用いて正規化だけ行っておきましょう。" 420 | ] 421 | }, 422 | { 423 | "cell_type": "code", 424 | "execution_count": null, 425 | "metadata": {}, 426 | "outputs": [], 427 | "source": [ 428 | "## documentを参考に___を埋めてデータセットを正規化してください。\n", 429 | "norm_layer = tf.keras.layers.experimental.preprocessing.Rescaling(scale=___)\n", 430 | "norm_train_dataset = train_data.map(lambda x, y: (norm_layer(x), y))\n", 431 | "norm_val_dataset = val_data.map(lambda x, y: (norm_layer(x), y))" 432 | ] 433 | }, 434 | { 435 | "cell_type": "markdown", 436 | "metadata": {}, 437 | "source": [ 438 | "### モデルの構築\n", 439 | "データを用意することができたので、学習に使用するモデルを構築します。\n", 440 | "\n", 441 | "今回はTransfer Learningの有無による精度の差を確認したいので、 \n", 442 | "Fashion-MNISTで使った通常のCNNモデルと、Transfer Learningするモデルの二つを構築していきます。\n", 443 | "\n", 444 | "まず、CNNのモデルを定義しましょう。" 445 | ] 446 | }, 447 | { 448 | "cell_type": "code", 449 | "execution_count": null, 450 | "metadata": {}, 451 | "outputs": [], 452 | "source": [ 453 | "from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dropout, Flatten, Dense\n", 454 | "\n", 455 | "## データセットのクラス数分の値を___に入れてください。\n", 456 | "## ヒント: クラス名はデータセットのclass_names属性にあるので、そのlenをとることでクラス数分の値を取得できそうです。\n", 457 | "NUM_CLASSES = ___\n", 458 | "\n", 459 | "def cnn():\n", 460 | " model = tf.keras.Sequential()\n", 461 | " model.add(Conv2D(32, kernel_size = (3,3), activation = \"relu\"))\n", 462 | " model.add(Conv2D(64, kernel_size = (3,3), activation = \"relu\"))\n", 463 | " model.add(MaxPooling2D(pool_size=(2,2)))\n", 464 | " model.add(Conv2D(64, kernel_size = (3,3), activation = \"relu\"))\n", 465 | " model.add(Dropout(0.25))\n", 466 | " model.add(Flatten())\n", 467 | " model.add(Dense(128, activation=\"relu\"))\n", 468 | " model.add(Dropout(0.5))\n", 469 | " model.add(Dense(NUM_CLASSES, activation = \"softmax\"))\n", 470 | " return model\n", 471 | "\n", 472 | "cnn_model = cnn()" 473 | ] 474 | }, 475 | { 476 | "cell_type": "markdown", 477 | "metadata": {}, 478 | "source": [ 479 | "次に、Transfer Learningするモデルを構築します。\n", 480 | "特徴量ベクトルが取り出すことができる学習済みモデルを読み込みます。\n", 481 | "\n", 482 | "https://tfhub.dev/google/tf2-preview/mobilenet_v2/feature_vector/4\n", 483 | "\n", 484 | "ここからurlをコピーして使います。" 485 | ] 486 | }, 487 | { 488 | "cell_type": "code", 489 | "execution_count": null, 490 | "metadata": {}, 491 | "outputs": [], 492 | "source": [ 493 | "feature_vector_url = \"https://tfhub.dev/google/tf2-preview/mobilenet_v2/feature_vector/4\"\n", 494 | "\n", 495 | "IMAGE_SHAPE = (224, 224)\n", 496 | "\n", 497 | "def mobilenet_v2():\n", 498 | " model = tf.keras.Sequential([\n", 499 | " hub.KerasLayer(feature_vector_url, input_shape=IMAGE_SHAPE+(3,)),\n", 500 | " ## クラス数分のpredictが出来るように最終層にDenseを追加してください。\n", 501 | " ## ヒント: これまでと同じような形でDence層のメソッドを追加すれば良いです。出力層になるので、unit数と活性化関数に注意してください。\n", 502 | " ])\n", 503 | " return model\n", 504 | "\n", 505 | "transfer_learning_model = mobilenet_v2()\n", 506 | "transfer_learning_model.summary()" 507 | ] 508 | }, 509 | { 510 | "cell_type": "markdown", 511 | "metadata": {}, 512 | "source": [ 513 | "最終層につけたDenseがTrainableで、特徴量ベクトルの層がUntrainableになっていることが確認できるかと思います。 \n", 514 | "このTrainable部分を学習させることで、学習モデルにはない犬種/猫種も判別できるモデルにすることができます。" 515 | ] 516 | }, 517 | { 518 | "cell_type": "markdown", 519 | "metadata": {}, 520 | "source": [ 521 | "### Training\n", 522 | "\n", 523 | "モデルの構築ができたので、訓練を開始します。" 524 | ] 525 | }, 526 | { 527 | "cell_type": "code", 528 | "execution_count": null, 529 | "metadata": {}, 530 | "outputs": [], 531 | "source": [ 532 | "## ___を埋めて最適化関数がadam, 損失関数が'categorical_crossentropy'モデルをコンパイルできるようにしてください\n", 533 | "cnn_model.compile(\n", 534 | " ___,\n", 535 | " ___,\n", 536 | " metrics=['categorical_accuracy']\n", 537 | ")" 538 | ] 539 | }, 540 | { 541 | "cell_type": "code", 542 | "execution_count": null, 543 | "metadata": {}, 544 | "outputs": [], 545 | "source": [ 546 | "## 同じように___を埋めて最適化関数がadam, 損失関数が'categorical_crossentropyのモデルをコンパイルできるようにしてください\n", 547 | "transfer_learning_model.compile(\n", 548 | " ___,\n", 549 | " ___,\n", 550 | " metrics=['categorical_accuracy']\n", 551 | ")" 552 | ] 553 | }, 554 | { 555 | "cell_type": "markdown", 556 | "metadata": {}, 557 | "source": [ 558 | "https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/Callback\n", 559 | "を参考に、指標として使いたいlossとaccuracyをbatch毎(step毎)に記録します。" 560 | ] 561 | }, 562 | { 563 | "cell_type": "code", 564 | "execution_count": null, 565 | "metadata": {}, 566 | "outputs": [], 567 | "source": [ 568 | "class CollectBatchStats(tf.keras.callbacks.Callback):\n", 569 | " def __init__(self):\n", 570 | " self.batch_losses = []\n", 571 | " self.batch_acc = []\n", 572 | " self.epoch_val_losses = []\n", 573 | " self.epoch_val_acc = []\n", 574 | " \n", 575 | " def on_train_batch_end(self, batch, logs=None):\n", 576 | " self.batch_losses.append(logs['loss'])\n", 577 | " self.batch_acc.append(logs['categorical_accuracy'])\n", 578 | " self.model.reset_metrics()\n", 579 | " \n", 580 | " def on_epoch_end(self, epoch, logs=None):\n", 581 | " ## 対応するリストに'val_loss'と'val_categorical_accuracy'のlogを追加してください\n", 582 | " ## ヒント: 一つ上のメソッド内を参考にしてください\n", 583 | " \n", 584 | " self.model.reset_metrics()" 585 | ] 586 | }, 587 | { 588 | "cell_type": "code", 589 | "execution_count": null, 590 | "metadata": {}, 591 | "outputs": [], 592 | "source": [ 593 | "batch_stats_callback_cnn = CollectBatchStats()\n", 594 | "## ___に前処理後のtrainとvalidationのデータセットをいれて学習が開始できるようにしてください\n", 595 | "## ヒント: 前処理をしているセルを確認してみてください。\n", 596 | "cnn_model.fit(___, validation_data=___, epochs=5, callbacks=[batch_stats_callback_cnn])" 597 | ] 598 | }, 599 | { 600 | "cell_type": "code", 601 | "execution_count": null, 602 | "metadata": {}, 603 | "outputs": [], 604 | "source": [ 605 | "batch_stats_callback_transfer_learning = CollectBatchStats()\n", 606 | "## 同じように___に前処理後のtrainとvalidationのデータセットをいれて学習が開始できるようにしてください\n", 607 | "## ヒント: 前処理をしているセルを確認してみてください。\n", 608 | "transfer_learning_model.fit(___, validation_data=___, epochs=5, callbacks=[batch_stats_callback_transfer_learning])" 609 | ] 610 | }, 611 | { 612 | "cell_type": "markdown", 613 | "metadata": {}, 614 | "source": [ 615 | "### 可視化\n", 616 | "訓練が終わったら、各モデルのlossとaccuracyの推移を確認してみます。" 617 | ] 618 | }, 619 | { 620 | "cell_type": "code", 621 | "execution_count": null, 622 | "metadata": {}, 623 | "outputs": [], 624 | "source": [ 625 | "plt.figure(figsize=(10, 7))\n", 626 | "plt.suptitle(\"Training\", fontsize=15)\n", 627 | "plt.subplot(2, 2, 1)\n", 628 | "plt.title('Model loss')\n", 629 | "plt.ylabel(\"Loss\")\n", 630 | "plt.xlabel(\"Steps\")\n", 631 | "plt.ylim([0,4])\n", 632 | "plt.plot(batch_stats_callback_cnn.batch_losses, label=\"CNN model\")\n", 633 | "plt.plot(batch_stats_callback_transfer_learning.batch_losses, label=\"Transfer Learning model\")\n", 634 | "plt.grid()\n", 635 | "\n", 636 | "plt.subplot(2, 2, 2)\n", 637 | "plt.title('Model accuracy')\n", 638 | "plt.ylabel(\"Accuracy\")\n", 639 | "plt.xlabel(\"Steps\")\n", 640 | "plt.ylim([0,1])\n", 641 | "plt.plot(batch_stats_callback_cnn.batch_acc, label=\"CNN model\")\n", 642 | "plt.plot(batch_stats_callback_transfer_learning.batch_acc, label=\"Transfer Learning model\")\n", 643 | "plt.grid()\n", 644 | "\n", 645 | "plt.legend(bbox_to_anchor=[1, 1])" 646 | ] 647 | }, 648 | { 649 | "cell_type": "code", 650 | "execution_count": null, 651 | "metadata": {}, 652 | "outputs": [], 653 | "source": [ 654 | "plt.figure(figsize=(10, 7))\n", 655 | "plt.suptitle(\"Validation\", fontsize=15)\n", 656 | "plt.subplot(2, 2, 1)\n", 657 | "plt.title('Model loss')\n", 658 | "plt.ylabel(\"Loss\")\n", 659 | "plt.xlabel(\"Steps\")\n", 660 | "plt.plot(batch_stats_callback_cnn.epoch_val_losses, label=\"CNN model\")\n", 661 | "plt.plot(batch_stats_callback_transfer_learning.epoch_val_losses, label=\"Transfer Learning model\")\n", 662 | "plt.grid()\n", 663 | "\n", 664 | "plt.subplot(2, 2, 2)\n", 665 | "plt.title('Model accuracy')\n", 666 | "plt.ylabel(\"Accuracy\")\n", 667 | "plt.xlabel(\"Steps\")\n", 668 | "plt.plot(batch_stats_callback_cnn.epoch_val_acc, label=\"CNN model\")\n", 669 | "plt.plot(batch_stats_callback_transfer_learning.epoch_val_acc, label=\"Transfer Learning model\")\n", 670 | "plt.grid()\n", 671 | "\n", 672 | "plt.legend(bbox_to_anchor=[1, 1])" 673 | ] 674 | }, 675 | { 676 | "cell_type": "markdown", 677 | "metadata": {}, 678 | "source": [ 679 | "両モデルの違いを確認できたでしょうか。" 680 | ] 681 | }, 682 | { 683 | "cell_type": "markdown", 684 | "metadata": {}, 685 | "source": [ 686 | "各モデルの個別の予測結果も確認しましょう。 \n", 687 | "先程作ったview_datasetメソッドを改良して、ラベルの他にpredictも出力できるようにしましょう。" 688 | ] 689 | }, 690 | { 691 | "cell_type": "code", 692 | "execution_count": null, 693 | "metadata": {}, 694 | "outputs": [], 695 | "source": [ 696 | "def view_dataset(model=None):\n", 697 | " for images, labels in val_data.take(1):\n", 698 | " for i in range(9):\n", 699 | " ax = plt.subplot(3, 3, i + 1)\n", 700 | " plt.imshow(images[i].numpy().astype(\"uint8\"))\n", 701 | " if not model:\n", 702 | " plt.title(\"label: \" + val_data.class_names[np.argmax(labels[i])])\n", 703 | " else:\n", 704 | " norm_img = images / 255\n", 705 | " result = model.predict(norm_img)\n", 706 | " plt.title(\n", 707 | " \"label:\" + val_data.class_names[np.argmax(labels[i])] +\n", 708 | " ## ___を埋めてmodelがpredictした予測ラベルを表示できるようにしましょう\n", 709 | " ## 上の\"label:\"でやってくことが参考になります。predictした結果を渡すことに注意しましょう\n", 710 | " \"\\npredict:\" + ______[___(___[_])]\n", 711 | " )\n", 712 | " plt.axis(\"off\")" 713 | ] 714 | }, 715 | { 716 | "cell_type": "code", 717 | "execution_count": null, 718 | "metadata": {}, 719 | "outputs": [], 720 | "source": [ 721 | "plt.figure(figsize=(10, 10))\n", 722 | "view_dataset(cnn_model)" 723 | ] 724 | }, 725 | { 726 | "cell_type": "code", 727 | "execution_count": null, 728 | "metadata": {}, 729 | "outputs": [], 730 | "source": [ 731 | "plt.figure(figsize=(10, 10))\n", 732 | "view_dataset(transfer_learning_model)" 733 | ] 734 | }, 735 | { 736 | "cell_type": "markdown", 737 | "metadata": {}, 738 | "source": [ 739 | "ImageNetデータセットにない犬種/猫種も予測できたでしょうか。" 740 | ] 741 | }, 742 | { 743 | "cell_type": "code", 744 | "execution_count": null, 745 | "metadata": {}, 746 | "outputs": [], 747 | "source": [] 748 | } 749 | ], 750 | "metadata": { 751 | "environment": { 752 | "kernel": "python3", 753 | "name": "tf2-gpu.2-8.m91", 754 | "type": "gcloud", 755 | "uri": "gcr.io/deeplearning-platform-release/tf2-gpu.2-8:m91" 756 | }, 757 | "kernelspec": { 758 | "display_name": "Python 3", 759 | "language": "python", 760 | "name": "python3" 761 | }, 762 | "language_info": { 763 | "codemirror_mode": { 764 | "name": "ipython", 765 | "version": 3 766 | }, 767 | "file_extension": ".py", 768 | "mimetype": "text/x-python", 769 | "name": "python", 770 | "nbconvert_exporter": "python", 771 | "pygments_lexer": "ipython3", 772 | "version": "3.7.12" 773 | } 774 | }, 775 | "nbformat": 4, 776 | "nbformat_minor": 4 777 | } 778 | -------------------------------------------------------------------------------- /hands-on/05_predict_structured_data/05_predict_structured_data.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "b53de4e4", 6 | "metadata": {}, 7 | "source": [ 8 | "# Gradient Boosting Decision Tree (GBDT)\n", 9 | "\n", 10 | "このハンズオンでは、勾配ブースティング手法のライブラリであるLightGBMを使って、構造化データに対してのモデルを作成していきます。\n", 11 | "\n", 12 | "また勾配ブースティングでは、比較的簡単に予測結果の判断根拠も求めることができます。\n", 13 | "\n", 14 | "この利点としては\n", 15 | "- 予測の判断根拠自体が推論システムとしてあると何かと便利\n", 16 | "- 学習したモデルが一般的な常識/知見に基づいた特徴量であるか確認できる \n", 17 | "\n", 18 | "などが挙げられます。\n", 19 | "\n", 20 | "このような、予測結果の判断根拠を得る手法についても紹介していきます。" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "id": "56e435e9", 27 | "metadata": { 28 | "scrolled": true 29 | }, 30 | "outputs": [], 31 | "source": [ 32 | "# 必要なライブラリ類のインストール\n", 33 | "!pip install lightgbm\n", 34 | "!pip install shap\n", 35 | "!pip install category_encoders\n", 36 | "!pip install google-cloud-storage" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": null, 42 | "id": "9187153c", 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "import pandas as pd\n", 47 | "import numpy as np\n", 48 | "import lightgbm as lgb\n", 49 | "from io import BytesIO\n", 50 | "from google.cloud import storage" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "id": "382f8afc", 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "## GoogleCloudStorageの接続\n", 61 | "project_id = 'hr-mixi'\n", 62 | "buclet_name = 'mixi_hands_on_data_2022'\n", 63 | "\n", 64 | "client = storage.Client(project_id)\n", 65 | "bucket = client.get_bucket(buclet_name)" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "id": "e35f8379", 71 | "metadata": {}, 72 | "source": [ 73 | "## データセットの読み込み\n", 74 | "今回は、実データを用いたタスクに挑戦していきます。 \n", 75 | "具体的には、過去数年の競輪のレースデータを使って、着順予測をしていきます。\n", 76 | "\n", 77 | "**注意: 今回のデータは非公開データとなります。本研修外での利用はお控えください。**\n", 78 | "\n", 79 | "GCSにデータを置いているので、取得してきましょう。" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": null, 85 | "id": "2399f754", 86 | "metadata": {}, 87 | "outputs": [], 88 | "source": [ 89 | "training_data_path = 'training_data/race_data.csv'\n", 90 | "\n", 91 | "blob = bucket.blob(training_data_path)\n", 92 | "content = blob.download_as_string()\n", 93 | "df = pd.read_csv(BytesIO(content))\n", 94 | "\n", 95 | "# 着順のない選手をデータセットから取り除く\n", 96 | "df = df[~df['rank_number'].isnull()].reset_index(drop=True)\n", 97 | "df = df[df['rank_number'] != 0].reset_index(drop=True)" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "id": "5889cf26", 103 | "metadata": {}, 104 | "source": [ 105 | "取得できたら、データを観察してみましょう。 " 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": null, 111 | "id": "ff1701aa", 112 | "metadata": { 113 | "scrolled": true 114 | }, 115 | "outputs": [], 116 | "source": [ 117 | "for c in df.columns:\n", 118 | " print(c, df.iloc[0][c])" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "id": "93a85bc5-563b-47b7-a5b8-730cccbd37a1", 124 | "metadata": {}, 125 | "source": [ 126 | "**NOTE:** 今回は時間の都合上各カラムのデータをさらっと眺めるだけにしていますが、機械学習モデルを開発する上で、データへの理解度(ドメイン知識)は非常に重要な要素となってきます。 \n", 127 | "データのAnalysisだけでも時間をかける価値があるということを理解しておいてください。" 128 | ] 129 | }, 130 | { 131 | "cell_type": "markdown", 132 | "id": "00e43c7f", 133 | "metadata": {}, 134 | "source": [ 135 | "## ラベルデータの作成\n", 136 | "次に、データからラベルを作成していきます。 \n", 137 | "今回は、rank_number つまり着順を使用します。\n", 138 | "\n", 139 | "このとき、着順をそのままラベルに使用すると、タスクは多クラス分類となります。 \n", 140 | "多クラス分類になると、それぞれのクラスを独立したものとして扱うことになりますが、1着と2着などを別クラスとして扱うことは正しいでしょうか。 \n", 141 | "例えば別の考え方として、ラベルを`3着以内orNot`や`1着orNot`に変換する方法も考えられます。 \n", 142 | "このようにした場合、今回のタスクは2値分類タスクとなります。 \n", 143 | "また、`1着orNot`にした場合、出力は確率になるので、その確率が高い順と考えると、結果的に着順も予測できることになります。 \n", 144 | "ラベルをどのように扱えばより良いかは自分で考える必要があるので、このようなドメイン知識が重要になってきます。 \n", 145 | "今回はそのままの着順予測と1着orNotの2値分類を作っていきます。" 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": null, 151 | "id": "8849c74e", 152 | "metadata": { 153 | "scrolled": true 154 | }, 155 | "outputs": [], 156 | "source": [ 157 | "def encord_rank_order(rank_number):\n", 158 | " # LightGBMも多クラス分類は0から始まるラベルが求められるが\n", 159 | " # 着順は1〜9になっているため、0~8に修正する\n", 160 | " return rank_number -1\n", 161 | "\n", 162 | "## ___を埋めて多クラス(9クラス)分類のラベルを作ってください。\n", 163 | "## ヒント: rank_numberには1~9の着順が表形式で入っており、それがapplyの中身で順に処理されていきます。また、applyの第一引数はfuncです\n", 164 | "rank_number = df['rank_number'].apply(___)\n", 165 | "\n", 166 | "## ___を埋めて独自ラベルとして1着orNotの2値分類のラベルを作ってください。\n", 167 | "## ヒント: (無名関数)lambdaの部分は'lambda 引数: (条件がTrueの時の値) if (条件) else (条件がFalseの時の値)'となっています。\n", 168 | "your_target = df['rank_number'].apply(lambda x: 1 if ______ else 0)\n" 169 | ] 170 | }, 171 | { 172 | "cell_type": "markdown", 173 | "id": "be47b0d2", 174 | "metadata": {}, 175 | "source": [ 176 | "## 学習/テスト用データセットの分割\n", 177 | "\n", 178 | "テストデータは日付で2021/01/01以降のレースを対象とします。" 179 | ] 180 | }, 181 | { 182 | "cell_type": "code", 183 | "execution_count": null, 184 | "id": "e4329cab", 185 | "metadata": {}, 186 | "outputs": [], 187 | "source": [ 188 | "train_test_split_date = 20210101\n", 189 | "\n", 190 | "train_index = df[df['event_date'] < train_test_split_date].index\n", 191 | "test_index = df[df['event_date'] >= train_test_split_date].index\n", 192 | "\n", 193 | "df_train = df.loc[train_index].reset_index(drop=True)\n", 194 | "rank_order_train = rank_number[train_index].values\n", 195 | "your_target_train = your_target[train_index]\n", 196 | "\n", 197 | "df_test = df.loc[test_index].reset_index(drop=True)\n", 198 | "rank_order_test = rank_number[test_index].values\n", 199 | "your_target_test = your_target[test_index]" 200 | ] 201 | }, 202 | { 203 | "cell_type": "markdown", 204 | "id": "e9695bca", 205 | "metadata": {}, 206 | "source": [ 207 | "## 前処理\n", 208 | "前処理では一般的に以下のような処理を行います。 \n", 209 | "\n", 210 | "- 文字列やカテゴリカルなデータを数値やベクトルに変換(LabelEncording, OneHotEncordingなど) \n", 211 | "- アルゴリズムが学習しやすいように変換(欠損値処理、正規化など) \n", 212 | "- 組み合わせや集計処理によって新しい特徴量を作成 \n", 213 | "など\n", 214 | "\n", 215 | "前処理コードを学習と予測用で分けているのは、予測時に学習時のラベルエンコーディングのIDのマップと同じようにIDを振り分ける必要がある等、学習時と同様のデータの変換をするように予測用の前処理コードに記載する必要があるためです。 \n", 216 | "今回もタスクに必要な前処理を行っていきます。\n", 217 | "\n", 218 | "**\\<チャレンジ\\>** \n", 219 | "余裕があれば、特徴量の変換や集計で新しい特徴量を作成してみてください。" 220 | ] 221 | }, 222 | { 223 | "cell_type": "code", 224 | "execution_count": null, 225 | "id": "cfc74bb6", 226 | "metadata": {}, 227 | "outputs": [], 228 | "source": [ 229 | "# 学習に使用しないカラムのリスト\n", 230 | "unuse_feature_list = [\n", 231 | " 'event_date', 'race_start_date_time', 'player_number', 'name', 'rank_number'\n", 232 | "]\n", 233 | "\n", 234 | "# レース内の集計をする特徴量\n", 235 | "stat_columns = [\n", 236 | " 'gear_ratio', 'age', 'prize', 'rank', 'total_win_count',\n", 237 | " 'average_race_point', 'place_rate', 'nige_count',\n", 238 | " 'sashi_count', 'makuri_count', 'mark_count', 'standing_count',\n", 239 | " 'back_count'\n", 240 | "]\n", 241 | "\n", 242 | "# カテゴリカル特徴量のリスト\n", 243 | "categorical_feature_list = [\n", 244 | " 'velodrome_code', 'program_name', 'grade', 'group', 'born_prefecture', 'leg_type'\n", 245 | "]\n", 246 | "\n", 247 | "import category_encoders as ce\n", 248 | "\n", 249 | "\n", 250 | "def training_preprocessing(df):\n", 251 | " # カテゴリカル変数の処理\n", 252 | " # LightGBMでは、ラベルエンコーディング(カテゴリをintでID付け)して、typeをcategoryに変換すればライブラリが上手く扱ってくれる\n", 253 | " categorical_encorder = ce.OrdinalEncoder(cols=categorical_feature_list, handle_unknown='impute')\n", 254 | " \n", 255 | " df = categorical_encorder.fit_transform(df)\n", 256 | " for c in categorical_feature_list:\n", 257 | " df[c] = df[c].astype('category')\n", 258 | " \n", 259 | " # レース内での平均値の特徴量の作成\n", 260 | " race_mean = df.groupby(['event_date', 'velodrome_code', 'race_number'])[stat_columns].mean()\n", 261 | "\n", 262 | " race_mean = race_mean.rename(columns= {x: x+'_mean' for x in race_mean.columns})\n", 263 | " df = pd.merge(df, race_mean, how='left', on=['event_date', 'velodrome_code', 'race_number'])\n", 264 | " \n", 265 | " # レース内での中央値の特徴量の作成\n", 266 | " race_median = df.groupby(['event_date', 'velodrome_code', 'race_number'])[stat_columns].median()\n", 267 | "\n", 268 | " race_median = race_median.rename(columns= {x: x+'_median' for x in race_median.columns})\n", 269 | " df = pd.merge(df, race_median, how='left', on=['event_date', 'velodrome_code', 'race_number'])\n", 270 | " \n", 271 | " # 不要なカラムを削除する\n", 272 | " df = df.drop(unuse_feature_list, axis=1)\n", 273 | "\n", 274 | " return df, categorical_encorder\n", 275 | "\n", 276 | "\n", 277 | "def prediction_preprocessing(df, categorical_encorder):\n", 278 | " # カテゴリカル変数の処理\n", 279 | " df = categorical_encorder.transform(df)\n", 280 | " for c in categorical_feature_list:\n", 281 | " df[c] = df[c].astype('category')\n", 282 | " \n", 283 | " # レース内での平均値の特徴量の作成\n", 284 | " race_mean = df.groupby(['event_date', 'velodrome_code', 'race_number'])[stat_columns].mean()\n", 285 | "\n", 286 | " race_mean = race_mean.rename(columns= {x: x+'_mean' for x in race_mean.columns})\n", 287 | " df = pd.merge(df, race_mean, how='left', on=['event_date', 'velodrome_code', 'race_number'])\n", 288 | " \n", 289 | " # レース内での中央値の特徴量の作成\n", 290 | " race_median = df.groupby(['event_date', 'velodrome_code', 'race_number'])[stat_columns].median()\n", 291 | "\n", 292 | " race_median = race_median.rename(columns= {x: x+'_median' for x in race_median.columns})\n", 293 | " df = pd.merge(df, race_median, how='left', on=['event_date', 'velodrome_code', 'race_number'])\n", 294 | " \n", 295 | " # 不要なカラムを削除する\n", 296 | " df = df.drop(unuse_feature_list, axis=1)\n", 297 | "\n", 298 | " return df\n" 299 | ] 300 | }, 301 | { 302 | "cell_type": "code", 303 | "execution_count": null, 304 | "id": "ac9aa104", 305 | "metadata": {}, 306 | "outputs": [], 307 | "source": [ 308 | "preproessed_df_train, categorical_encorder = training_preprocessing(df_train)" 309 | ] 310 | }, 311 | { 312 | "cell_type": "code", 313 | "execution_count": null, 314 | "id": "0cba548a", 315 | "metadata": {}, 316 | "outputs": [], 317 | "source": [ 318 | "preproessed_df_train" 319 | ] 320 | }, 321 | { 322 | "cell_type": "markdown", 323 | "id": "1c30e269", 324 | "metadata": {}, 325 | "source": [ 326 | "## 学習データから検証データを分割\n", 327 | "\n", 328 | "学習用と検証用は0.8:0.2で分割しています。 \n", 329 | "検証用データは、モデル学習時のtest_lossの計算のほか、 \n", 330 | "パラメーターの自動チューニング時のloss指標の計算に使用されます。" 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "execution_count": null, 336 | "id": "4c4d813a", 337 | "metadata": {}, 338 | "outputs": [], 339 | "source": [ 340 | "val_rate = 0.2\n", 341 | "\n", 342 | "train_val_split_point = int(len(df_train)*(1-val_rate))\n", 343 | "\n", 344 | "df_val = preproessed_df_train.iloc[train_val_split_point:].reset_index(drop=True)\n", 345 | "df_train = preproessed_df_train.iloc[:train_val_split_point].reset_index(drop=True)\n", 346 | "\n", 347 | "rank_order_val = rank_order_train[train_val_split_point:]\n", 348 | "rank_order_train = rank_order_train[:train_val_split_point]\n", 349 | "\n", 350 | "your_target_val = your_target_train[train_val_split_point:]\n", 351 | "your_target_train = your_target_train[:train_val_split_point]\n" 352 | ] 353 | }, 354 | { 355 | "cell_type": "markdown", 356 | "id": "7fdd24e3", 357 | "metadata": { 358 | "tags": [] 359 | }, 360 | "source": [ 361 | "## 学習\n", 362 | "\n", 363 | "準備が整ったので、モデルの学習をさせていきます。\n", 364 | "\n", 365 | "主要なLightGBMのパラメータを下記に記載します。\n", 366 | "\n", 367 | "```\n", 368 | "- 出力形式に関する要素\n", 369 | " - objective\n", 370 | " - regression: 回帰\n", 371 | " - binary: 二値分類\n", 372 | " - multiclass: 多クラス分類\n", 373 | " - metric\n", 374 | " - 回帰\n", 375 | " - mae: mean absolute error・平均絶対誤差\n", 376 | " - mse: mean squared error平均2乗誤差\n", 377 | " - 二値分類\n", 378 | " - binary_logloss: クロスエントロピー\n", 379 | " - binary_error: 正解率\n", 380 | " - 多クラス分類\n", 381 | " - multi_logloss: softmax\n", 382 | " - multi_error: 正解率\n", 383 | "- モデル構造に関する要素\n", 384 | " - learning_rate: 学習率 Default=0.1 0以上\n", 385 | " - num_iterations: 木の数\n", 386 | " - num_leaves: 葉(条件の数)\n", 387 | "```\n", 388 | "\n", 389 | "公式リファレンス https://lightgbm.readthedocs.io/en/latest/Parameters.html\n", 390 | " \n", 391 | "**\\<チャレンジ\\>** \n", 392 | "余裕が有れば、lgb_paramsに任意のパラメータを追加して、モデルの精度の変化を観察してください" 393 | ] 394 | }, 395 | { 396 | "cell_type": "code", 397 | "execution_count": null, 398 | "id": "2ac248de", 399 | "metadata": { 400 | "scrolled": true 401 | }, 402 | "outputs": [], 403 | "source": [ 404 | "# 着順の多クラス分類モデルの学習\n", 405 | "\n", 406 | "lgb_train = lgb.Dataset(df_train, rank_order_train)\n", 407 | "lgb_test = lgb.Dataset(df_val, rank_order_val, reference=lgb_train)\n", 408 | "\n", 409 | "## lgb_paramsのobjectiveとmetricを正しい値で埋めてください\n", 410 | "lgb_params = {\n", 411 | " 'boosting_type': 'gbdt',\n", 412 | " 'n_estimators': 1000,\n", 413 | " 'num_class': 9,\n", 414 | " 'early_stopping_rounds': 50,\n", 415 | " 'objective': '______',\n", 416 | " 'metric': '______',\n", 417 | "}\n", 418 | "\n", 419 | "booster_rank_number = lgb.train(\n", 420 | " lgb_params, lgb_train, valid_sets=lgb_test\n", 421 | ")" 422 | ] 423 | }, 424 | { 425 | "cell_type": "code", 426 | "execution_count": null, 427 | "id": "0812b417", 428 | "metadata": { 429 | "scrolled": true 430 | }, 431 | "outputs": [], 432 | "source": [ 433 | "# オリジナルターゲット(2値分類)のモデルの学習\n", 434 | "lgb_train = lgb.Dataset(df_train, your_target_train)\n", 435 | "lgb_test = lgb.Dataset(df_val, your_target_val, reference=lgb_train)\n", 436 | "\n", 437 | "## lgb_paramsのobjectiveとmetricを正しい値で埋めてください\n", 438 | "lgb_params = {\n", 439 | " 'boosting_type': 'gbdt',\n", 440 | " 'n_estimators': 1000,\n", 441 | " 'early_stopping_rounds': 50,\n", 442 | " 'objective': '_____',\n", 443 | " 'metric': '_____',\n", 444 | "}\n", 445 | "\n", 446 | "booster_your_target = lgb.train(\n", 447 | " lgb_params, lgb_train, valid_sets=lgb_test\n", 448 | ")" 449 | ] 450 | }, 451 | { 452 | "cell_type": "markdown", 453 | "id": "ffe8a6f2-e3fd-4487-b07d-ed41368c8bdc", 454 | "metadata": {}, 455 | "source": [ 456 | "学習が始まって、経過が確認できるでしょうか" 457 | ] 458 | }, 459 | { 460 | "cell_type": "markdown", 461 | "id": "a0fe44d1", 462 | "metadata": {}, 463 | "source": [ 464 | "## モデルの判断根拠: Importance\n", 465 | "\n", 466 | "学習が完了したら、特徴量毎のImportanceを確認してみましょう。 \n", 467 | "feature_importanceメソッドでモデルにおける特徴量の重要度を得られます。 \n", 468 | "重要度の指標:\n", 469 | "- split: 決定木の分岐を使用した数 \n", 470 | "- gain: その特徴量が使用する分岐からの目的関数の減少 " 471 | ] 472 | }, 473 | { 474 | "cell_type": "code", 475 | "execution_count": null, 476 | "id": "debc8086", 477 | "metadata": {}, 478 | "outputs": [], 479 | "source": [ 480 | "# テストデータの前処理の実行\n", 481 | "preprocessed_df_test = prediction_preprocessing(df_test, categorical_encorder)" 482 | ] 483 | }, 484 | { 485 | "cell_type": "code", 486 | "execution_count": null, 487 | "id": "e4a8ac45", 488 | "metadata": { 489 | "scrolled": true 490 | }, 491 | "outputs": [], 492 | "source": [ 493 | "# split\n", 494 | "importance = pd.DataFrame(\n", 495 | " booster_your_target.feature_importance(importance_type = 'split'),\n", 496 | " index=preproessed_df_train.columns,\n", 497 | " columns=['importance']\n", 498 | ")\n", 499 | "importance.sort_values(['importance'], ascending=False)" 500 | ] 501 | }, 502 | { 503 | "cell_type": "code", 504 | "execution_count": null, 505 | "id": "781abb69", 506 | "metadata": { 507 | "scrolled": true 508 | }, 509 | "outputs": [], 510 | "source": [ 511 | "# gain\n", 512 | "importance = pd.DataFrame(\n", 513 | " booster_your_target.feature_importance(importance_type = 'gain'),\n", 514 | " index=preproessed_df_train.columns,\n", 515 | " columns=['importance']\n", 516 | ")\n", 517 | "importance.sort_values(['importance'], ascending=False)" 518 | ] 519 | }, 520 | { 521 | "cell_type": "markdown", 522 | "id": "8d878279", 523 | "metadata": {}, 524 | "source": [ 525 | "## モデルの判断根拠: SHAP\n", 526 | "また、SHAPを用いることでも、特徴量がモデル/予測に対してどのような影響を与えたかを計測できます。 \n", 527 | "試してみましょう。" 528 | ] 529 | }, 530 | { 531 | "cell_type": "code", 532 | "execution_count": null, 533 | "id": "4b08035a", 534 | "metadata": {}, 535 | "outputs": [], 536 | "source": [ 537 | "import shap\n", 538 | "shap.initjs()" 539 | ] 540 | }, 541 | { 542 | "cell_type": "code", 543 | "execution_count": null, 544 | "id": "11d81c48", 545 | "metadata": {}, 546 | "outputs": [], 547 | "source": [ 548 | "#LightGBMは決定木アルゴリズムなのでTreeExplainerを使う\n", 549 | "# NNモデルでDeepExplainerを使うと今回のようなテーブルデータだけでなく、画像データに対しても適用することができる\n", 550 | "\n", 551 | "explainer = shap.TreeExplainer(booster_your_target, feature_perturbation = \"tree_path_dependent\")\n", 552 | "shap_values = explainer.shap_values(preprocessed_df_test.values)" 553 | ] 554 | }, 555 | { 556 | "cell_type": "code", 557 | "execution_count": null, 558 | "id": "ee57c572", 559 | "metadata": {}, 560 | "outputs": [], 561 | "source": [ 562 | "# 特徴量の貢献度をプロット\n", 563 | "shap.summary_plot(shap_values, preprocessed_df_test, plot_type=\"bar\")" 564 | ] 565 | }, 566 | { 567 | "cell_type": "markdown", 568 | "id": "63a895c5", 569 | "metadata": {}, 570 | "source": [ 571 | "### 予測ごとの判断根拠\n", 572 | "shap_valuesには各予測に対しての各特徴量の貢献度が格納されています。 \n", 573 | "これを使うことで、例えばあるレースの結果の予測は、この特徴量が強く影響しているためという表現ができるようになります。 \n", 574 | "\n", 575 | "検証用データの0番目の予測結果を見てみましょう。" 576 | ] 577 | }, 578 | { 579 | "cell_type": "code", 580 | "execution_count": null, 581 | "id": "c5b3e6ba", 582 | "metadata": {}, 583 | "outputs": [], 584 | "source": [ 585 | "idx = 0\n", 586 | "\n", 587 | "shap_v = pd.DataFrame(shap_values[1], columns=preprocessed_df_test.columns)" 588 | ] 589 | }, 590 | { 591 | "cell_type": "code", 592 | "execution_count": null, 593 | "id": "b5088e38", 594 | "metadata": {}, 595 | "outputs": [], 596 | "source": [ 597 | "# 予測結果に対しでpositiveな影響を与えている特徴量\n", 598 | "shap_v.iloc[idx].sort_values(ascending=False).iloc[:10]" 599 | ] 600 | }, 601 | { 602 | "cell_type": "code", 603 | "execution_count": null, 604 | "id": "1c07956a", 605 | "metadata": {}, 606 | "outputs": [], 607 | "source": [ 608 | "# 予測結果に対しでnegativeな影響を与えている特徴量\n", 609 | "shap_v.iloc[idx].sort_values(ascending=True).iloc[:10]" 610 | ] 611 | }, 612 | { 613 | "cell_type": "markdown", 614 | "id": "455da7b5", 615 | "metadata": {}, 616 | "source": [ 617 | "## 精度検証\n", 618 | "\n", 619 | "特徴量の貢献度はあらかた調べられたので、締め括りとしてこのモデルの精度の評価をしましょう。\n", 620 | "今回の精度指標は、 \n", 621 | "`レース内で最も1着である確率である確率が高いと評価された選手が実際に1着であった確率` \n", 622 | "と設定します。\n", 623 | "\n", 624 | "データは1行で1選手を表現しているため、1レース単位は複数行を集約する必要があります。 \n", 625 | "groupbyを使ってレースごとのindexを取得していきましょう。" 626 | ] 627 | }, 628 | { 629 | "cell_type": "code", 630 | "execution_count": null, 631 | "id": "42b1049a", 632 | "metadata": {}, 633 | "outputs": [], 634 | "source": [ 635 | "race_groups = df_test.groupby(['event_date', 'velodrome_code', 'race_number']).groups" 636 | ] 637 | }, 638 | { 639 | "cell_type": "markdown", 640 | "id": "87325e20", 641 | "metadata": {}, 642 | "source": [ 643 | "### 着順の多クラス分類モデル" 644 | ] 645 | }, 646 | { 647 | "cell_type": "code", 648 | "execution_count": null, 649 | "id": "3f283cbe", 650 | "metadata": {}, 651 | "outputs": [], 652 | "source": [ 653 | "# 予測結果の取得\n", 654 | "rank_number_prediction_result = booster_rank_number.predict(preprocessed_df_test)" 655 | ] 656 | }, 657 | { 658 | "cell_type": "code", 659 | "execution_count": null, 660 | "id": "8559d215", 661 | "metadata": {}, 662 | "outputs": [], 663 | "source": [ 664 | "# 予測結果の0番目の出力\n", 665 | "# 9つの要素がありそれぞれが各ラベルの確率を表現しています、つまり合計は1になります\n", 666 | "print(rank_number_prediction_result[0])" 667 | ] 668 | }, 669 | { 670 | "cell_type": "code", 671 | "execution_count": null, 672 | "id": "1506fede", 673 | "metadata": {}, 674 | "outputs": [], 675 | "source": [ 676 | "# レースごとの出力値の0番目(1着と推定した確率)がもっとも大きい(np.argmax)選手の実際の着順が1着だった数/ レースの数\n", 677 | "[\n", 678 | " df_test.iloc[idx[np.argmax(\n", 679 | " [\n", 680 | " res[0] for res in rank_number_prediction_result[idx]\n", 681 | " ]\n", 682 | " )]]['rank_number']\n", 683 | " for race, idx in race_groups.items()\n", 684 | "].count(1) / len(race_groups)" 685 | ] 686 | }, 687 | { 688 | "cell_type": "markdown", 689 | "id": "0f7b96eb", 690 | "metadata": {}, 691 | "source": [ 692 | "### '1着 or Not'の2値分類モデル" 693 | ] 694 | }, 695 | { 696 | "cell_type": "code", 697 | "execution_count": null, 698 | "id": "ab9d2640", 699 | "metadata": {}, 700 | "outputs": [], 701 | "source": [ 702 | "your_target_prediction_result = booster_your_target.predict(preprocessed_df_test)" 703 | ] 704 | }, 705 | { 706 | "cell_type": "code", 707 | "execution_count": null, 708 | "id": "bdb3c439", 709 | "metadata": {}, 710 | "outputs": [], 711 | "source": [ 712 | "your_target_prediction_result" 713 | ] 714 | }, 715 | { 716 | "cell_type": "code", 717 | "execution_count": null, 718 | "id": "e61781e2", 719 | "metadata": {}, 720 | "outputs": [], 721 | "source": [ 722 | "# レースごとの出力値がもっとも大きい(np.argmax)選手の実際の着順が1着だった数/ レースの数\n", 723 | "[\n", 724 | " df_test.iloc[idx[np.argmax(your_target_prediction_result[idx])]]['rank_number']\n", 725 | " for race, idx in race_groups.items()\n", 726 | "].count(1) / len(race_groups)" 727 | ] 728 | }, 729 | { 730 | "cell_type": "markdown", 731 | "id": "5de9f85b-f892-4ed3-8435-d670931e8187", 732 | "metadata": {}, 733 | "source": [ 734 | "精度はどうでしょうか? \n", 735 | "着順の多クラス分類と、'1着orNot'の2値分類モデルでどれくらい差があったでしょうか?" 736 | ] 737 | }, 738 | { 739 | "cell_type": "markdown", 740 | "id": "cc8b7299-aca1-4dec-ab8c-e32a9e0ea571", 741 | "metadata": {}, 742 | "source": [ 743 | "========================================================================" 744 | ] 745 | }, 746 | { 747 | "cell_type": "markdown", 748 | "id": "803da971", 749 | "metadata": {}, 750 | "source": [ 751 | "# やってみよう: リアルタイムの競輪レース予測\n", 752 | "\n", 753 | "ここまでで、GBDTのハンズオンは一通り終了です。 \n", 754 | "ですが、折角競輪の予測を作ったので、実際に今日のレースを予測してみましょう!\n", 755 | "\n", 756 | "GCSに本日のレースデータのcsvを作成してあります。 \n", 757 | "このデータに対する予測結果を作成して、今日の全レースに対する予測を作成していきましょう。" 758 | ] 759 | }, 760 | { 761 | "cell_type": "code", 762 | "execution_count": null, 763 | "id": "8b3b2f9a", 764 | "metadata": {}, 765 | "outputs": [], 766 | "source": [ 767 | "# 本日のレースデータをGCSから取得\n", 768 | "prediction_data_path = 'prediction_data/race_data.csv'\n", 769 | "blob = bucket.blob(prediction_data_path)\n", 770 | "content = blob.download_as_string()\n", 771 | "prediction_df = pd.read_csv(BytesIO(content))" 772 | ] 773 | }, 774 | { 775 | "cell_type": "code", 776 | "execution_count": null, 777 | "id": "e3324666", 778 | "metadata": {}, 779 | "outputs": [], 780 | "source": [ 781 | "# groupsの作成\n", 782 | "prediction_race_groups = prediction_df.groupby(['event_date', 'velodrome_code', 'race_number']).groups\n", 783 | "\n", 784 | "# 前処理の実行\n", 785 | "preprocessed_prediction_df = prediction_preprocessing(prediction_df, categorical_encorder)\n", 786 | "\n", 787 | "# 予測結果の作成\n", 788 | "# ここでは'1着orNotモデル'を使用しています。別のモデルを使いたい場合は、変更してください。\n", 789 | "today_prediction_result = booster_your_target.predict(preprocessed_prediction_df)" 790 | ] 791 | }, 792 | { 793 | "cell_type": "markdown", 794 | "id": "eb42492b-6db3-47ce-a982-90bd666a63c8", 795 | "metadata": {}, 796 | "source": [ 797 | "できたら、今日のレースが予想とマッチするかみていきます。" 798 | ] 799 | }, 800 | { 801 | "cell_type": "markdown", 802 | "id": "f8f796a7", 803 | "metadata": {}, 804 | "source": [ 805 | "### 予測結果の確認\n", 806 | "まず、今日の全レースの予想を出力します。" 807 | ] 808 | }, 809 | { 810 | "cell_type": "code", 811 | "execution_count": null, 812 | "id": "ffa7d1a2", 813 | "metadata": {}, 814 | "outputs": [], 815 | "source": [ 816 | "velodrome_code = {\n", 817 | " 11: '函館競輪場', \n", 818 | " 12: '青森競輪場', \n", 819 | " 13: 'いわき平競輪場', \n", 820 | " 21: '弥彦競輪場', \n", 821 | " 22: '前橋競輪場', \n", 822 | " 23: '取手競輪場', \n", 823 | " 24: '宇都宮競輪場', \n", 824 | " 25: '大宮競輪場', \n", 825 | " 26: '西武園競輪場', \n", 826 | " 27: '京王閣競輪場', \n", 827 | " 28: '立川競輪場', \n", 828 | " 31: '松戸競輪場', \n", 829 | " 32: '千葉競輪場', \n", 830 | " 33: '花月園競輪場', \n", 831 | " 34: '川崎競輪場', \n", 832 | " 35: '平塚競輪場', \n", 833 | " 36: '小田原競輪場', \n", 834 | " 37: '伊東競輪場', \n", 835 | " 38: '静岡競輪場', \n", 836 | " 41: '一宮競輪場', \n", 837 | " 42: '名古屋競輪場', \n", 838 | " 43: '岐阜競輪場', \n", 839 | " 44: '大垣競輪場', \n", 840 | " 45: '豊橋競輪場', \n", 841 | " 46: '富山競輪場', \n", 842 | " 47: '松阪競輪場', \n", 843 | " 48: '四日市競輪場', \n", 844 | " 51: '福井競輪場', \n", 845 | " 52: '大津競輪場', \n", 846 | " 53: '奈良競輪場', \n", 847 | " 54: '向日町競輪場', \n", 848 | " 55: '和歌山競輪場', \n", 849 | " 56: '岸和田競輪場', \n", 850 | " 61: '玉野競輪場', \n", 851 | " 62: '広島競輪場', \n", 852 | " 63: '防府競輪場', \n", 853 | " 71: '高松競輪場', \n", 854 | " 72: '観音寺競輪場', \n", 855 | " 73: '小松島競輪場', \n", 856 | " 74: '高知競輪場', \n", 857 | " 75: '松山競輪場', \n", 858 | " 81: '小倉競輪場', \n", 859 | " 83: '久留米競輪場', \n", 860 | " 84: '武雄競輪場', \n", 861 | " 85: '佐世保競輪場', \n", 862 | " 86: '別府競輪場',\n", 863 | " 87: '熊本競輪場', \n", 864 | "}" 865 | ] 866 | }, 867 | { 868 | "cell_type": "code", 869 | "execution_count": null, 870 | "id": "ac717022", 871 | "metadata": {}, 872 | "outputs": [], 873 | "source": [ 874 | "for race, idx in prediction_race_groups.items():\n", 875 | " print('会場: ', velodrome_code[race[1]])\n", 876 | " print('レース番号: ', race[2])\n", 877 | " \n", 878 | " # 1レース分の予測結果を正規化(合計1に変換)して確率の形にしています\n", 879 | " normed_result = today_prediction_result[idx] / sum(today_prediction_result[idx])\n", 880 | " \n", 881 | " for entry_num, prob in zip(prediction_df.iloc[idx]['entry_number'].values, normed_result):\n", 882 | " print(entry_num, '番: ', round(prob, 3), '%')" 883 | ] 884 | }, 885 | { 886 | "cell_type": "markdown", 887 | "id": "b11e8704-e4a2-4b87-a729-4c7be7a560b1", 888 | "metadata": {}, 889 | "source": [ 890 | "予測結果を出力できたら、その予測が当たってるかどうかをTipstarで確認してみましょう。 \n", 891 | "https://tipstar.com/keirin/channels \n", 892 | "過去のレースは既に結果が出ているので、答え合わせができるかと思います。\n", 893 | "\n", 894 | "また競輪は比較的高頻度で開催されているので、今の時間帯でもレース開始時間の近いものがあると思います。 \n", 895 | "上記のURLから、直近のレースを選択してください。\n", 896 | "そして、そのレースの予測を今一度確認してみてください。 \n", 897 | "確認できたら、予測とレース結果が同じになることをリアルタイムで確認していきます。 \n", 898 | "Tipstarにレースの映像があるので、それを見ながら予想した選手を応援しましょう!" 899 | ] 900 | } 901 | ], 902 | "metadata": { 903 | "environment": { 904 | "kernel": "python3", 905 | "name": "tf2-gpu.2-8.m91", 906 | "type": "gcloud", 907 | "uri": "gcr.io/deeplearning-platform-release/tf2-gpu.2-8:m91" 908 | }, 909 | "kernelspec": { 910 | "display_name": "Python 3 (ipykernel)", 911 | "language": "python", 912 | "name": "python3" 913 | }, 914 | "language_info": { 915 | "codemirror_mode": { 916 | "name": "ipython", 917 | "version": 3 918 | }, 919 | "file_extension": ".py", 920 | "mimetype": "text/x-python", 921 | "name": "python", 922 | "nbconvert_exporter": "python", 923 | "pygments_lexer": "ipython3", 924 | "version": "3.9.6" 925 | } 926 | }, 927 | "nbformat": 4, 928 | "nbformat_minor": 5 929 | } 930 | --------------------------------------------------------------------------------