From 2165a9d64475e69b72bdc68cb6b98aab650f2d04 Mon Sep 17 00:00:00 2001 From: thirakawa Date: Thu, 9 Dec 2021 18:23:27 +0900 Subject: [PATCH] [update] update notebooks --- 00_tutorial/operation_check.ipynb | 184 ++++- 00_tutorial/python_and_numpy.ipynb | 956 +++++++++++++------------- 02_dnn_simple_pytorch/MNIST_CNN.ipynb | 410 ++++++++++- 02_dnn_simple_pytorch/MNIST_MLP.ipynb | 375 +++++++++- 11_cnn_pytorch/03_resnet.ipynb | 586 +++++++++++++++- 5 files changed, 2029 insertions(+), 482 deletions(-) diff --git a/00_tutorial/operation_check.ipynb b/00_tutorial/operation_check.ipynb index 713b3c2..c42ba2a 100644 --- a/00_tutorial/operation_check.ipynb +++ b/00_tutorial/operation_check.ipynb @@ -1 +1,183 @@ -{"nbformat":4,"nbformat_minor":0,"metadata":{"accelerator":"GPU","colab":{"name":"00_operation_check.ipynb","provenance":[],"collapsed_sections":[]},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.5.2"}},"cells":[{"cell_type":"markdown","metadata":{"id":"8G5SYENhn6Vg"},"source":["# Google Colaboratoryの動作確認\n","\n","---\n","## 目的\n","Google Colaboratoryの動作環境を確認し,使い方について学ぶ."]},{"cell_type":"markdown","metadata":{"id":"KOKaXxLwyb2D"},"source":["## Google Colaboratoryについて\n","\n","Google Colaboratory (以下,Colab.) はGoogleが提供するクラウド型のJupyter Notebook環境です.\n","Google Colab.は機械学習や画像認識に関する教育や研究を目的として開発されたサービスであり,無料で使用することができます."]},{"cell_type":"markdown","metadata":{"id":"TylkVfSeLFFZ"},"source":["## Google Colab. (Jupyter Notebook) のセル\n","\n","Google Colab.では「セル」にプログラムや説明文を記述して,ノートのようにプログラムを整理します.\n","セルには\n","1. Markdown セル\n","2. Code セル\n","の2種類が存在します.\n","\n","#### 1. Markdownセル\n","Markdownセルは,GitHubのREADMEやQiitaのページを記述する際に用いられるMarkdown記法を用いて,説明文等を記述することができます.\n","\n","#### 2. Codeセル\n","Codeセルはプログラムを記述するためのセルです.通常のPythonと同じようにプログラムを記述します."]},{"cell_type":"markdown","metadata":{"id":"MpHiC0O3LFFa"},"source":["## 実行方法\n","実行はCodeセルの左にある実行ボタンをクリックすることで,一つづつ実行することができます.\n","また,実行したいセルを選択した状態で,`Shift + Enter`を押すことでも実行可能です."]},{"cell_type":"markdown","metadata":{"id":"3gcE9SVYzC3m"},"source":["### 計算機のOS確認\n","下記のコードを実行してOSを確認してみます.\n","\n"]},{"cell_type":"code","metadata":{"id":"FJqP5_-vyb8u"},"source":["!lsb_release -a"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"pFGC3y1I0Upl"},"source":["### ハードウェアの確認\n","\n","#### CPU\n","\n","下記のプログラムを実行してCPU情報を確認します."]},{"cell_type":"code","metadata":{"id":"6B680KB_0Utd"},"source":["!cat /proc/cpuinfo | grep 'model name' | uniq\n","!cat /proc/cpuinfo | grep 'processor' | uniq"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"e9b3Yt1V0UxV"},"source":["#### メモリ\n","\n","メモリの総容量と現在の使用状況はこのページの右上の「RAM」と表示されているアイコンにカーソルを重ねることでも,確認することができます."]},{"cell_type":"code","metadata":{"id":"Q3ppY9ZT0U14"},"source":["!cat /proc/meminfo | grep 'MemTotal'\n","!cat /proc/meminfo | grep 'MemAvailable'"],"execution_count":null,"outputs":[]},{"cell_type":"markdown","metadata":{"id":"AQ0vG_Wt2HGy"},"source":["#### GPU\n","\n","下記のプログラムを実行して,GPUの種類を確認します.\n","情報が表示されない場合は,**上部のメニューバーの「ランタイム」→「ランタイムのタイプを変更」からハードウェアアクセラレータをGPUにしてください.**"]},{"cell_type":"code","metadata":{"id":"JNFjzVEj2HLK"},"source":["!nvidia-smi"],"execution_count":null,"outputs":[]}]} \ No newline at end of file +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "8G5SYENhn6Vg" + }, + "source": [ + "# Google Colaboratoryの動作確認\n", + "\n", + "---\n", + "## 目的\n", + "Google Colaboratoryの動作環境を確認し,使い方について学ぶ." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "KOKaXxLwyb2D" + }, + "source": [ + "## Google Colaboratoryについて\n", + "\n", + "Google Colaboratory (以下,Colab.) はGoogleが提供するクラウド型のJupyter Notebook環境です.\n", + "Google Colab.は機械学習や画像認識に関する教育や研究を目的として開発されたサービスであり,無料で使用することができます." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TylkVfSeLFFZ" + }, + "source": [ + "## Google Colab. (Jupyter Notebook) のセル\n", + "\n", + "Google Colab.では「セル」にプログラムや説明文を記述して,ノートのようにプログラムを整理します.\n", + "セルには\n", + "\n", + "* Markdown セル\n", + "* Code セル\n", + "\n", + "の2種類が存在します.\n", + "\n", + "#### 1. Markdownセル\n", + "Markdownセルは,GitHubのREADMEやQiitaのページを記述する際に用いられるMarkdown記法を用いて,説明文等を記述することができます.\n", + "\n", + "#### 2. Codeセル\n", + "Codeセルはプログラムを記述するためのセルです.通常のPythonと同じようにプログラムを記述します." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MpHiC0O3LFFa" + }, + "source": [ + "## 実行方法\n", + "実行はCodeセルの左にある実行ボタンをクリックすることで,一つづつ実行することができます.\n", + "また,実行したいセルを選択した状態で,`Shift + Enter`を押すことでも実行可能です." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3gcE9SVYzC3m" + }, + "source": [ + "### 計算機のOS確認\n", + "下記のコードを実行してOSを確認してみます.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "FJqP5_-vyb8u" + }, + "outputs": [], + "source": [ + "!lsb_release -a" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "pFGC3y1I0Upl" + }, + "source": [ + "### ハードウェアの確認\n", + "\n", + "#### CPU\n", + "\n", + "下記のプログラムを実行してCPU情報を確認します." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6B680KB_0Utd" + }, + "outputs": [], + "source": [ + "!cat /proc/cpuinfo | grep 'model name' | uniq\n", + "!cat /proc/cpuinfo | grep 'processor' | uniq" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "e9b3Yt1V0UxV" + }, + "source": [ + "#### メモリ\n", + "\n", + "メモリの総容量と現在の使用状況はこのページの右上の「RAM」と表示されているアイコンにカーソルを重ねることでも,確認することができます." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Q3ppY9ZT0U14" + }, + "outputs": [], + "source": [ + "!cat /proc/meminfo | grep 'MemTotal'\n", + "!cat /proc/meminfo | grep 'MemAvailable'" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "AQ0vG_Wt2HGy" + }, + "source": [ + "#### GPU\n", + "\n", + "下記のプログラムを実行して,GPUの種類を確認します.\n", + "情報が表示されない場合は,**上部のメニューバーの「ランタイム」→「ランタイムのタイプを変更」からハードウェアアクセラレータをGPUにしてください.**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "JNFjzVEj2HLK" + }, + "outputs": [], + "source": [ + "!nvidia-smi" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "collapsed_sections": [], + "name": "00_operation_check.ipynb", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/00_tutorial/python_and_numpy.ipynb b/00_tutorial/python_and_numpy.ipynb index 0a4333b..cb568ad 100644 --- a/00_tutorial/python_and_numpy.ipynb +++ b/00_tutorial/python_and_numpy.ipynb @@ -1,481 +1,481 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "8G5SYENhn6Vg" - }, - "source": [ - "# PythonプログラミングとNumPy\n", - "\n", - "---\n", - "## 目的\n", - "PythonとNumPyの基本的な使い方について学ぶ." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "kbswCri5freT" - }, - "source": [ - "## Pythonプログラムの実行方法\n", - "Pythonは近年,画像認識や機械学習等の人工知能研究分野で広く用いられているプログラミング言語です.\n", - "\n", - "Pythonの特徴としては,\n", - "\n", - "1. 基本的な文法が簡単で覚えやすい,書きやすい\n", - "2. ユニバーサルな言語で,どのようなOSでも同じように動作することが可能\n", - "3. 画像認識や機械学習,データ解析に役立つモジュールが豊富に開発されている\n", - "\n", - "という特徴があります.\n", - "以下では,Pythonの基本的な文法をプログラムを交えて説明します.\n", - "\n", - "### モジュール\n", - "モジュールとはPythonにおいて特定の計算を便利に行う,簡単に記述するためのプログラム群であり,C言語のライブラリに相当するものです.\n", - "\n", - "代表的なツールとしては\n", - "* 行列演算など:numpy\n", - "* 科学技術計算など:scipy\n", - "* グラフの描画など:matplotlib\n", - "* 機械学習:scikit-learn\n", - "* ディープラーニング:pylearn2, caffe, chainer\n", - "* 画像処理:pillow, scikit-image, opencv\n", - "* シミュレーション:simpy\n", - "* 解析的な計算:theano\n", - "* インタラクティブシェル:ipython\n", - "\n", - "などの様々なものが存在しています.\n", - "\n", - "\n", - "### print文\n", - "定義した変数などを表示するためにはprint関数(print文)を用います.\n", - "print関数の引数として複数の値を指定することで,一度に複数の情報を表示することができます." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "juQa2f_mnsWa" - }, - "outputs": [], - "source": [ - "print(\"Hello world!\")\n", - "print(1)\n", - "print(\"abc\", 123)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "EqCCmSHDhIL3" - }, - "source": [ - "### Pythonの型\n", - "\n", - "Pythonの代表的な変数の型としては,以下のものが挙げられます.\n", - "\n", - "* 数値型:整数,浮動小数点数,複素数\n", - " - C言語のようなint, floatなどの型宣言は必要ありません\n", - "* コンテナ:リスト,タプル,辞書,集合\n", - " - 型の混在が可能で,基本的にどんな型のデータでも代入可能\n", - "* 文字列\n", - " - シングルクォート (') またはダブルクォート (\") で囲むことで文字列として表現\n", - "* 定数:真偽値(True, False),None(C言語のNULLに相当)\n", - "\n", - "以下では,本実習で使用する代表的な型とPythonの文法についてプログラムを交えて説明します." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "52R-Ydv4ojbz" - }, - "outputs": [], - "source": [ - "# 整数,浮動小数点数\n", - "i = 10\n", - "f = 0.001\n", - "\n", - "# コンテナ(リスト,タプル,辞書,集合)\n", - "l = [1, 0.01, \"abcd\"]\n", - "t = (3, -4.5, \"defg\")\n", - "d = {\"a\": 1.0, \"b\": 2.0, \"c\": 3.0}\n", - "s = {1, 2, 3}\n", - "\n", - "# 真偽値\n", - "b1 = True\n", - "b2 = False\n", - "\n", - "# None\n", - "n = None" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "MYJzRDFXiSrs" - }, - "source": [ - "### 文字列\n", - "文字列はダブルクォート「\"」で囲むことで定義します.\n", - "\n", - "#### 文字列の整形方法\n", - "C言語のprintf系関数のようなものです.\n", - "「%」演算子を利用します.\n", - "複数の変数を文字列に代入する場合は「(...)」を用います.\n", - "(下記プログラム参照)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "OMXeqWSmfrCc" - }, - "outputs": [], - "source": [ - "# + 演算子で連結することが可能です\n", - "string1 = \"abc\" + \"def\"\n", - "print(string1)\n", - "\n", - "# 文字列の整形方法\n", - "# 1. 一つの変数の場合\n", - "value = 10\n", - "string2 = \"integer: %d\" % value\n", - "print(string2)\n", - "\n", - "# 2. 複数の場合\n", - "value1 = 10\n", - "float_val1 = 0.01\n", - "str1 = \"abc\"\n", - "string3 = \"integer: %d, float value: %f, string: %s\" % (value1, float_val1, str1)\n", - "print(string3)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "hOMZNxTWkvQ5" - }, - "source": [ - "#### 制御構文\n", - "PythonでもC言語と同様の制御構文が用意されています.\n", - "\n", - "* if文\n", - "* while文\n", - "* for文\n", - "\n", - "C言語の大きな特徴としてこれらの制御構文の対象範囲をインデント(スペース4つまたは2つ)で表現することが挙げられます.スペースの数はプログラム内で統一する必要があります." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "XI3IQSPjkvW4" - }, - "outputs": [], - "source": [ - "# if文\n", - "a = 0.5\n", - "if a < 0:\n", - " print(\"negative value\")\n", - "elif a > 0:\n", - " print(\"positive value\")\n", - "else:\n", - " print(\"zero\")\n", - "\n", - "# while文\n", - "print(\"==========\")\n", - "b = 0\n", - "while b < 5:\n", - " print(b)\n", - " b += 1\n", - "\n", - "# for文 1 \n", - "print(\"==========\")\n", - "c = [0.1, 2.34, 5.6, 7.89]\n", - "for x in c:\n", - " print(x)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "tuplJdt8kvc9" - }, - "source": [ - "#### 関数\n", - "Pythonの関数は「def」で定義を行います.\n", - "関数の定義でも上の制御構文と同様にインデントを用いて範囲を指定します." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "bw2B0QsCkviz" - }, - "outputs": [], - "source": [ - "# 関数定義\n", - "def sample_func1(x, y):\n", - " z = x + y\n", - " return z\n", - " \n", - "# 関数の実行\n", - "a = 10\n", - "b = 20\n", - "c = sample_func1(a, b)\n", - "print(c)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "ASUdSMbDpy0c" - }, - "source": [ - "#### 多重代入\n", - "C言語と異なり,複数の値を同時にreturnできる." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "zCOGVVHgpysI" - }, - "outputs": [], - "source": [ - "def sample_func2(x, y):\n", - " x = a + 1\n", - " y = b * 2\n", - " return x, y\n", - " \n", - "c, d = sample_func2(1, 2)\n", - "print(c, d)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "cmiETFJ4qksS" - }, - "source": [ - "#### モジュールのインポートと実行\n", - "モジュールは「import」を用いることでプログラム内で使用することが可能です.\n", - "ここでは,行列計算のモジュールである「Numpy」を使用してみます." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "G0J4cwFXqcAh" - }, - "outputs": [], - "source": [ - "# 通常の読み込み\n", - "import numpy\n", - "\n", - "# numpyという関数をnpという名前で使用できるように読み込む\n", - "import numpy as np\n", - "\n", - "# numpy.linalgの中のnormという関数を読み込む(他は読み込まない)\n", - "from numpy.linalg import norm" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "MQy1q1QOseV3" - }, - "source": [ - "***\n", - "## Numpyの基本的な使い方\n", - "本実習をはじめとする画像認識や機械学習のプログラムでは,Numpyを用いてデータの操作を行います.\n", - "以下では,Numpyの基本的な使い方をプログラムを通じて学びます.\n", - "\n", - "### N次元配列:ndarray\n", - "Numpyでは上のリストやタプルとは異なり,型を統一する必要があります." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "X-JroKd3sPqU" - }, - "outputs": [], - "source": [ - "import numpy as np\n", - "\n", - "a = np.array([[0, 0, 1], [0, 0, 2]], dtype=np.float32) # dtype=***で配列要素の型を決めています\n", - "b = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32)\n", - "print(a)\n", - "print(b)\n", - "\n", - "# 行列の演算(和・積)\n", - "c = a + b\n", - "d = a * b # * は要素ごとの掛け算です\n", - "print(c)\n", - "print(d)\n", - "\n", - "# (数学的な)行列の掛け算\n", - "e = np.dot(a, b.T) # b.Tは行列bの転置を表しています\n", - "print(e)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "xRlYHA9QuzTx" - }, - "source": [ - "### 行列の扱い方\n", - "\n", - "#### 特定の要素を取り出す(インデキシング)\n", - "\n", - "`a[i, j]`:行列aの`(i, j)`要素を取り出します.\n", - "\n", - "#### 特定の範囲を切り出す(スライシング)\n", - "範囲を指定する際にはコロン「:」を用います\n", - "\n", - "`a[i,:]`:行列aの`i`行目(のベクトル)\n", - "\n", - "`a[:,j]`:行列aの`j`列目(のベクトル)\n", - "\n", - "`a[:,0:3]`:行列aの`0, 1, 2`列目の部分行列\n", - "\n", - "#### ファンシーインデキシング\n", - "\n", - "条件に合致する特定の要素のみを取り出します.\n", - "認識を失敗したデータだけを取り出す場合などに用いることができる便利な機能です.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "4Mx7l7O3uDJJ" - }, - "outputs": [], - "source": [ - "# インデキシング\n", - "a_elem = a[0, 2]\n", - "print(a_elem)\n", - "\n", - "# スライシング\n", - "a_slice1 = a[1, :]\n", - "a_slice2 = a[:, 2]\n", - "print(a_slice1)\n", - "print(a_slice2)\n", - "\n", - "b_slice = b[1, 0:2]\n", - "print(b_slice)\n", - "\n", - "# ファンシーインデキシング\n", - "a = np.array([1, 0, 1, 0, 0], dtype=np.int32)\n", - "b = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], dtype=np.float32)\n", - "print(b[:, a == 1])" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "colab_type": "text", - "id": "s4PYMSWWwrRf" - }, - "source": [ - "### Numpyの便利な関数\n", - "Numpyには行列を演算するための便利な関数が多数含まれています.\n", - "以下では,いくつかの関数を紹介します.\n", - "ここで全てを紹介することは難しいので,興味のある方は公式のreferenceページへアクセスして調べてみてください.\n", - "\n", - "[Numpy Reference (関数一覧の公式ページ)](https://docs.scipy.org/doc/numpy/reference/)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": {}, - "colab_type": "code", - "id": "N3aFEDIvx0kd" - }, - "outputs": [], - "source": [ - "a = np.array([0,1,2], dtype=np.float32)\n", - "\n", - "# exp(指数関数を要素ごとに計算)\n", - "print(np.exp(a))\n", - "# power(累乗)\n", - "print(np.power(a, 2))\n", - "\n", - "\n", - "# 集計用の関数(非ゼロの要素数を計算)\n", - "b = np.array([1, 0, 1, 0, 0], dtype=np.int32)\n", - "print(np.count_nonzero(b))" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "collapsed_sections": [], - "name": "00_python_and_numpy.ipynb", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.5.2" - } + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "8G5SYENhn6Vg" + }, + "source": [ + "# PythonプログラミングとNumPy\n", + "\n", + "---\n", + "## 目的\n", + "PythonとNumPyの基本的な使い方について学ぶ." + ] }, - "nbformat": 4, - "nbformat_minor": 0 + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "kbswCri5freT" + }, + "source": [ + "## Pythonプログラムの実行方法\n", + "Pythonは近年,画像認識や機械学習等の人工知能研究分野で広く用いられているプログラミング言語です.\n", + "\n", + "Pythonの特徴としては,\n", + "\n", + "1. 基本的な文法が簡単で覚えやすい,書きやすい\n", + "2. ユニバーサルな言語で,どのようなOSでも同じように動作することが可能\n", + "3. 画像認識や機械学習,データ解析に役立つモジュールが豊富に開発されている\n", + "\n", + "という特徴があります.\n", + "以下では,Pythonの基本的な文法をプログラムを交えて説明します.\n", + "\n", + "### モジュール\n", + "モジュールとはPythonにおいて特定の計算を便利に行う,簡単に記述するためのプログラム群であり,C言語のライブラリに相当するものです.\n", + "\n", + "代表的なツールとしては\n", + "* 行列演算など:numpy\n", + "* 科学技術計算など:scipy\n", + "* グラフの描画など:matplotlib\n", + "* 機械学習:scikit-learn\n", + "* ディープラーニング:pylearn2, caffe, chainer\n", + "* 画像処理:pillow, scikit-image, opencv\n", + "* シミュレーション:simpy\n", + "* 解析的な計算:theano\n", + "* インタラクティブシェル:ipython\n", + "\n", + "などの様々なものが存在しています.\n", + "\n", + "\n", + "### print文\n", + "定義した変数などを表示するためにはprint関数(print文)を用います.\n", + "print関数の引数として複数の値を指定することで,一度に複数の情報を表示することができます." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "juQa2f_mnsWa" + }, + "outputs": [], + "source": [ + "print(\"Hello world!\")\n", + "print(1)\n", + "print(\"abc\", 123)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "EqCCmSHDhIL3" + }, + "source": [ + "### Pythonの型\n", + "\n", + "Pythonの代表的な変数の型としては,以下のものが挙げられます.\n", + "\n", + "* 数値型:整数,浮動小数点数,複素数\n", + " - C言語のようなint, floatなどの型宣言は必要ありません\n", + "* コンテナ:リスト,タプル,辞書,集合\n", + " - 型の混在が可能で,基本的にどんな型のデータでも代入可能\n", + "* 文字列\n", + " - シングルクォート (') またはダブルクォート (\") で囲むことで文字列として表現\n", + "* 定数:真偽値(True, False),None(C言語のNULLに相当)\n", + "\n", + "以下では,本実習で使用する代表的な型とPythonの文法についてプログラムを交えて説明します." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "52R-Ydv4ojbz" + }, + "outputs": [], + "source": [ + "# 整数,浮動小数点数\n", + "i = 10\n", + "f = 0.001\n", + "\n", + "# コンテナ(リスト,タプル,辞書,集合)\n", + "l = [1, 0.01, \"abcd\"]\n", + "t = (3, -4.5, \"defg\")\n", + "d = {\"a\": 1.0, \"b\": 2.0, \"c\": 3.0}\n", + "s = {1, 2, 3}\n", + "\n", + "# 真偽値\n", + "b1 = True\n", + "b2 = False\n", + "\n", + "# None\n", + "n = None" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "MYJzRDFXiSrs" + }, + "source": [ + "### 文字列\n", + "文字列はダブルクォート「\"」で囲むことで定義します.\n", + "\n", + "#### 文字列の整形方法\n", + "C言語のprintf系関数のようなものです.\n", + "「%」演算子を利用します.\n", + "複数の変数を文字列に代入する場合は「(...)」を用います.\n", + "(下記プログラム参照)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "OMXeqWSmfrCc" + }, + "outputs": [], + "source": [ + "# + 演算子で連結することが可能です\n", + "string1 = \"abc\" + \"def\"\n", + "print(string1)\n", + "\n", + "# 文字列の整形方法\n", + "# 1. 一つの変数の場合\n", + "value = 10\n", + "string2 = \"integer: %d\" % value\n", + "print(string2)\n", + "\n", + "# 2. 複数の場合\n", + "value1 = 10\n", + "float_val1 = 0.01\n", + "str1 = \"abc\"\n", + "string3 = \"integer: %d, float value: %f, string: %s\" % (value1, float_val1, str1)\n", + "print(string3)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "hOMZNxTWkvQ5" + }, + "source": [ + "#### 制御構文\n", + "PythonでもC言語と同様の制御構文が用意されています.\n", + "\n", + "* if文\n", + "* while文\n", + "* for文\n", + "\n", + "C言語の大きな特徴としてこれらの制御構文の対象範囲をインデント(スペース4つまたは2つ)で表現することが挙げられます.スペースの数はプログラム内で統一する必要があります." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "XI3IQSPjkvW4" + }, + "outputs": [], + "source": [ + "# if文\n", + "a = 0.5\n", + "if a < 0:\n", + " print(\"negative value\")\n", + "elif a > 0:\n", + " print(\"positive value\")\n", + "else:\n", + " print(\"zero\")\n", + "\n", + "# while文\n", + "print(\"==========\")\n", + "b = 0\n", + "while b < 5:\n", + " print(b)\n", + " b += 1\n", + "\n", + "# for文 1 \n", + "print(\"==========\")\n", + "c = [0.1, 2.34, 5.6, 7.89]\n", + "for x in c:\n", + " print(x)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "tuplJdt8kvc9" + }, + "source": [ + "#### 関数\n", + "Pythonの関数は「def」で定義を行います.\n", + "関数の定義でも上の制御構文と同様にインデントを用いて範囲を指定します." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "bw2B0QsCkviz" + }, + "outputs": [], + "source": [ + "# 関数定義\n", + "def sample_func1(x, y):\n", + " z = x + y\n", + " return z\n", + " \n", + "# 関数の実行\n", + "a = 10\n", + "b = 20\n", + "c = sample_func1(a, b)\n", + "print(c)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "ASUdSMbDpy0c" + }, + "source": [ + "#### 多重代入\n", + "C言語と異なり,複数の値を同時にreturnできる." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "zCOGVVHgpysI" + }, + "outputs": [], + "source": [ + "def sample_func2(x, y):\n", + " x = a + 1\n", + " y = b * 2\n", + " return x, y\n", + " \n", + "c, d = sample_func2(1, 2)\n", + "print(c, d)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "cmiETFJ4qksS" + }, + "source": [ + "#### モジュールのインポートと実行\n", + "モジュールは「import」を用いることでプログラム内で使用することが可能です.\n", + "ここでは,行列計算のモジュールである「Numpy」を使用してみます." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "G0J4cwFXqcAh" + }, + "outputs": [], + "source": [ + "# 通常の読み込み\n", + "import numpy\n", + "\n", + "# numpyという関数をnpという名前で使用できるように読み込む\n", + "import numpy as np\n", + "\n", + "# numpy.linalgの中のnormという関数を読み込む(他は読み込まない)\n", + "from numpy.linalg import norm" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "MQy1q1QOseV3" + }, + "source": [ + "***\n", + "## Numpyの基本的な使い方\n", + "本実習をはじめとする画像認識や機械学習のプログラムでは,Numpyを用いてデータの操作を行います.\n", + "以下では,Numpyの基本的な使い方をプログラムを通じて学びます.\n", + "\n", + "### N次元配列:ndarray\n", + "Numpyでは上のリストやタプルとは異なり,型を統一する必要があります." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "X-JroKd3sPqU" + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "a = np.array([[0, 0, 1], [0, 0, 2]], dtype=np.float32) # dtype=***で配列要素の型を決めています\n", + "b = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32)\n", + "print(a)\n", + "print(b)\n", + "\n", + "# 行列の演算(和・積)\n", + "c = a + b\n", + "d = a * b # * は要素ごとの掛け算です\n", + "print(c)\n", + "print(d)\n", + "\n", + "# (数学的な)行列の掛け算\n", + "e = np.dot(a, b.T) # b.Tは行列bの転置を表しています\n", + "print(e)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "xRlYHA9QuzTx" + }, + "source": [ + "### 行列の扱い方\n", + "\n", + "#### 特定の要素を取り出す(インデキシング)\n", + "\n", + "`a[i, j]`:行列aの`(i, j)`要素を取り出します.\n", + "\n", + "#### 特定の範囲を切り出す(スライシング)\n", + "範囲を指定する際にはコロン「:」を用います\n", + "\n", + "`a[i,:]`:行列aの`i`行目(のベクトル)\n", + "\n", + "`a[:,j]`:行列aの`j`列目(のベクトル)\n", + "\n", + "`a[:,0:3]`:行列aの`0, 1, 2`列目の部分行列\n", + "\n", + "#### ファンシーインデキシング\n", + "\n", + "条件に合致する特定の要素のみを取り出します.\n", + "認識を失敗したデータだけを取り出す場合などに用いることができる便利な機能です.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "4Mx7l7O3uDJJ" + }, + "outputs": [], + "source": [ + "# インデキシング\n", + "a_elem = a[0, 2]\n", + "print(a_elem)\n", + "\n", + "# スライシング\n", + "a_slice1 = a[1, :]\n", + "a_slice2 = a[:, 2]\n", + "print(a_slice1)\n", + "print(a_slice2)\n", + "\n", + "b_slice = b[1, 0:2]\n", + "print(b_slice)\n", + "\n", + "# ファンシーインデキシング\n", + "a = np.array([1, 0, 1, 0, 0], dtype=np.int32)\n", + "b = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], dtype=np.float32)\n", + "print(b[:, a == 1])" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "colab_type": "text", + "id": "s4PYMSWWwrRf" + }, + "source": [ + "### Numpyの便利な関数\n", + "Numpyには行列を演算するための便利な関数が多数含まれています.\n", + "以下では,いくつかの関数を紹介します.\n", + "ここで全てを紹介することは難しいので,興味のある方は公式のreferenceページへアクセスして調べてみてください.\n", + "\n", + "[Numpy Reference (関数一覧の公式ページ)](https://docs.scipy.org/doc/numpy/reference/)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": {}, + "colab_type": "code", + "id": "N3aFEDIvx0kd" + }, + "outputs": [], + "source": [ + "a = np.array([0,1,2], dtype=np.float32)\n", + "\n", + "# exp(指数関数を要素ごとに計算)\n", + "print(np.exp(a))\n", + "# power(累乗)\n", + "print(np.power(a, 2))\n", + "\n", + "\n", + "# 集計用の関数(非ゼロの要素数を計算)\n", + "b = np.array([1, 0, 1, 0, 0], dtype=np.int32)\n", + "print(np.count_nonzero(b))" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "collapsed_sections": [], + "name": "00_python_and_numpy.ipynb", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 } diff --git a/02_dnn_simple_pytorch/MNIST_CNN.ipynb b/02_dnn_simple_pytorch/MNIST_CNN.ipynb index 37b2e66..75f3d56 100644 --- a/02_dnn_simple_pytorch/MNIST_CNN.ipynb +++ b/02_dnn_simple_pytorch/MNIST_CNN.ipynb @@ -1 +1,409 @@ -{"cells":[{"cell_type":"markdown","metadata":{"id":"xP6-w6Uxb6jR"},"source":["# CNNによる画像認識(MNIST, PyTorch実装)\n","\n","\n","---\n","## 目的\n","PyTorch実装による畳み込みニューラルネットワーク(CNN)を用いてMNISTデータセットに対する文字認識を行う.\n","評価はConfusion Matrixにより各クラスの認識率を用いて行う.\n","\n","また,GPUを用いたネットワークの計算を行う."]},{"cell_type":"markdown","metadata":{"id":"5rQGfxWYK_4O"},"source":["## 準備\n","\n","### Google Colaboratoryの設定確認・変更\n","本チュートリアルではPyTorchを利用してニューラルネットワークの実装を確認,学習および評価を行います.\n","**GPUを用いて処理を行うために,上部のメニューバーの「ランタイム」→「ランタイムのタイプを変更」からハードウェアアクセラレータをGPUにしてください.**"]},{"cell_type":"markdown","metadata":{"id":"RsGSLNkYQmkG"},"source":["## モジュールのインポート\n","はじめに必要なモジュールをインポートする."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"SLeGt2xaNFOB"},"outputs":[],"source":["from time import time\n","import torch\n","import torch.nn as nn\n","\n","import torchvision\n","import torchvision.transforms as transforms\n","\n","import torchsummary"]},{"cell_type":"markdown","metadata":{"id":"FjrYHYpuLbrx"},"source":["### GPUの確認\n","GPUを使用した計算が可能かどうかを確認します.\n","\n","`Use CUDA: True`と表示されれば,GPUを使用した計算をPyTorchで行うことが可能です.\n","Falseとなっている場合は,上記の「Google Colaboratoryの設定確認・変更」に記載している手順にしたがって,設定を変更した後に,モジュールのインポートから始めてください."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"6wYHKJ-WLbry"},"outputs":[],"source":["use_cuda = torch.cuda.is_available()\n","print('Use CUDA:', use_cuda)"]},{"cell_type":"markdown","metadata":{"id":"Ue60y-upamyo"},"source":["## データセットの読み込みと確認\n","学習データ(MNIST Dataset)を読み込みます."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"n7zpMk-4axYm"},"outputs":[],"source":["train_data = torchvision.datasets.MNIST(root=\"./\", train=True, transform=transforms.ToTensor(), download=True)\n","test_data = torchvision.datasets.MNIST(root=\"./\", train=False, transform=transforms.ToTensor(), download=True)\n","\n","print(type(train_data.data), type(train_data.targets))\n","print(type(test_data.data), type(test_data.targets))\n","print(train_data.data.size(), train_data.targets.size())\n","print(test_data.data.size(), test_data.targets.size())"]},{"cell_type":"markdown","metadata":{"id":"G418kZOgToXR"},"source":["## ネットワークモデルの定義\n","\n","畳み込みニューラルネットワークを定義します.\n","\n","ここでは,畳み込み層2層,全結合層3層から構成されるネットワークとします.\n","\n","1層目の畳み込み層は入力チャンネル数が1,出力する特徴マップ数が16,畳み込むフィルタサイズが3x3です.\n","2層目の畳み込み層は入力チャネル数が16.出力する特徴マップ数が32,畳み込むフィルタサイズは同じく3x3です.\n","1つ目の全結合層は入力ユニット数は`7*7*32`とし,出力は1024としています.\n","次の全結合層入力,出力共に1024,出力層は入力が1024,出力が10です.\n","また,活性化関数として`self.act`にシグモイド関数を定義します.\n","さらに,プーリング処理を行うための`self.pool`を定義します.\n","ここでは,maxpoolingを使用します.\n","これらの各層の構成を`__init__`関数で定義します.\n","\n","次に,`forward`関数では,定義した層を接続して処理するように記述します.\n","`forward`関数の引数`x`は入力データです.\n","それを`__init__`関数で定義した`conv1`に入力し,その出力を活性化関数である`self.act`に与えます.\n","そして,その出力を`self.pool`に与えて,プーリング処理結果を`h`として出力します.\n","2層目の畳み込み層でも同様の手順で処理を行います.\n","\n","畳み込みを適用した後の特徴マップを全結合層へと入力して,識別結果を出力します.\n","まず.畳み込みによって得られた特徴マップの形状(チャンネルx縦x横)を1次元の配列へと変換します.\n","ここで,`view()`を用いることで,`h`の配列を操作します.引数として,変換したい配列のサイズを入力します.\n","まず一つ目の引数の`h.size()[0]`で,`h`の1次元目のサイズを取得し,変換後の配列の1次元目のサイズとして指定します.\n","二つ目の引数の`-1`で任意のサイズを指定します.\n","これにより,`h`を(バッチ数x任意の長さのデータ)の形状へ変換します.\n","変換した`h`を全結合層および活性化関数へと順次入力することで,最終的にクラススコアを返します."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"8FJhkBJnTuPd"},"outputs":[],"source":["class CNN(nn.Module):\n"," def __init__(self):\n"," super().__init__()\n"," self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1)\n"," self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1)\n"," self.l1 = nn.Linear(7*7*32, 1024)\n"," self.l2 = nn.Linear(1024, 1024)\n"," self.l3 = nn.Linear(1024, 10)\n"," self.act = nn.ReLU()\n"," self.pool = nn.MaxPool2d(2, 2)\n","\n"," def forward(self, x):\n"," h = self.pool(self.act(self.conv1(x)))\n"," h = self.pool(self.act(self.conv2(h)))\n"," h = h.view(h.size()[0], -1)\n"," h = self.act(self.l1(h))\n"," h = self.act(self.l2(h))\n"," h = self.l3(h)\n"," return h"]},{"cell_type":"markdown","metadata":{"id":"ijVjOGVhb6vs"},"source":["## ネットワークの作成\n","上のプログラムで定義したネットワークを作成します.\n","\n","`CNN`クラスを呼び出して,ネットワークモデルを定義します.\n","また,GPUを使う場合(`use_cuda == True`)には,ネットワークモデルをGPUメモリ上に配置します.\n","これにより,GPUを用いた演算が可能となります.\n","\n","学習を行う際の最適化方法としてモーメンタムSGD(モーメンタム付き確率的勾配降下法)を利用します.\n","また,学習率を0.01,モーメンタムを0.9として引数に与えます.\n","\n","最後に,定義したネットワークの詳細情報を`torchsummary.summary()`関数を用いて表示します."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"SyfYfpXvb62g"},"outputs":[],"source":["model = CNN()\n","if use_cuda:\n"," model.cuda()\n","\n","optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)\n","\n","# モデルの情報を表示\n","if use_cuda:\n"," torchsummary.summary(model, (1, 28, 28), device='cuda')\n","else:\n"," torchsummary.summary(model, (1, 28, 28), device='cpu')"]},{"cell_type":"markdown","metadata":{"id":"lhbw4THgb680"},"source":["## 学習\n","読み込んだMNISTデータセットと作成したネットワークを用いて,学習を行います.\n","\n","1回の誤差を算出するデータ数(ミニバッチサイズ)を100,学習エポック数を10とします.\n","\n","次にデータローダーを定義します.\n","データローダーでは,上で読み込んだデータセット(`train_data`)を用いて,for文で指定したミニバッチサイズでデータを読み込むオブジェクトを作成します.\n","この時,`shuffle=True`と設定することで,読み込むデータを毎回ランダムに指定します.\n","\n","次に,誤差関数を設定します.\n","今回は,分類問題をあつかうため,クロスエントロピー誤差を計算するための`CrossEntropyLoss`を`criterion`として定義します.\n","\n","学習を開始します.\n","\n","各更新において,学習用データと教師データをそれぞれ`image`と`label`とします.\n","学習モデルにimageを与えて各クラスの確率yを取得します.\n","各クラスの確率yと教師ラベルtとの誤差を`criterion`で算出します.\n","また,認識精度も算出します.\n","そして,誤差をbackward関数で逆伝播し,ネットワークの更新を行います."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"UsBaxg2Wb7Dp"},"outputs":[],"source":["# ミニバッチサイズ・エポック数の設定\n","batch_size = 100\n","epoch_num = 10\n","\n","# データローダーの設定\n","train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True)\n","\n","# 誤差関数の設定\n","criterion = nn.CrossEntropyLoss()\n","if use_cuda:\n"," criterion.cuda()\n","\n","# ネットワークを学習モードへ変更\n","model.train()\n","\n","# 学習の実行\n","train_start = time()\n","for epoch in range(1, epoch_num+1):\n"," sum_loss = 0.0\n"," count = 0\n","\n"," for image, label in train_loader:\n","\n"," if use_cuda:\n"," image = image.cuda()\n"," label = label.cuda()\n","\n"," y = model(image)\n","\n"," loss = criterion(y, label)\n"," model.zero_grad()\n"," loss.backward()\n"," optimizer.step()\n","\n"," sum_loss += loss.item()\n","\n"," pred = torch.argmax(y, dim=1)\n"," count += torch.sum(pred == label)\n","\n"," print(\"epoch: {}, mean loss: {}, mean accuracy: {}, elapsed time: {}\".format(epoch, sum_loss/600, count.item()/60000., time() - train_start))"]},{"cell_type":"markdown","metadata":{"id":"f5oxc_C-b6g9"},"source":["## テスト\n","\n","学習したネットワークを用いて,テストデータに対する認識率の確認を行います.\n","\n","`model.eval()`を適用することで,ネットワーク演算を評価モードへ変更します.\n","これにより,学習時と評価時で挙動が異なる演算(dropout等)を変更することが可能です.\n","また,`torch.no_grad()`を適用することで,学習時には必要になる勾配情報を保持することなく演算を行います."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"eDwQ-iJtjSaL"},"outputs":[],"source":["# データローダーの準備\n","test_loader = torch.utils.data.DataLoader(test_data, batch_size=100, shuffle=False)\n","\n","# ネットワークを評価モードへ変更\n","model.eval()\n","\n","# 評価の実行\n","count = 0\n","with torch.no_grad():\n"," for image, label in test_loader:\n","\n"," if use_cuda:\n"," image = image.cuda()\n"," label = label.cuda()\n"," \n"," y = model(image)\n","\n"," pred = torch.argmax(y, dim=1)\n"," count += torch.sum(pred == label)\n","\n","print(\"test accuracy: {}\".format(count.item() / 10000.))"]},{"cell_type":"markdown","metadata":{"id":"RO9gksBuj0qm"},"source":["## 課題\n","\n","\n","### 1. GPUを用いた場合とCPUを用いた場合の学習での計算時間の違いを確認しましょう.\n","\n","**ヒント**\n","\n","GPUとCPUの切り替えは「GPUの確認」というセル(本ページ上部)にある`use_cuda`の`True`, `False`を変更することで,切り替えが可能です.\n","\n","\n","### 2. ネットワークの構造を変更し,認識精度の変化を確認しましょう.\n","\n","**ヒント:ネットワーク構造の変更としては,次のようなものが考えられます.**\n","* 中間層のユニット数\n","* 層の数\n","* 活性化関数\n"," * `nn.Tanh()`や`nn.ReLU()`, `nn.LeakyReLU()`などが考えられます.\n"," * その他のPyTorchで使用できる活性化関数は[こちらページ](https://pytorch.org/docs/stable/nn.html#non-linear-activations-weighted-sum-nonlinearity)にまとめられています.\n","\n","※ ネットワーク構造を変更した際には,`torchsummary.summary(***)`を使用し,ネットワーク構造を変更した際のパラメータ数の変化を確認してみましょう.\n","\n","\n","### 3. 学習の設定を変更し,認識精度の変化を確認しましょう.\n","\n","**ヒント:プログラムの中で変更で切る設定は次のようなものが存在します.**\n","* ミニバッチサイズ\n","* 学習回数(Epoch数)\n","* 学習率\n","* 最適化手法\n"," * `torch.optim.Adagrad()`や`torch.optim.Adam()`などが考えられます.\n"," * PyTorchで使用できる最適化手法は[こちらのページ](https://pytorch.org/docs/stable/optim.html#algorithms)にまとめられています.\n","\n"]}],"metadata":{"accelerator":"GPU","colab":{"collapsed_sections":[],"name":"10_MNIST_CNN.ipynb","provenance":[]},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.9"}},"nbformat":4,"nbformat_minor":0} +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "xP6-w6Uxb6jR" + }, + "source": [ + "# CNNによる画像認識(MNIST, PyTorch実装)\n", + "\n", + "\n", + "---\n", + "## 目的\n", + "PyTorch実装による畳み込みニューラルネットワーク(CNN)を用いてMNISTデータセットに対する文字認識を行う.\n", + "評価はConfusion Matrixにより各クラスの認識率を用いて行う.\n", + "\n", + "また,GPUを用いたネットワークの計算を行う." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5rQGfxWYK_4O" + }, + "source": [ + "## 準備\n", + "\n", + "### Google Colaboratoryの設定確認・変更\n", + "本チュートリアルではPyTorchを利用してニューラルネットワークの実装を確認,学習および評価を行います.\n", + "**GPUを用いて処理を行うために,上部のメニューバーの「ランタイム」→「ランタイムのタイプを変更」からハードウェアアクセラレータをGPUにしてください.**" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RsGSLNkYQmkG" + }, + "source": [ + "## モジュールのインポート\n", + "はじめに必要なモジュールをインポートする." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "SLeGt2xaNFOB" + }, + "outputs": [], + "source": [ + "from time import time\n", + "import torch\n", + "import torch.nn as nn\n", + "\n", + "import torchvision\n", + "import torchvision.transforms as transforms\n", + "\n", + "import torchsummary" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FjrYHYpuLbrx" + }, + "source": [ + "### GPUの確認\n", + "GPUを使用した計算が可能かどうかを確認します.\n", + "\n", + "`Use CUDA: True`と表示されれば,GPUを使用した計算をPyTorchで行うことが可能です.\n", + "Falseとなっている場合は,上記の「Google Colaboratoryの設定確認・変更」に記載している手順にしたがって,設定を変更した後に,モジュールのインポートから始めてください." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6wYHKJ-WLbry" + }, + "outputs": [], + "source": [ + "use_cuda = torch.cuda.is_available()\n", + "print('Use CUDA:', use_cuda)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Ue60y-upamyo" + }, + "source": [ + "## データセットの読み込みと確認\n", + "学習データ(MNIST Dataset)を読み込みます." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "n7zpMk-4axYm" + }, + "outputs": [], + "source": [ + "train_data = torchvision.datasets.MNIST(root=\"./\", train=True, transform=transforms.ToTensor(), download=True)\n", + "test_data = torchvision.datasets.MNIST(root=\"./\", train=False, transform=transforms.ToTensor(), download=True)\n", + "\n", + "print(type(train_data.data), type(train_data.targets))\n", + "print(type(test_data.data), type(test_data.targets))\n", + "print(train_data.data.size(), train_data.targets.size())\n", + "print(test_data.data.size(), test_data.targets.size())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "G418kZOgToXR" + }, + "source": [ + "## ネットワークモデルの定義\n", + "\n", + "畳み込みニューラルネットワークを定義します.\n", + "\n", + "ここでは,畳み込み層2層,全結合層3層から構成されるネットワークとします.\n", + "\n", + "1層目の畳み込み層は入力チャンネル数が1,出力する特徴マップ数が16,畳み込むフィルタサイズが3x3です.\n", + "2層目の畳み込み層は入力チャネル数が16.出力する特徴マップ数が32,畳み込むフィルタサイズは同じく3x3です.\n", + "1つ目の全結合層は入力ユニット数は`7*7*32`とし,出力は1024としています.\n", + "次の全結合層入力,出力共に1024,出力層は入力が1024,出力が10です.\n", + "また,活性化関数として`self.act`にシグモイド関数を定義します.\n", + "さらに,プーリング処理を行うための`self.pool`を定義します.\n", + "ここでは,maxpoolingを使用します.\n", + "これらの各層の構成を`__init__`関数で定義します.\n", + "\n", + "次に,`forward`関数では,定義した層を接続して処理するように記述します.\n", + "`forward`関数の引数`x`は入力データです.\n", + "それを`__init__`関数で定義した`conv1`に入力し,その出力を活性化関数である`self.act`に与えます.\n", + "そして,その出力を`self.pool`に与えて,プーリング処理結果を`h`として出力します.\n", + "2層目の畳み込み層でも同様の手順で処理を行います.\n", + "\n", + "畳み込みを適用した後の特徴マップを全結合層へと入力して,識別結果を出力します.\n", + "まず.畳み込みによって得られた特徴マップの形状(チャンネルx縦x横)を1次元の配列へと変換します.\n", + "ここで,`view()`を用いることで,`h`の配列を操作します.引数として,変換したい配列のサイズを入力します.\n", + "まず一つ目の引数の`h.size()[0]`で,`h`の1次元目のサイズを取得し,変換後の配列の1次元目のサイズとして指定します.\n", + "二つ目の引数の`-1`で任意のサイズを指定します.\n", + "これにより,`h`を(バッチ数x任意の長さのデータ)の形状へ変換します.\n", + "変換した`h`を全結合層および活性化関数へと順次入力することで,最終的にクラススコアを返します." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "8FJhkBJnTuPd" + }, + "outputs": [], + "source": [ + "class CNN(nn.Module):\n", + " def __init__(self):\n", + " super().__init__()\n", + " self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=1, padding=1)\n", + " self.conv2 = nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1)\n", + " self.l1 = nn.Linear(7*7*32, 1024)\n", + " self.l2 = nn.Linear(1024, 1024)\n", + " self.l3 = nn.Linear(1024, 10)\n", + " self.act = nn.ReLU()\n", + " self.pool = nn.MaxPool2d(2, 2)\n", + "\n", + " def forward(self, x):\n", + " h = self.pool(self.act(self.conv1(x)))\n", + " h = self.pool(self.act(self.conv2(h)))\n", + " h = h.view(h.size()[0], -1)\n", + " h = self.act(self.l1(h))\n", + " h = self.act(self.l2(h))\n", + " h = self.l3(h)\n", + " return h" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ijVjOGVhb6vs" + }, + "source": [ + "## ネットワークの作成\n", + "上のプログラムで定義したネットワークを作成します.\n", + "\n", + "`CNN`クラスを呼び出して,ネットワークモデルを定義します.\n", + "また,GPUを使う場合(`use_cuda == True`)には,ネットワークモデルをGPUメモリ上に配置します.\n", + "これにより,GPUを用いた演算が可能となります.\n", + "\n", + "学習を行う際の最適化方法としてモーメンタムSGD(モーメンタム付き確率的勾配降下法)を利用します.\n", + "また,学習率を0.01,モーメンタムを0.9として引数に与えます.\n", + "\n", + "最後に,定義したネットワークの詳細情報を`torchsummary.summary()`関数を用いて表示します." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "SyfYfpXvb62g" + }, + "outputs": [], + "source": [ + "model = CNN()\n", + "if use_cuda:\n", + " model.cuda()\n", + "\n", + "optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)\n", + "\n", + "# モデルの情報を表示\n", + "if use_cuda:\n", + " torchsummary.summary(model, (1, 28, 28), device='cuda')\n", + "else:\n", + " torchsummary.summary(model, (1, 28, 28), device='cpu')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "lhbw4THgb680" + }, + "source": [ + "## 学習\n", + "読み込んだMNISTデータセットと作成したネットワークを用いて,学習を行います.\n", + "\n", + "1回の誤差を算出するデータ数(ミニバッチサイズ)を100,学習エポック数を10とします.\n", + "\n", + "次にデータローダーを定義します.\n", + "データローダーでは,上で読み込んだデータセット(`train_data`)を用いて,for文で指定したミニバッチサイズでデータを読み込むオブジェクトを作成します.\n", + "この時,`shuffle=True`と設定することで,読み込むデータを毎回ランダムに指定します.\n", + "\n", + "次に,誤差関数を設定します.\n", + "今回は,分類問題をあつかうため,クロスエントロピー誤差を計算するための`CrossEntropyLoss`を`criterion`として定義します.\n", + "\n", + "学習を開始します.\n", + "\n", + "各更新において,学習用データと教師データをそれぞれ`image`と`label`とします.\n", + "学習モデルにimageを与えて各クラスの確率yを取得します.\n", + "各クラスの確率yと教師ラベルtとの誤差を`criterion`で算出します.\n", + "また,認識精度も算出します.\n", + "そして,誤差をbackward関数で逆伝播し,ネットワークの更新を行います." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "UsBaxg2Wb7Dp" + }, + "outputs": [], + "source": [ + "# ミニバッチサイズ・エポック数の設定\n", + "batch_size = 100\n", + "epoch_num = 10\n", + "\n", + "# データローダーの設定\n", + "train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True)\n", + "\n", + "# 誤差関数の設定\n", + "criterion = nn.CrossEntropyLoss()\n", + "if use_cuda:\n", + " criterion.cuda()\n", + "\n", + "# ネットワークを学習モードへ変更\n", + "model.train()\n", + "\n", + "# 学習の実行\n", + "train_start = time()\n", + "for epoch in range(1, epoch_num+1):\n", + " sum_loss = 0.0\n", + " count = 0\n", + "\n", + " for image, label in train_loader:\n", + "\n", + " if use_cuda:\n", + " image = image.cuda()\n", + " label = label.cuda()\n", + "\n", + " y = model(image)\n", + "\n", + " loss = criterion(y, label)\n", + " model.zero_grad()\n", + " loss.backward()\n", + " optimizer.step()\n", + "\n", + " sum_loss += loss.item()\n", + "\n", + " pred = torch.argmax(y, dim=1)\n", + " count += torch.sum(pred == label)\n", + "\n", + " print(\"epoch: {}, mean loss: {}, mean accuracy: {}, elapsed time: {}\".format(epoch, sum_loss/600, count.item()/60000., time() - train_start))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "f5oxc_C-b6g9" + }, + "source": [ + "## テスト\n", + "\n", + "学習したネットワークを用いて,テストデータに対する認識率の確認を行います.\n", + "\n", + "`model.eval()`を適用することで,ネットワーク演算を評価モードへ変更します.\n", + "これにより,学習時と評価時で挙動が異なる演算(dropout等)を変更することが可能です.\n", + "また,`torch.no_grad()`を適用することで,学習時には必要になる勾配情報を保持することなく演算を行います." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "eDwQ-iJtjSaL" + }, + "outputs": [], + "source": [ + "# データローダーの準備\n", + "test_loader = torch.utils.data.DataLoader(test_data, batch_size=100, shuffle=False)\n", + "\n", + "# ネットワークを評価モードへ変更\n", + "model.eval()\n", + "\n", + "# 評価の実行\n", + "count = 0\n", + "with torch.no_grad():\n", + " for image, label in test_loader:\n", + "\n", + " if use_cuda:\n", + " image = image.cuda()\n", + " label = label.cuda()\n", + " \n", + " y = model(image)\n", + "\n", + " pred = torch.argmax(y, dim=1)\n", + " count += torch.sum(pred == label)\n", + "\n", + "print(\"test accuracy: {}\".format(count.item() / 10000.))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RO9gksBuj0qm" + }, + "source": [ + "## 課題\n", + "\n", + "\n", + "### 1. GPUを用いた場合とCPUを用いた場合の学習での計算時間の違いを確認しましょう.\n", + "\n", + "**ヒント**\n", + "\n", + "GPUとCPUの切り替えは「GPUの確認」というセル(本ページ上部)にある`use_cuda`の`True`, `False`を変更することで,切り替えが可能です.\n", + "\n", + "\n", + "### 2. ネットワークの構造を変更し,認識精度の変化を確認しましょう.\n", + "\n", + "**ヒント:ネットワーク構造の変更としては,次のようなものが考えられます.**\n", + "* 中間層のユニット数\n", + "* 層の数\n", + "* 活性化関数\n", + " * `nn.Tanh()`や`nn.ReLU()`, `nn.LeakyReLU()`などが考えられます.\n", + " * その他のPyTorchで使用できる活性化関数は[こちらページ](https://pytorch.org/docs/stable/nn.html#non-linear-activations-weighted-sum-nonlinearity)にまとめられています.\n", + "\n", + "※ ネットワーク構造を変更した際には,`torchsummary.summary(***)`を使用し,ネットワーク構造を変更した際のパラメータ数の変化を確認してみましょう.\n", + "\n", + "\n", + "### 3. 学習の設定を変更し,認識精度の変化を確認しましょう.\n", + "\n", + "**ヒント:プログラムの中で変更で切る設定は次のようなものが存在します.**\n", + "* ミニバッチサイズ\n", + "* 学習回数(Epoch数)\n", + "* 学習率\n", + "* 最適化手法\n", + " * `torch.optim.Adagrad()`や`torch.optim.Adam()`などが考えられます.\n", + " * PyTorchで使用できる最適化手法は[こちらのページ](https://pytorch.org/docs/stable/optim.html#algorithms)にまとめられています.\n", + "\n" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "collapsed_sections": [], + "name": "10_MNIST_CNN.ipynb", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/02_dnn_simple_pytorch/MNIST_MLP.ipynb b/02_dnn_simple_pytorch/MNIST_MLP.ipynb index 276bb3e..9dd01e9 100644 --- a/02_dnn_simple_pytorch/MNIST_MLP.ipynb +++ b/02_dnn_simple_pytorch/MNIST_MLP.ipynb @@ -1 +1,374 @@ -{"cells":[{"cell_type":"markdown","metadata":{"id":"_NZwOwd9KsKz"},"source":["# MLPによる画像認識(MNIST, PyTorch実装)\n","\n","---\n","## 目的\n","Pytorch実装による多層パーセプトロン(MLP)を用いてMNISTデータセットに対する文字認識を行う.\n","評価はConfusion Matrixにより各クラスの認識率を用いて行う."]},{"cell_type":"markdown","metadata":{"id":"H-0vTan1NYLI"},"source":["## 使用するデータセット\n","今回の文字認識では,MNIST Datasetを用いる.[MNIST Dataset](http://yann.lecun.com/exdb/mnist/)は,0から9までの数字が記述されている画像から構成されたデータセットである.MNIST Datasetの文字画像は,以下のように白黒で比較的認識しやすいように画像処理されている.\n","\n","![MNIST_sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143078/559938dc-9a99-d426-010b-e000bca0aac6.png)"]},{"cell_type":"markdown","metadata":{"id":"RsGSLNkYQmkG"},"source":["## モジュールのインポート\n","はじめに必要なモジュールをインポートする.\n","\n","今回は`torch` (PyTorch) をインポートする."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"SLeGt2xaNFOB"},"outputs":[],"source":["from time import time\n","import numpy as np\n","import torch\n","import torch.nn as nn\n","\n","import torchvision\n","import torchvision.transforms as transforms\n","\n","import torchsummary"]},{"cell_type":"markdown","metadata":{"id":"Ue60y-upamyo"},"source":["## データセットの読み込みと確認\n","学習データ(MNIST Dataset)を読み込みます.\n","\n","読み込んだ学習データのサイズを確認します.\n","学習データ数は6万枚,評価データは1万枚,1つのデータのサイズは28x28の786次元となっています."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"n7zpMk-4axYm"},"outputs":[],"source":["train_data = torchvision.datasets.MNIST(root=\"./\", train=True, transform=transforms.ToTensor(), download=True)\n","test_data = torchvision.datasets.MNIST(root=\"./\", train=False, transform=transforms.ToTensor(), download=True)\n","\n","print(type(train_data.data), type(train_data.targets))\n","print(type(test_data.data), type(test_data.targets))\n","print(train_data.data.size(), train_data.targets.size())\n","print(test_data.data.size(), test_data.targets.size())"]},{"cell_type":"markdown","metadata":{"id":"MN-KoymJbe25"},"source":["### MNISTデータセットの表示\n","\n","MNISTデータセットに含まれる画像を表示してみます.\n","ここでは,matplotlibを用いて複数の画像を表示させるプログラムを利用します."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"ehg-aZh8be9Z"},"outputs":[],"source":["import matplotlib.pyplot as plt\n","\n","cols = 10\n","\n","plt.clf()\n","fig = plt.figure(figsize=(14, 1.4))\n","for c in range(cols):\n"," ax = fig.add_subplot(1, cols, c + 1)\n"," ax.imshow(train_data[c][0].view(28, 28), cmap=plt.get_cmap('gray'))\n"," ax.set_axis_off()\n","plt.show()"]},{"cell_type":"markdown","metadata":{"id":"G418kZOgToXR"},"source":["## ネットワークモデルの定義\n","\n","ニューラルネットワークを定義します.\n","ここでは,入力層,中間層,出力層から構成される3層のニューラルネットワークとします.\n","\n","入力層のユニット数は入力データのサイズによります.\n","ここでは`28 x 28 = 786`とし,画像の画素値を1次元配列として並べ替えたデータを入力するように指定します.\n","\n","中間層と出力層のユニット数は引数として与え,それぞれ`n_hidden`,`n_out`とします.\n","PyTorchでは,`__init__`関数にこれらの引数を与えて各層を定義します.\n","各層はLinear関数としています.これは全結合層を意味しています.\n","また,`self.act`で活性化関数を指定します.ここでは,シグモイド関数を活性化関数として指定します.\n","\n","そして,`forward`関数で定義した層を接続して処理するように記述します.\n","`forward`関数の引数`x`は入力データを示しています.\n","それを`forward`関数で定義した`l1`という中間層および活性化関数`act`へ順番に入力します.\n","その出力を`h1`としています.\n","`h1`は出力層`l2`に与えられ,その出力を`h2`としています."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"8FJhkBJnTuPd"},"outputs":[],"source":["class MLP(nn.Module):\n"," def __init__(self, n_hidden, n_out):\n"," super().__init__()\n"," self.l1 = nn.Linear(28*28, n_hidden)\n"," self.l2 = nn.Linear(n_hidden, n_out)\n"," self.act = nn.Sigmoid()\n"," \n"," def forward(self, x):\n"," h1 = self.act(self.l1(x))\n"," h2 = self.l2(h1)\n"," return h2"]},{"cell_type":"markdown","metadata":{"id":"OF_0s3vBYBES"},"source":["## ネットワークの作成\n","上のプログラムで定義したネットワークを作成します.\n","\n","まず,中間層と出力層のユニット数を定義します.\n","ここでは,中間層のユニット数`hidden_num`を8,出力層のユニット数`out_num`をMNISTのクラス数に対応する10とします.\n","\n","各層のユニット数を上で定義した`MLP`クラスの引数として与え,ネットワークモデルを定義します.\n","\n","学習を行う際の最適化方法としてモーメンタムSGD(モーメンタム付き確率的勾配降下法)を利用します.\n","また,学習率を0.01,モーメンタムを0.9として引数に与えます.\n","\n","最後に,定義したネットワークの詳細情報を`torchsummary.summary()`関数を用いて表示します.第一引数に詳細を表示したいモデル,第二引数にネットワークへ入力されるデータのサイズを指定します.\n","これによって,ネットワークの構造を確認することができます."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"FTAUhy9qX4QU"},"outputs":[],"source":["# ユニット数の定義\n","hidden_num = 8\n","out_num = 10\n","\n","# ネットワークの作成\n","model = MLP(n_hidden=hidden_num, n_out=out_num)\n","\n","# 最適化手法の設定\n","optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)\n","\n","# 定義したモデルの情報を表示\n","torchsummary.summary(model, (1, 28*28), device='cpu')"]},{"cell_type":"markdown","metadata":{"id":"iGfy76HRYy4S"},"source":["## 学習\n","読み込んだMNISTデータセットと作成したネットワークを用いて,学習を行います.\n","\n","1回の誤差を算出するデータ数(ミニバッチサイズ)を100,学習エポック数を10とします.\n","\n","次にデータローダーを定義します.\n","データローダーでは,上で読み込んだデータセット(`train_data`)を用いて,for文で指定したミニバッチサイズでデータを読み込むオブジェクトを作成します.\n","この時,`shuffle=True`と設定することで,読み込むデータを毎回ランダムに指定します.\n","\n","次に,誤差関数を設定します.\n","今回は,分類問題をあつかうため,クロスエントロピー誤差を計算するための`CrossEntropyLoss`を`criterion`として定義します.\n","\n","学習を開始します.\n","\n","各更新において,学習用データと教師データをそれぞれ`image`と`label`とします.\n","学習モデルにimageを与えて各クラスの確率yを取得します.\n","各クラスの確率yと教師ラベルtとの誤差を`criterion`で算出します.\n","また,認識精度も算出します.\n","そして,誤差をbackward関数で逆伝播し,ネットワークの更新を行います."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"C0iI0zC-ZSY2"},"outputs":[],"source":["# ミニバッチサイズ・エポック数の設定\n","batch_size = 100\n","epoch_num = 10\n","\n","# データローダーの設定\n","train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True)\n","\n","# 誤差関数の設定\n","criterion = nn.CrossEntropyLoss()\n","\n","# ネットワークを学習モードへ変更\n","model.train()\n","\n","# 学習の実行\n","for epoch in range(1, epoch_num+1):\n"," sum_loss = 0.0\n"," count = 0\n"," \n"," for image, label in train_loader:\n"," image = image.view(image.size()[0], -1)\n"," y = model(image)\n"," \n"," loss = criterion(y, label)\n"," model.zero_grad()\n"," loss.backward()\n"," optimizer.step()\n"," \n"," sum_loss += loss.item()\n"," \n"," pred = torch.argmax(y, dim=1)\n"," count += torch.sum(pred == label)\n","\n"," print(\"epoch:{}, mean loss: {}, mean accuracy: {}\".format(epoch, sum_loss/600, count.item()/60000.))"]},{"cell_type":"markdown","metadata":{"id":"Ti1LytKAZYIO"},"source":["## テスト\n","\n","学習したネットワークを用いて,テストデータに対する認識率の確認を行います.\n","\n","`model.eval()`を適用することで,ネットワーク演算を評価モードへ変更します.\n","これにより,学習時と評価時で挙動が異なる演算(dropout等)を変更することが可能です.\n","また,`torch.no_grad()`を適用することで,学習時には必要になる勾配情報を保持することなく演算を行います."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"635DQ0ATYBJN"},"outputs":[],"source":["# データローダーの準備\n","test_loader = torch.utils.data.DataLoader(test_data, batch_size=100, shuffle=False)\n","\n","# ネットワークを評価モードへ変更\n","model.eval()\n","\n","# 評価の実行\n","count = 0\n","with torch.no_grad():\n"," for image, label in test_loader:\n"," image = image.view(image.size()[0], -1)\n"," y = model(image)\n","\n"," pred = torch.argmax(y, dim=1)\n"," count += torch.sum(pred == label)\n","\n","print(\"test accuracy: {}\".format(count.item() / 10000.))"]},{"cell_type":"markdown","metadata":{"id":"7YT4hqE3Ycpg"},"source":["## 課題\n","\n","### 1. ネットワークの構造を変更し,認識精度の変化を確認しましょう.\n","\n","**ヒント:ネットワーク構造の変更としては,次のようなものが考えられます.**\n","* 中間層のユニット数\n","* 層の数\n","* 活性化関数\n"," * `nn.Tanh()`や`nn.ReLU()`, `nn.LeakyReLU()`などが考えられます.\n"," * その他のPyTorchで使用できる活性化関数は[こちらページ](https://pytorch.org/docs/stable/nn.html#non-linear-activations-weighted-sum-nonlinearity)にまとめられています.\n","\n","※ ネットワーク構造を変更した際には,`torchsummary.summary()`関数を使用し,ネットワーク構造を変更した際のパラメータ数の変化を確認してみましょう.\n","\n"," \n"]}],"metadata":{"accelerator":"GPU","colab":{"collapsed_sections":[],"name":"09_MNIST_MLP.ipynb","provenance":[]},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.9"}},"nbformat":4,"nbformat_minor":0} +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "_NZwOwd9KsKz" + }, + "source": [ + "# MLPによる画像認識(MNIST, PyTorch実装)\n", + "\n", + "---\n", + "## 目的\n", + "Pytorch実装による多層パーセプトロン(MLP)を用いてMNISTデータセットに対する文字認識を行う.\n", + "評価はConfusion Matrixにより各クラスの認識率を用いて行う." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "H-0vTan1NYLI" + }, + "source": [ + "## 使用するデータセット\n", + "今回の文字認識では,MNIST Datasetを用いる.[MNIST Dataset](http://yann.lecun.com/exdb/mnist/)は,0から9までの数字が記述されている画像から構成されたデータセットである.MNIST Datasetの文字画像は,以下のように白黒で比較的認識しやすいように画像処理されている.\n", + "\n", + "![MNIST_sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143078/559938dc-9a99-d426-010b-e000bca0aac6.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RsGSLNkYQmkG" + }, + "source": [ + "## モジュールのインポート\n", + "はじめに必要なモジュールをインポートする.\n", + "\n", + "今回は`torch` (PyTorch) をインポートする." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "SLeGt2xaNFOB" + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import torch\n", + "import torch.nn as nn\n", + "\n", + "import torchvision\n", + "import torchvision.transforms as transforms\n", + "\n", + "import torchsummary" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Ue60y-upamyo" + }, + "source": [ + "## データセットの読み込みと確認\n", + "学習データ(MNIST Dataset)を読み込みます.\n", + "\n", + "読み込んだ学習データのサイズを確認します.\n", + "学習データ数は6万枚,評価データは1万枚,1つのデータのサイズは28x28の786次元となっています." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "n7zpMk-4axYm" + }, + "outputs": [], + "source": [ + "train_data = torchvision.datasets.MNIST(root=\"./\", train=True, transform=transforms.ToTensor(), download=True)\n", + "test_data = torchvision.datasets.MNIST(root=\"./\", train=False, transform=transforms.ToTensor(), download=True)\n", + "\n", + "print(type(train_data.data), type(train_data.targets))\n", + "print(type(test_data.data), type(test_data.targets))\n", + "print(train_data.data.size(), train_data.targets.size())\n", + "print(test_data.data.size(), test_data.targets.size())" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MN-KoymJbe25" + }, + "source": [ + "### MNISTデータセットの表示\n", + "\n", + "MNISTデータセットに含まれる画像を表示してみます.\n", + "ここでは,matplotlibを用いて複数の画像を表示させるプログラムを利用します." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ehg-aZh8be9Z" + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "cols = 10\n", + "\n", + "plt.clf()\n", + "fig = plt.figure(figsize=(14, 1.4))\n", + "for c in range(cols):\n", + " ax = fig.add_subplot(1, cols, c + 1)\n", + " ax.imshow(train_data[c][0].view(28, 28), cmap=plt.get_cmap('gray'))\n", + " ax.set_axis_off()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "G418kZOgToXR" + }, + "source": [ + "## ネットワークモデルの定義\n", + "\n", + "ニューラルネットワークを定義します.\n", + "ここでは,入力層,中間層,出力層から構成される3層のニューラルネットワークとします.\n", + "\n", + "入力層のユニット数は入力データのサイズによります.\n", + "ここでは`28 x 28 = 786`とし,画像の画素値を1次元配列として並べ替えたデータを入力するように指定します.\n", + "\n", + "中間層と出力層のユニット数は引数として与え,それぞれ`n_hidden`,`n_out`とします.\n", + "PyTorchでは,`__init__`関数にこれらの引数を与えて各層を定義します.\n", + "各層はLinear関数としています.これは全結合層を意味しています.\n", + "また,`self.act`で活性化関数を指定します.ここでは,シグモイド関数を活性化関数として指定します.\n", + "\n", + "そして,`forward`関数で定義した層を接続して処理するように記述します.\n", + "`forward`関数の引数`x`は入力データを示しています.\n", + "それを`forward`関数で定義した`l1`という中間層および活性化関数`act`へ順番に入力します.\n", + "その出力を`h1`としています.\n", + "`h1`は出力層`l2`に与えられ,その出力を`h2`としています." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "8FJhkBJnTuPd" + }, + "outputs": [], + "source": [ + "class MLP(nn.Module):\n", + " def __init__(self, n_hidden, n_out):\n", + " super().__init__()\n", + " self.l1 = nn.Linear(28*28, n_hidden)\n", + " self.l2 = nn.Linear(n_hidden, n_out)\n", + " self.act = nn.Sigmoid()\n", + " \n", + " def forward(self, x):\n", + " h1 = self.act(self.l1(x))\n", + " h2 = self.l2(h1)\n", + " return h2" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "OF_0s3vBYBES" + }, + "source": [ + "## ネットワークの作成\n", + "上のプログラムで定義したネットワークを作成します.\n", + "\n", + "まず,中間層と出力層のユニット数を定義します.\n", + "ここでは,中間層のユニット数`hidden_num`を8,出力層のユニット数`out_num`をMNISTのクラス数に対応する10とします.\n", + "\n", + "各層のユニット数を上で定義した`MLP`クラスの引数として与え,ネットワークモデルを定義します.\n", + "\n", + "学習を行う際の最適化方法としてモーメンタムSGD(モーメンタム付き確率的勾配降下法)を利用します.\n", + "また,学習率を0.01,モーメンタムを0.9として引数に与えます.\n", + "\n", + "最後に,定義したネットワークの詳細情報を`torchsummary.summary()`関数を用いて表示します.第一引数に詳細を表示したいモデル,第二引数にネットワークへ入力されるデータのサイズを指定します.\n", + "これによって,ネットワークの構造を確認することができます." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "FTAUhy9qX4QU" + }, + "outputs": [], + "source": [ + "# ユニット数の定義\n", + "hidden_num = 8\n", + "out_num = 10\n", + "\n", + "# ネットワークの作成\n", + "model = MLP(n_hidden=hidden_num, n_out=out_num)\n", + "\n", + "# 最適化手法の設定\n", + "optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)\n", + "\n", + "# 定義したモデルの情報を表示\n", + "torchsummary.summary(model, (1, 28*28), device='cpu')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "iGfy76HRYy4S" + }, + "source": [ + "## 学習\n", + "読み込んだMNISTデータセットと作成したネットワークを用いて,学習を行います.\n", + "\n", + "1回の誤差を算出するデータ数(ミニバッチサイズ)を100,学習エポック数を10とします.\n", + "\n", + "次にデータローダーを定義します.\n", + "データローダーでは,上で読み込んだデータセット(`train_data`)を用いて,for文で指定したミニバッチサイズでデータを読み込むオブジェクトを作成します.\n", + "この時,`shuffle=True`と設定することで,読み込むデータを毎回ランダムに指定します.\n", + "\n", + "次に,誤差関数を設定します.\n", + "今回は,分類問題をあつかうため,クロスエントロピー誤差を計算するための`CrossEntropyLoss`を`criterion`として定義します.\n", + "\n", + "学習を開始します.\n", + "\n", + "各更新において,学習用データと教師データをそれぞれ`image`と`label`とします.\n", + "学習モデルにimageを与えて各クラスの確率yを取得します.\n", + "各クラスの確率yと教師ラベルtとの誤差を`criterion`で算出します.\n", + "また,認識精度も算出します.\n", + "そして,誤差をbackward関数で逆伝播し,ネットワークの更新を行います." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "C0iI0zC-ZSY2" + }, + "outputs": [], + "source": [ + "# ミニバッチサイズ・エポック数の設定\n", + "batch_size = 100\n", + "epoch_num = 10\n", + "\n", + "# データローダーの設定\n", + "train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True)\n", + "\n", + "# 誤差関数の設定\n", + "criterion = nn.CrossEntropyLoss()\n", + "\n", + "# ネットワークを学習モードへ変更\n", + "model.train()\n", + "\n", + "# 学習の実行\n", + "for epoch in range(1, epoch_num+1):\n", + " sum_loss = 0.0\n", + " count = 0\n", + " \n", + " for image, label in train_loader:\n", + " image = image.view(image.size()[0], -1)\n", + " y = model(image)\n", + " \n", + " loss = criterion(y, label)\n", + " model.zero_grad()\n", + " loss.backward()\n", + " optimizer.step()\n", + " \n", + " sum_loss += loss.item()\n", + " \n", + " pred = torch.argmax(y, dim=1)\n", + " count += torch.sum(pred == label)\n", + "\n", + " print(\"epoch:{}, mean loss: {}, mean accuracy: {}\".format(epoch, sum_loss/600, count.item()/60000.))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Ti1LytKAZYIO" + }, + "source": [ + "## テスト\n", + "\n", + "学習したネットワークを用いて,テストデータに対する認識率の確認を行います.\n", + "\n", + "`model.eval()`を適用することで,ネットワーク演算を評価モードへ変更します.\n", + "これにより,学習時と評価時で挙動が異なる演算(dropout等)を変更することが可能です.\n", + "また,`torch.no_grad()`を適用することで,学習時には必要になる勾配情報を保持することなく演算を行います." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "635DQ0ATYBJN" + }, + "outputs": [], + "source": [ + "# データローダーの準備\n", + "test_loader = torch.utils.data.DataLoader(test_data, batch_size=100, shuffle=False)\n", + "\n", + "# ネットワークを評価モードへ変更\n", + "model.eval()\n", + "\n", + "# 評価の実行\n", + "count = 0\n", + "with torch.no_grad():\n", + " for image, label in test_loader:\n", + " image = image.view(image.size()[0], -1)\n", + " y = model(image)\n", + "\n", + " pred = torch.argmax(y, dim=1)\n", + " count += torch.sum(pred == label)\n", + "\n", + "print(\"test accuracy: {}\".format(count.item() / 10000.))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7YT4hqE3Ycpg" + }, + "source": [ + "## 課題\n", + "\n", + "### 1. ネットワークの構造を変更し,認識精度の変化を確認しましょう.\n", + "\n", + "**ヒント:ネットワーク構造の変更としては,次のようなものが考えられます.**\n", + "* 中間層のユニット数\n", + "* 層の数\n", + "* 活性化関数\n", + " * `nn.Tanh()`や`nn.ReLU()`, `nn.LeakyReLU()`などが考えられます.\n", + " * その他のPyTorchで使用できる活性化関数は[こちらページ](https://pytorch.org/docs/stable/nn.html#non-linear-activations-weighted-sum-nonlinearity)にまとめられています.\n", + "\n", + "※ ネットワーク構造を変更した際には,`torchsummary.summary()`関数を使用し,ネットワーク構造を変更した際のパラメータ数の変化を確認してみましょう.\n", + "\n", + " \n" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "collapsed_sections": [], + "name": "09_MNIST_MLP.ipynb", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/11_cnn_pytorch/03_resnet.ipynb b/11_cnn_pytorch/03_resnet.ipynb index 3a8d3e3..b4a9677 100644 --- a/11_cnn_pytorch/03_resnet.ipynb +++ b/11_cnn_pytorch/03_resnet.ipynb @@ -1 +1,585 @@ -{"cells":[{"cell_type":"markdown","metadata":{"id":"wJU2RPpSvlQT"},"source":["# CIFAR10を用いた物体認識(ResNet)\n","\n","\n","---\n","## 目的\n","\n","畳み込みニューラルネットワークのモデルとして,ResNetを用いて実験を行い,その構造を理解する."]},{"cell_type":"markdown","metadata":{"id":"5rQGfxWYK_4O"},"source":["## 準備\n","\n","### Google Colaboratoryの設定確認・変更\n","本チュートリアルではPyTorchを利用してニューラルネットワークの実装を確認,学習および評価を行います.\n","**GPUを用いて処理を行うために,上部のメニューバーの「ランタイム」→「ランタイムのタイプを変更」からハードウェアアクセラレータをGPUにしてください.**\n"]},{"cell_type":"markdown","metadata":{"id":"Xo4jjpmwvle1"},"source":["## モジュールのインポート\n","はじめに必要なモジュールをインポートする.\n","\n","### GPUの確認\n","GPUを使用した計算が可能かどうかを確認します.\n","\n","`GPU availability: True`と表示されれば,GPUを使用した計算をChainerで行うことが可能です.\n","Falseとなっている場合は,上記の「Google Colaboratoryの設定確認・変更」に記載している手順にしたがって,設定を変更した後に,モジュールのインポートから始めてください.\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"iCeaCulfvlao"},"outputs":[],"source":["# モジュールのインポート\n","from time import time\n","import numpy as np\n","import torch\n","import torch.nn as nn\n","\n","import torchvision\n","import torchvision.transforms as transforms\n","\n","import torchsummary\n","\n","# GPUの確認\n","use_cuda = torch.cuda.is_available()\n","print('Use CUDA:', use_cuda)"]},{"cell_type":"markdown","metadata":{"id":"ppjeW5MbysXC"},"source":["## データセットの読み込みと確認\n","\n","学習データ(CIFAR10データセット)を読み込みます."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"K_xx-TkVvls6"},"outputs":[],"source":["transform_train = transforms.Compose([transforms.RandomCrop(32, padding=1),\n"," transforms.RandomHorizontalFlip(),\n"," transforms.ToTensor()])\n","transform_test = transforms.Compose([transforms.ToTensor()])\n","\n","train_data = torchvision.datasets.CIFAR10(root=\"./\", train=True, transform=transform_train, download=True)\n","test_data = torchvision.datasets.CIFAR10(root=\"./\", train=False, transform=transform_test, download=True)"]},{"cell_type":"markdown","metadata":{"id":"xgDd3iX2zmSV"},"source":["## Residual Networks (ResNet)\n","Residual Networks (ResNet) は,2015年のILSVRCの優勝モデルです.\n","VGGNet[2]で示されたように,ネットワークを深くすることは表現能力を向上させ,認識精度を改善できます.\n","しかし,あまりにも深いネットワークは効率的な学習が困難でした.\n","\n","ResNetは,通常のネットワークのように,何かしらの処理ブロックによる変換$F(x)$を単純に次の層に渡していくのではなく,\n","スキップ構造によりその処理ブロックへの入力$x$をショートカットし, $H(x) = F(x)+x$を次の層に渡すようにしています.\n","スキップ構造により,誤差逆伝播時に勾配が消失しても,層をまたいで値を伝播することができます.\n","このショートカットを含めた処理単位をResidual blockと呼びます.\n","スキップ構造により非常に深いネットワークにおいても効率的に学習ができるようになりました.\n","Residual blockは,3×3 のフィルタサイズを持つ畳み込み層とBatch Normalization,ReLUから構成されています.\n","\n","深いネットワークでは,ある層のパラメータの更新によって,その次の層への入力の分布がバッチ毎に大きく変化してしまう内部共変量シフト (Internal covariate shift) が発生し,学習が効率的に進まない問題がありました.\n","Batch Normalizationは,内部共変量シフトを正規化し,なるべく各層が独立して学習を行えるようにすることで,学習を安定化・高速化する手法です.\n","ResNetでは,このBatch Normalizationとスキップ構造をResidual blockに組み込むことで非常に深いネットワークの学習を実現しています.\n","\n","\n","\n","### Basic BlockとBottleneck\n","Residual BlockにはBasic BlockとBottleneckと呼ばれる2種類のResidual blockの構造があります.\n","Basic Blockは3x3の畳み込みを二つ用いた構造となっており,比較的浅いResNet(ResNet-18や34など)使用されます.\n","一方,Bottleneckは1×1, 3×3, 1×1の3つの畳み込みを用いた構造となっており,一度チャンネル数を削減して畳み込みを行い,再度元のチャンネル数に戻すという処理を行っています.Bottleneck構造は深いResNet(ResNet-50, 101, 152など)に用いられます.\n","この2つの構造は同等の計算コストですが,BottleNeck型を採用することで精度を保持しながら計算効率化もできるというメリットがあります.\n","\n","![BasicBlockBottleNeck](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143078/a642b5c5-ec42-1705-72ab-2911bb82f97e.png)\n","\n","\n","### ImageNet版ResNetとCIFAR10/100版ResNetの違い\n","ImageNet版ResNetとCIFAR10/100版ResNetの違いについては,[こちら(ノートブック下部)](#note)に記述していますので,興味のある方はご確認ください.\n","\n","## ネットワークモデルの定義\n","\n","Residual Network (ResNet) を定義します.\n","\n","### Residual Block (Basic BlockとBottleneck) の定義\n","まずはじめに,2種類のResidual Block(BasicBlockとBottleneck)を定義します.\n","ここでは,`BasicBlock(nn.Module)`および`Bottleneck(nn.Module)`で,任意の形の構造(チャンネル数など)を定義できるクラスを作成します.\n","`__init__`関数の引数である,`inplanes`は入力される特徴マップのチャンネル数,`planes`はBottleNeck内の特徴マップのチャンネル数を指定します.\n","また,`stride`はResidual Block内の1つ目の3x3の畳み込み層のstrideの値です.\n","`downsample`は,Residual Blockに入力された特徴マップサイズと畳み込み演算後の特徴マップのサイズが異なる場合に元の特徴マップ (resudual) のサイズを調整するための演算を定義するための引数です(詳細は後述)."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["class BasicBlock(nn.Module):\n"," expansion = 1\n"," def __init__(self, inplanes, planes, stride=1, downsample=None):\n"," super().__init__()\n"," self.convs = nn.Sequential(\n"," nn.Conv2d(inplanes, planes, kernel_size=3, stride=stride, padding=1, bias=False),\n"," nn.BatchNorm2d(planes),\n"," nn.ReLU(inplace=True),\n"," nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False),\n"," nn.BatchNorm2d(planes),\n"," )\n"," self.downsample = downsample\n"," self.relu = nn.ReLU(inplace=True)\n"," self.stride = stride\n","\n"," def forward(self, x):\n"," residual = x\n"," out = self.convs(x)\n"," if self.downsample is not None:\n"," residual = self.downsample(x)\n"," out += residual\n"," out = self.relu(out)\n"," return out\n","\n","\n","class Bottleneck(nn.Module):\n"," expansion = 4\n"," def __init__(self, in_planes, planes, stride=1, downsample=None):\n"," super().__init__()\n"," self.convs = nn.Sequential(\n"," nn.Conv2d(in_planes, planes, kernel_size=1, bias=False),\n"," nn.BatchNorm2d(planes),\n"," nn.ReLU(inplace=True),\n"," nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False),\n"," nn.BatchNorm2d(planes),\n"," nn.ReLU(inplace=True),\n"," nn.Conv2d(planes, self.expansion * planes, kernel_size=1, bias=False),\n"," nn.BatchNorm2d(self.expansion * planes),\n"," )\n"," self.downsample = downsample\n"," self.relu = nn.ReLU(inplace=True)\n"," self.stride = stride\n","\n"," def forward(self, x):\n"," residual = x\n"," out = self.convs(x)\n"," if self.downsample is not None:\n"," residual = self.downsample(x)\n"," out += residual\n"," out = self.relu(out)\n"," return out"]},{"cell_type":"markdown","metadata":{},"source":["### ResNetの定義\n","上で定義したResidual Blockを活用して,ResNetを定義します.\n","ここでは,用いるResidual Blockの種類に応じて,`ResNetBasicBlock`と`ResNetBottleneck`の2種類のResNetを定義します.\n","\n","`__init__()`内の`depth`は構築したいResNetの層数を指定します(20, 44, 110など).\n","また,`n_class`はデータセットに応じて,クラス数を指定します.\n","`__init__()`内では,まずはじめに入力された`depth`がResNetを構築することができる数になっているかを確認します.\n","ResNetには,一番最初に単一の畳み込み層と出力層(全結合層)があります.\n","そのため,これら2つの層とResidual Block内の畳み込み層の数の合計が全体の層数となります.\n","また,Residual Blockは特徴マップのサイズに応じて,大きく3つのブロックから構成されています.\n","そのため,ResNetの層数は\n","$$(Res. Block内の畳み込みの数) * (1ブロックあたりのResidual Blockの数) * 3 + 2$$\n","となります.\n","そのため,BasicBlockを用いる際の層数は$6n+2$,Bottleneckを用いる際の層数$9n+2$となります($n$は1ブロックあたりのResidual Blockの数).\n","\n","`self._make_layer()`は,任意の形(層数)のResidual Blockからなる層を定義します.\n","Residual Blockに入力されるチャンネル数`planes`,BottleNeckの数`n_blocks`,畳み込みのストライド`stride`を指定します.\n","その後,それらの引数に従い,指定した数・パラメータのBasickBlockまたはBottleneckをリスト内に格納します.\n","最後に,`nn.Sequential`を用いて一塊の層として定義し,任意の数の層を持つresidual blockを定義します([nn.Sequentialについて](#note)).\n","\n","このとき,入力される特徴マップのサイズと畳み込み後(残差を計算する際)の特徴マップのサイズが異なる場合に,特徴マップのサイズを調整する`downsample`を定義します.\n","具体的には,特徴マップのサイズを調整することができるstrideで1x1の畳み込みを適用することで,マップのサイズを合わせます.\n","この`downsample`は,調整する必要がある場合に,`BasicBlock`および`Bottleneck`の引数として入力し,活用します."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"TNHnp_YczmY3"},"outputs":[],"source":["class ResNetBasicBlock(nn.Module):\n"," def __init__(self, depth, n_class=10):\n"," super().__init__()\n"," # 指定した深さ(畳み込みの層数)でネットワークを構築できるかを確認\n"," assert (depth - 2) % 6 == 0, 'When use basicblock, depth should be 6n+2 (e.g. 20, 32, 44).'\n"," n_blocks = (depth - 2) // 6 # 1ブロックあたりのBasic Blockの数を決定\n","\n"," self.inplanes = 16\n","\n"," self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1, bias=False)\n"," self.bn1 = nn.BatchNorm2d(16)\n"," self.relu = nn.ReLU(inplace=True)\n","\n"," self.layer1 = self._make_layer(16, n_blocks)\n"," self.layer2 = self._make_layer(32, n_blocks, stride=2)\n"," self.layer3 = self._make_layer(64, n_blocks, stride=2)\n","\n"," self.avgpool = nn.AvgPool2d(8)\n"," self.fc = nn.Linear(64 * BasicBlock.expansion, n_class)\n","\n"," def _make_layer(self, planes, n_blocks, stride=1):\n"," downsample = None\n"," if stride != 1 or self.inplanes != planes * BasicBlock.expansion:\n"," downsample = nn.Sequential(\n"," nn.Conv2d(self.inplanes, planes * BasicBlock.expansion, kernel_size=1, stride=stride, bias=False),\n"," nn.BatchNorm2d(planes * BasicBlock.expansion),\n"," )\n","\n"," layers = []\n"," layers.append(BasicBlock(self.inplanes, planes, stride, downsample))\n"," self.inplanes = planes * BasicBlock.expansion\n"," for _ in range(0, n_blocks - 1):\n"," layers.append(BasicBlock(self.inplanes, planes))\n","\n"," return nn.Sequential(*layers)\n","\n"," def forward(self, x):\n"," x = self.conv1(x)\n"," x = self.bn1(x)\n"," x = self.relu(x)\n","\n"," x = self.layer1(x)\n"," x = self.layer2(x)\n"," x = self.layer3(x)\n","\n"," x = self.avgpool(x)\n"," x = x.view(x.size(0), -1)\n"," x = self.fc(x)\n"," return x\n","\n","\n","class ResNetBottleneck(nn.Module):\n"," def __init__(self, depth, n_class=10):\n"," super().__init__()\n"," # 指定した深さ(畳み込みの層数)でネットワークを構築できるかを確認\n"," assert (depth - 2) % 9 == 0, 'When use Bottleneck, depth should be 9n+2 (e.g. 47, 56, 110, 1199).'\n"," n_blocks = (depth - 2) // 9 # 1ブロックあたりのBasic Blockの数を決定\n","\n"," self.inplanes = 16\n","\n"," self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1, bias=False)\n"," self.bn1 = nn.BatchNorm2d(16)\n"," self.relu = nn.ReLU(inplace=True)\n","\n"," self.layer1 = self._make_layer(16, n_blocks)\n"," self.layer2 = self._make_layer(32, n_blocks, stride=2)\n"," self.layer3 = self._make_layer(64, n_blocks, stride=2)\n","\n"," self.avgpool = nn.AvgPool2d(8)\n"," self.fc = nn.Linear(64 * Bottleneck.expansion, n_class)\n","\n"," def _make_layer(self, planes, n_blocks, stride=1):\n"," downsample = None\n"," if stride != 1 or self.inplanes != planes * Bottleneck.expansion:\n"," downsample = nn.Sequential(\n"," nn.Conv2d(self.inplanes, planes * Bottleneck.expansion, kernel_size=1, stride=stride, bias=False),\n"," nn.BatchNorm2d(planes * Bottleneck.expansion),\n"," )\n","\n"," layers = []\n"," layers.append(Bottleneck(self.inplanes, planes, stride, downsample))\n"," self.inplanes = planes * Bottleneck.expansion\n"," for _ in range(0, n_blocks - 1):\n"," layers.append(Bottleneck(self.inplanes, planes))\n","\n"," return nn.Sequential(*layers)\n","\n"," def forward(self, x):\n"," x = self.conv1(x)\n"," x = self.bn1(x)\n"," x = self.relu(x)\n","\n"," x = self.layer1(x)\n"," x = self.layer2(x)\n"," x = self.layer3(x)\n","\n"," x = self.avgpool(x)\n"," x = x.view(x.size(0), -1)\n"," x = self.fc(x)\n"," return x"]},{"cell_type":"markdown","metadata":{"id":"8Dwuvfouzmd7"},"source":["## ネットワークの作成\n","上のプログラムで定義したネットワークを作成します.\n","使用したいResdual Block構造の種類に応じて,層数を指定します.\n","\n","CNNクラスを呼び出して,ネットワークモデルを定義します. \n","また,GPUを使う場合(`use_cuda == True`)には,ネットワークモデルをGPUメモリ上に配置します. \n","これにより,GPUを用いた演算が可能となります.\n","\n","学習を行う際の最適化方法としてモーメンタムSGD (モーメンタム付き確率的勾配降下法) を利用します. \n","また,学習率を0.01,モーメンタムを0.9として引数に与えます.\n","\n","最後に,定義したネットワークの詳細情報を`torchsummary.summary()`関数を用いて表示します."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"23m79Eq-zmjl"},"outputs":[],"source":["# ResNetの層数を指定 (e.g. 20, 32, 44, 47, 56, 110, 1199)\n","n_layers = 20\n","\n","# ResNetを構築\n","model = ResNetBasicBlock(depth=n_layers, n_class=10) # BasicBlock構造を用いる場合\n","# model = ResNetBottleneck(depth=n_layers, n_class=10) # Bottleneck構造を用いる場合\n","\n","if use_cuda:\n"," model.cuda()\n","\n","optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)\n","\n","# モデルの情報を表示\n","torchsummary.summary(model, (3, 32, 32))"]},{"cell_type":"markdown","metadata":{"id":"MUNa9Xe79vAG"},"source":["## 学習\n","1回の誤差を算出するデータ数(ミニバッチサイズ)を128,学習エポック数を100とします.\n","CIFAR10の学習データサイズを取得し,1エポック内における更新回数を求めます.\n","学習モデルに`image`を与えて各クラスの確率yを取得します.各クラスの確率yと教師ラベル`label`との誤差をsoftmax coross entropy誤差関数で算出します.\n","また,認識精度も算出します.そして,誤差をbackward関数で逆伝播し,ネットワークの更新を行います."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"68RE3RTa76-W"},"outputs":[],"source":["# ミニバッチサイズ・エポック数の設定\n","batch_size = 128\n","epoch_num = 10\n","n_iter = len(train_data) / batch_size\n","\n","# データローダーの設定\n","train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True, num_workers=2)\n","\n","# 誤差関数の設定\n","criterion = nn.CrossEntropyLoss()\n","if use_cuda:\n"," criterion.cuda()\n","\n","# ネットワークを学習モードへ変更\n","model.train()\n","\n","start = time()\n","for epoch in range(1, epoch_num+1):\n"," sum_loss = 0.0\n"," count = 0\n"," \n"," for image, label in train_loader:\n"," if use_cuda:\n"," image = image.cuda()\n"," label = label.cuda()\n","\n"," y = model(image)\n"," loss = criterion(y, label)\n"," \n"," model.zero_grad()\n"," loss.backward()\n"," optimizer.step()\n"," \n"," sum_loss += loss.item()\n"," \n"," pred = torch.argmax(y, dim=1)\n"," count += torch.sum(pred == label)\n","\n"," print(\"epoch: {}, mean loss: {}, mean accuracy: {}, elapsed_time :{}\".format(epoch,\n"," sum_loss / n_iter,\n"," count.item() / len(train_data),\n"," time() - start))"]},{"cell_type":"markdown","metadata":{"id":"119eIrSmzmw6"},"source":["## テスト\n","学習したネットワークモデルを用いて評価を行います."]},{"cell_type":"code","execution_count":null,"metadata":{"id":"yoYVMRGLzm1I"},"outputs":[],"source":["# データローダーの準備\n","test_loader = torch.utils.data.DataLoader(test_data, batch_size=100, shuffle=False)\n","\n","# ネットワークを評価モードへ変更\n","model.eval()\n","\n","# 評価の実行\n","count = 0\n","with torch.no_grad():\n"," for image, label in test_loader:\n","\n"," if use_cuda:\n"," image = image.cuda()\n"," label = label.cuda()\n"," \n"," y = model(image)\n","\n"," pred = torch.argmax(y, dim=1)\n"," count += torch.sum(pred == label)\n","\n","print(\"test accuracy: {}\".format(count.item() / len(test_data)))"]},{"cell_type":"markdown","metadata":{"id":"_U8wsW37hUUI"},"source":["## 課題\n","\n","\n","### 1. 学習の設定を変更し,認識精度の変化を確認しましょう.\n","\n","**ヒント:プログラムの中で変更で切る設定は次のようなものが存在します.**\n","* ミニバッチサイズ\n","* 学習回数(Epoch数)\n","* 学習率\n","* 最適化手法\n"," * `torch.optim.Adagrad()`や`torch.optim.Adam()`などが考えられます.\n"," * PyTorchで使用できる最適化手法は[こちらのページ](https://pytorch.org/docs/stable/optim.html#algorithms)にまとめられています.\n","\n","\n","### 2. Data Augmentationの種類を追加して学習を行いましょう.\n","\n","**ヒント**\n",":学習時に使用するData Augmentationは`transform_train`の部分で変更できます.\n","\n","```python\n","transform_train = transforms.Compose([(この部分に使用するAugmentationの処理を追加) ,\n"," transforms.ToTensor(),\n"," transforms.LinearTransformation(Z, mean)])\n","```\n","\n","PyTorch(torchvision)で使用可能な変換は[こちらのページ](https://pytorch.org/docs/stable/torchvision/transforms.html)にまとめられています.\n"]},{"cell_type":"markdown","metadata":{},"source":["## 備考\n","\n","\n","### nn.Sequential()について\n","また,層を定義する`__init__`内では,`nn.Sequential()`という関数が用いられています.\n","これは,複数の層が格納されたリストを引数として受け取り,これらの層をひとまとめにしたオブジェクト(層)を定義する関数です・\n","下の関数では,畳み込みやBatchNormalizationがリスト内にされています.\n","`nn.Sequential`で定義した層`self.convs`では,実際に演算する際,すなわち`formward()`関数内では,`self.convs(x)`とすることで,リストに格納した演算をその順番通りに処理して返すことができます.\n","\n","### ImageNet版ResNetとCIFAR10/100版ResNetの違い\n","このノートブックで実装したResNetは,ResNetの元論文でCIFAR10/100の分類実験に使用された構造を定義しています.\n","ResNetには大きく,ImageNet版とCIFAR版があり,今日広く用いられているモデルはImageNet版となります.\n","ImageNetとCIFAR版の主な違いは以下の通りです.\n","\n","| | ImageNet | CIFAR |\n","|-------------------|----------|-------|\n","| 1層目の畳み込みのカーネルサイズ | 7x7 | 3x3 |\n","| 1層目の畳み込み後の特徴マップチャンネル数 | 64 | 16 |\n","| 複数のRes. Blockを統合したブロックの数 | 4 | 3 |\n","| 各ブロック内のRes. Blockの数 | 構造・ブロックによって異なる | `(層数 - 2) // 6` または `(層数 - 2) // 6` |\n","\n","ImageNet版のResNetは`torchvision.models`に実装されており,学習済みモデルなども公開されています([torchvisionのResNetリファレンスページ](https://pytorch.org/vision/stable/models.html#id10))."]}],"metadata":{"accelerator":"GPU","colab":{"collapsed_sections":[],"name":"13_cifar_resnet.ipynb","provenance":[]},"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.9"}},"nbformat":4,"nbformat_minor":0} +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "wJU2RPpSvlQT" + }, + "source": [ + "# CIFAR10を用いた物体認識(ResNet)\n", + "\n", + "\n", + "---\n", + "## 目的\n", + "\n", + "畳み込みニューラルネットワークのモデルとして,ResNetを用いて実験を行い,その構造を理解する." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "5rQGfxWYK_4O" + }, + "source": [ + "## 準備\n", + "\n", + "### Google Colaboratoryの設定確認・変更\n", + "本チュートリアルではPyTorchを利用してニューラルネットワークの実装を確認,学習および評価を行います.\n", + "**GPUを用いて処理を行うために,上部のメニューバーの「ランタイム」→「ランタイムのタイプを変更」からハードウェアアクセラレータをGPUにしてください.**\n", + "\n", + "## モジュールのインポート\n", + "はじめに必要なモジュールをインポートする.\n", + "\n", + "### GPUの確認\n", + "GPUを使用した計算が可能かどうかを確認します.\n", + "\n", + "`GPU availability: True`と表示されれば,GPUを使用した計算をChainerで行うことが可能です.\n", + "Falseとなっている場合は,上記の「Google Colaboratoryの設定確認・変更」に記載している手順にしたがって,設定を変更した後に,モジュールのインポートから始めてください." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "iCeaCulfvlao" + }, + "outputs": [], + "source": [ + "# モジュールのインポート\n", + "from time import time\n", + "import numpy as np\n", + "import torch\n", + "import torch.nn as nn\n", + "\n", + "import torchvision\n", + "import torchvision.transforms as transforms\n", + "\n", + "import torchsummary\n", + "\n", + "# GPUの確認\n", + "use_cuda = torch.cuda.is_available()\n", + "print('Use CUDA:', use_cuda)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ppjeW5MbysXC" + }, + "source": [ + "## データセットの読み込みと確認\n", + "\n", + "学習データ(CIFAR10データセット)を読み込みます." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "K_xx-TkVvls6" + }, + "outputs": [], + "source": [ + "transform_train = transforms.Compose([transforms.RandomCrop(32, padding=1),\n", + " transforms.RandomHorizontalFlip(),\n", + " transforms.ToTensor()])\n", + "transform_test = transforms.Compose([transforms.ToTensor()])\n", + "\n", + "train_data = torchvision.datasets.CIFAR10(root=\"./\", train=True, transform=transform_train, download=True)\n", + "test_data = torchvision.datasets.CIFAR10(root=\"./\", train=False, transform=transform_test, download=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xgDd3iX2zmSV" + }, + "source": [ + "## Residual Networks (ResNet)\n", + "Residual Networks (ResNet) は,2015年のILSVRCの優勝モデルです.\n", + "VGGNet[2]で示されたように,ネットワークを深くすることは表現能力を向上させ,認識精度を改善できます.\n", + "しかし,あまりにも深いネットワークは効率的な学習が困難でした.\n", + "\n", + "ResNetは,通常のネットワークのように,何かしらの処理ブロックによる変換$F(x)$を単純に次の層に渡していくのではなく,\n", + "スキップ構造によりその処理ブロックへの入力$x$をショートカットし, $H(x) = F(x)+x$を次の層に渡すようにしています.\n", + "スキップ構造により,誤差逆伝播時に勾配が消失しても,層をまたいで値を伝播することができます.\n", + "このショートカットを含めた処理単位をResidual blockと呼びます.\n", + "スキップ構造により非常に深いネットワークにおいても効率的に学習ができるようになりました.\n", + "Residual blockは,3×3 のフィルタサイズを持つ畳み込み層とBatch Normalization,ReLUから構成されています.\n", + "\n", + "深いネットワークでは,ある層のパラメータの更新によって,その次の層への入力の分布がバッチ毎に大きく変化してしまう内部共変量シフト (Internal covariate shift) が発生し,学習が効率的に進まない問題がありました.\n", + "Batch Normalizationは,内部共変量シフトを正規化し,なるべく各層が独立して学習を行えるようにすることで,学習を安定化・高速化する手法です.\n", + "ResNetでは,このBatch Normalizationとスキップ構造をResidual blockに組み込むことで非常に深いネットワークの学習を実現しています.\n", + "\n", + "\n", + "\n", + "### Basic BlockとBottleneck\n", + "Residual BlockにはBasic BlockとBottleneckと呼ばれる2種類のResidual blockの構造があります.\n", + "Basic Blockは3x3の畳み込みを二つ用いた構造となっており,比較的浅いResNet(ResNet-18や34など)使用されます.\n", + "一方,Bottleneckは1×1, 3×3, 1×1の3つの畳み込みを用いた構造となっており,一度チャンネル数を削減して畳み込みを行い,再度元のチャンネル数に戻すという処理を行っています.Bottleneck構造は深いResNet(ResNet-50, 101, 152など)に用いられます.\n", + "この2つの構造は同等の計算コストですが,BottleNeck型を採用することで精度を保持しながら計算効率化もできるというメリットがあります.\n", + "\n", + "![BasicBlockBottleNeck](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143078/a642b5c5-ec42-1705-72ab-2911bb82f97e.png)\n", + "\n", + "\n", + "### ImageNet版ResNetとCIFAR10/100版ResNetの違い\n", + "ImageNet版ResNetとCIFAR10/100版ResNetの違いについては,本ノートブックの下部に記述していますので,興味のある方はご確認ください.\n", + "\n", + "## ネットワークモデルの定義\n", + "\n", + "Residual Network (ResNet) を定義します.\n", + "\n", + "### Residual Block (Basic BlockとBottleneck) の定義\n", + "まずはじめに,2種類のResidual Block(BasicBlockとBottleneck)を定義します.\n", + "ここでは,`BasicBlock(nn.Module)`および`Bottleneck(nn.Module)`で,任意の形の構造(チャンネル数など)を定義できるクラスを作成します.\n", + "`__init__`関数の引数である,`inplanes`は入力される特徴マップのチャンネル数,`planes`はBottleNeck内の特徴マップのチャンネル数を指定します.\n", + "また,`stride`はResidual Block内の1つ目の3x3の畳み込み層のstrideの値です.\n", + "`downsample`は,Residual Blockに入力された特徴マップサイズと畳み込み演算後の特徴マップのサイズが異なる場合に元の特徴マップ (resudual) のサイズを調整するための演算を定義するための引数です(詳細は後述)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class BasicBlock(nn.Module):\n", + " expansion = 1\n", + " def __init__(self, inplanes, planes, stride=1, downsample=None):\n", + " super().__init__()\n", + " self.convs = nn.Sequential(\n", + " nn.Conv2d(inplanes, planes, kernel_size=3, stride=stride, padding=1, bias=False),\n", + " nn.BatchNorm2d(planes),\n", + " nn.ReLU(inplace=True),\n", + " nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False),\n", + " nn.BatchNorm2d(planes),\n", + " )\n", + " self.downsample = downsample\n", + " self.relu = nn.ReLU(inplace=True)\n", + " self.stride = stride\n", + "\n", + " def forward(self, x):\n", + " residual = x\n", + " out = self.convs(x)\n", + " if self.downsample is not None:\n", + " residual = self.downsample(x)\n", + " out += residual\n", + " out = self.relu(out)\n", + " return out\n", + "\n", + "\n", + "class Bottleneck(nn.Module):\n", + " expansion = 4\n", + " def __init__(self, in_planes, planes, stride=1, downsample=None):\n", + " super().__init__()\n", + " self.convs = nn.Sequential(\n", + " nn.Conv2d(in_planes, planes, kernel_size=1, bias=False),\n", + " nn.BatchNorm2d(planes),\n", + " nn.ReLU(inplace=True),\n", + " nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False),\n", + " nn.BatchNorm2d(planes),\n", + " nn.ReLU(inplace=True),\n", + " nn.Conv2d(planes, self.expansion * planes, kernel_size=1, bias=False),\n", + " nn.BatchNorm2d(self.expansion * planes),\n", + " )\n", + " self.downsample = downsample\n", + " self.relu = nn.ReLU(inplace=True)\n", + " self.stride = stride\n", + "\n", + " def forward(self, x):\n", + " residual = x\n", + " out = self.convs(x)\n", + " if self.downsample is not None:\n", + " residual = self.downsample(x)\n", + " out += residual\n", + " out = self.relu(out)\n", + " return out" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ResNetの定義\n", + "上で定義したResidual Blockを活用して,ResNetを定義します.\n", + "ここでは,使用するResidual Blockの種類に応じて,`ResNetBasicBlock`と`ResNetBottleneck`の2種類のResNetを定義します.\n", + "\n", + "`__init__()`内の`depth`は構築したいResNetの層数を指定します(20, 44, 110など).\n", + "また,`n_class`はデータセットに応じて,クラス数を指定します.\n", + "`__init__()`内では,まずはじめに入力された`depth`がResNetを構築することができる数になっているかを確認します.\n", + "ResNetには,一番最初に単一の畳み込み層と出力層(全結合層)があります.\n", + "そのため,これら2つの層とResidual Block内の畳み込み層の数の合計が全体の層数となります.\n", + "また,Residual Blockは特徴マップのサイズに応じて,大きく3つのブロックから構成されています.\n", + "そのため,ResNetの層数は\n", + "$$(Res. Block内の畳み込みの数) * (1ブロックあたりのResidual Blockの数) * 3 + 2$$\n", + "となります.\n", + "そのため,BasicBlockを用いる際の層数は$6n+2$,Bottleneckを用いる際の層数$9n+2$となります($n$は1ブロックあたりのResidual Blockの数).\n", + "\n", + "`self._make_layer()`は,任意の形(層数)のResidual Blockからなる層を定義します.\n", + "Residual Blockに入力されるチャンネル数`planes`,BottleNeckの数`n_blocks`,畳み込みのストライド`stride`を指定します.\n", + "その後,それらの引数に従い,指定した数・パラメータのBasickBlockまたはBottleneckをリスト内に格納します.\n", + "最後に,`nn.Sequential`を用いて一塊の層として定義し,任意の数の層を持つresidual blockを定義します([nn.Sequentialについて](#note)).\n", + "\n", + "このとき,入力される特徴マップのサイズと畳み込み後(残差を計算する際)の特徴マップのサイズが異なる場合に,特徴マップのサイズを調整する`downsample`を定義します.\n", + "具体的には,特徴マップのサイズを調整することができるstrideで1x1の畳み込みを適用することで,マップのサイズを合わせます.\n", + "この`downsample`は,調整する必要がある場合に,`BasicBlock`および`Bottleneck`の引数として入力し,活用します." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "TNHnp_YczmY3" + }, + "outputs": [], + "source": [ + "class ResNetBasicBlock(nn.Module):\n", + " def __init__(self, depth, n_class=10):\n", + " super().__init__()\n", + " # 指定した深さ(畳み込みの層数)でネットワークを構築できるかを確認\n", + " assert (depth - 2) % 6 == 0, 'When use basicblock, depth should be 6n+2 (e.g. 20, 32, 44).'\n", + " n_blocks = (depth - 2) // 6 # 1ブロックあたりのBasic Blockの数を決定\n", + "\n", + " self.inplanes = 16\n", + "\n", + " self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1, bias=False)\n", + " self.bn1 = nn.BatchNorm2d(16)\n", + " self.relu = nn.ReLU(inplace=True)\n", + "\n", + " self.layer1 = self._make_layer(16, n_blocks)\n", + " self.layer2 = self._make_layer(32, n_blocks, stride=2)\n", + " self.layer3 = self._make_layer(64, n_blocks, stride=2)\n", + "\n", + " self.avgpool = nn.AvgPool2d(8)\n", + " self.fc = nn.Linear(64 * BasicBlock.expansion, n_class)\n", + "\n", + " def _make_layer(self, planes, n_blocks, stride=1):\n", + " downsample = None\n", + " if stride != 1 or self.inplanes != planes * BasicBlock.expansion:\n", + " downsample = nn.Sequential(\n", + " nn.Conv2d(self.inplanes, planes * BasicBlock.expansion, kernel_size=1, stride=stride, bias=False),\n", + " nn.BatchNorm2d(planes * BasicBlock.expansion),\n", + " )\n", + "\n", + " layers = []\n", + " layers.append(BasicBlock(self.inplanes, planes, stride, downsample))\n", + " self.inplanes = planes * BasicBlock.expansion\n", + " for _ in range(0, n_blocks - 1):\n", + " layers.append(BasicBlock(self.inplanes, planes))\n", + "\n", + " return nn.Sequential(*layers)\n", + "\n", + " def forward(self, x):\n", + " x = self.conv1(x)\n", + " x = self.bn1(x)\n", + " x = self.relu(x)\n", + "\n", + " x = self.layer1(x)\n", + " x = self.layer2(x)\n", + " x = self.layer3(x)\n", + "\n", + " x = self.avgpool(x)\n", + " x = x.view(x.size(0), -1)\n", + " x = self.fc(x)\n", + " return x\n", + "\n", + "\n", + "class ResNetBottleneck(nn.Module):\n", + " def __init__(self, depth, n_class=10):\n", + " super().__init__()\n", + " # 指定した深さ(畳み込みの層数)でネットワークを構築できるかを確認\n", + " assert (depth - 2) % 9 == 0, 'When use Bottleneck, depth should be 9n+2 (e.g. 47, 56, 110, 1199).'\n", + " n_blocks = (depth - 2) // 9 # 1ブロックあたりのBasic Blockの数を決定\n", + "\n", + " self.inplanes = 16\n", + "\n", + " self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1, bias=False)\n", + " self.bn1 = nn.BatchNorm2d(16)\n", + " self.relu = nn.ReLU(inplace=True)\n", + "\n", + " self.layer1 = self._make_layer(16, n_blocks)\n", + " self.layer2 = self._make_layer(32, n_blocks, stride=2)\n", + " self.layer3 = self._make_layer(64, n_blocks, stride=2)\n", + "\n", + " self.avgpool = nn.AvgPool2d(8)\n", + " self.fc = nn.Linear(64 * Bottleneck.expansion, n_class)\n", + "\n", + " def _make_layer(self, planes, n_blocks, stride=1):\n", + " downsample = None\n", + " if stride != 1 or self.inplanes != planes * Bottleneck.expansion:\n", + " downsample = nn.Sequential(\n", + " nn.Conv2d(self.inplanes, planes * Bottleneck.expansion, kernel_size=1, stride=stride, bias=False),\n", + " nn.BatchNorm2d(planes * Bottleneck.expansion),\n", + " )\n", + "\n", + " layers = []\n", + " layers.append(Bottleneck(self.inplanes, planes, stride, downsample))\n", + " self.inplanes = planes * Bottleneck.expansion\n", + " for _ in range(0, n_blocks - 1):\n", + " layers.append(Bottleneck(self.inplanes, planes))\n", + "\n", + " return nn.Sequential(*layers)\n", + "\n", + " def forward(self, x):\n", + " x = self.conv1(x)\n", + " x = self.bn1(x)\n", + " x = self.relu(x)\n", + "\n", + " x = self.layer1(x)\n", + " x = self.layer2(x)\n", + " x = self.layer3(x)\n", + "\n", + " x = self.avgpool(x)\n", + " x = x.view(x.size(0), -1)\n", + " x = self.fc(x)\n", + " return x" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "8Dwuvfouzmd7" + }, + "source": [ + "## ネットワークの作成\n", + "上のプログラムで定義したネットワークを作成します.\n", + "使用したいResdual Block構造の種類に応じて,層数を指定します.\n", + "\n", + "CNNクラスを呼び出して,ネットワークモデルを定義します. \n", + "また,GPUを使う場合(`use_cuda == True`)には,ネットワークモデルをGPUメモリ上に配置します. \n", + "これにより,GPUを用いた演算が可能となります.\n", + "\n", + "学習を行う際の最適化方法としてモーメンタムSGD (モーメンタム付き確率的勾配降下法) を利用します. \n", + "また,学習率を0.01,モーメンタムを0.9として引数に与えます.\n", + "\n", + "最後に,定義したネットワークの詳細情報を`torchsummary.summary()`関数を用いて表示します." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "23m79Eq-zmjl" + }, + "outputs": [], + "source": [ + "# ResNetの層数を指定 (e.g. 20, 32, 44, 47, 56, 110, 1199)\n", + "n_layers = 20\n", + "\n", + "# ResNetを構築\n", + "model = ResNetBasicBlock(depth=n_layers, n_class=10) # BasicBlock構造を用いる場合\n", + "# model = ResNetBottleneck(depth=n_layers, n_class=10) # Bottleneck構造を用いる場合\n", + "\n", + "if use_cuda:\n", + " model.cuda()\n", + "\n", + "optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)\n", + "\n", + "# モデルの情報を表示\n", + "torchsummary.summary(model, (3, 32, 32))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MUNa9Xe79vAG" + }, + "source": [ + "## 学習\n", + "1回の誤差を算出するデータ数(ミニバッチサイズ)を128,学習エポック数を100とします.\n", + "CIFAR10の学習データサイズを取得し,1エポック内における更新回数を求めます.\n", + "学習モデルに`image`を与えて各クラスの確率yを取得します.各クラスの確率yと教師ラベル`label`との誤差をsoftmax coross entropy誤差関数で算出します.\n", + "また,認識精度も算出します.そして,誤差をbackward関数で逆伝播し,ネットワークの更新を行います." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "68RE3RTa76-W" + }, + "outputs": [], + "source": [ + "# ミニバッチサイズ・エポック数の設定\n", + "batch_size = 128\n", + "epoch_num = 10\n", + "n_iter = len(train_data) / batch_size\n", + "\n", + "# データローダーの設定\n", + "train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True, num_workers=2)\n", + "\n", + "# 誤差関数の設定\n", + "criterion = nn.CrossEntropyLoss()\n", + "if use_cuda:\n", + " criterion.cuda()\n", + "\n", + "# ネットワークを学習モードへ変更\n", + "model.train()\n", + "\n", + "start = time()\n", + "for epoch in range(1, epoch_num+1):\n", + " sum_loss = 0.0\n", + " count = 0\n", + " \n", + " for image, label in train_loader:\n", + " if use_cuda:\n", + " image = image.cuda()\n", + " label = label.cuda()\n", + "\n", + " y = model(image)\n", + " loss = criterion(y, label)\n", + " \n", + " model.zero_grad()\n", + " loss.backward()\n", + " optimizer.step()\n", + " \n", + " sum_loss += loss.item()\n", + " \n", + " pred = torch.argmax(y, dim=1)\n", + " count += torch.sum(pred == label)\n", + "\n", + " print(\"epoch: {}, mean loss: {}, mean accuracy: {}, elapsed_time :{}\".format(epoch,\n", + " sum_loss / n_iter,\n", + " count.item() / len(train_data),\n", + " time() - start))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "119eIrSmzmw6" + }, + "source": [ + "## テスト\n", + "学習したネットワークモデルを用いて評価を行います." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "yoYVMRGLzm1I" + }, + "outputs": [], + "source": [ + "# データローダーの準備\n", + "test_loader = torch.utils.data.DataLoader(test_data, batch_size=100, shuffle=False)\n", + "\n", + "# ネットワークを評価モードへ変更\n", + "model.eval()\n", + "\n", + "# 評価の実行\n", + "count = 0\n", + "with torch.no_grad():\n", + " for image, label in test_loader:\n", + "\n", + " if use_cuda:\n", + " image = image.cuda()\n", + " label = label.cuda()\n", + " \n", + " y = model(image)\n", + "\n", + " pred = torch.argmax(y, dim=1)\n", + " count += torch.sum(pred == label)\n", + "\n", + "print(\"test accuracy: {}\".format(count.item() / len(test_data)))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "_U8wsW37hUUI" + }, + "source": [ + "## 課題\n", + "\n", + "\n", + "### 1. 学習の設定を変更し,認識精度の変化を確認しましょう.\n", + "\n", + "**ヒント:プログラムの中で変更で切る設定は次のようなものが存在します.**\n", + "* ミニバッチサイズ\n", + "* 学習回数(Epoch数)\n", + "* 学習率\n", + "* 最適化手法\n", + " * `torch.optim.Adagrad()`や`torch.optim.Adam()`などが考えられます.\n", + " * PyTorchで使用できる最適化手法は[こちらのページ](https://pytorch.org/docs/stable/optim.html#algorithms)にまとめられています.\n", + "\n", + "\n", + "### 2. Data Augmentationの種類を追加して学習を行いましょう.\n", + "\n", + "**ヒント**\n", + ":学習時に使用するData Augmentationは`transform_train`の部分で変更できます.\n", + "\n", + "```python\n", + "transform_train = transforms.Compose([(この部分に使用するAugmentationの処理を追加) ,\n", + " transforms.ToTensor(),\n", + " transforms.LinearTransformation(Z, mean)])\n", + "```\n", + "\n", + "PyTorch(torchvision)で使用可能な変換は[こちらのページ](https://pytorch.org/docs/stable/torchvision/transforms.html)にまとめられています.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 備考\n", + "\n", + "\n", + "### nn.Sequential()について\n", + "また,層を定義する`__init__`内では,`nn.Sequential()`という関数が用いられています.\n", + "これは,複数の層が格納されたリストを引数として受け取り,これらの層をひとまとめにしたオブジェクト(層)を定義する関数です・\n", + "下の関数では,畳み込みやBatchNormalizationがリスト内にされています.\n", + "`nn.Sequential`で定義した層`self.convs`では,実際に演算する際,すなわち`formward()`関数内では,`self.convs(x)`とすることで,リストに格納した演算をその順番通りに処理して返すことができます.\n", + "\n", + "### ImageNet版ResNetとCIFAR10/100版ResNetの違い\n", + "このノートブックで実装したResNetは,ResNetの元論文でCIFAR10/100の分類実験に使用された構造を定義しています.\n", + "ResNetには大きく,ImageNet版とCIFAR版があり,今日広く用いられているモデルはImageNet版となります.\n", + "ImageNetとCIFAR版の主な違いは以下の通りです.\n", + "\n", + "| | ImageNet | CIFAR |\n", + "|-------------------|----------|-------|\n", + "| 1層目の畳み込みのカーネルサイズ | 7x7 | 3x3 |\n", + "| 1層目の畳み込み後の特徴マップチャンネル数 | 64 | 16 |\n", + "| 複数のRes. Blockを統合したブロックの数 | 4 | 3 |\n", + "| 各ブロック内のRes. Blockの数 | 構造・ブロックによって異なる | `(層数 - 2) // 6` (Basicblock) または `(層数 - 2) // 9` (BottleNeck) |\n", + "\n", + "ImageNet版のResNetは`torchvision.models`に実装されており,学習済みモデルなども公開されています([torchvisionのResNetリファレンスページ](https://pytorch.org/vision/stable/models.html#id10))." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "collapsed_sections": [], + "name": "13_cifar_resnet.ipynb", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}