マイブログ リスト

医療言語処理講座

2019年6月23日日曜日

Pyゼミ0.04 MSI P65をDual bootにしてみた

ノートPCデュアルブート

PS42が上手くいくならP65でも上手くいくはず?


Keyword: MSI P65-8RE-015JP、Dual Boot、Ubuntu18.04、Windows10

前回Pyゼミ0.03でMSI社のノートPC PS42のデュアルブートインストール記を書きました。
当初Ubuntu16.04でかなり苦戦しましたが,
Ubuntu18.04に変えるとかなりあっさりとインストールできたので,今回は上位機種のP65-8RE-015JPにトライしました。

P65-8RE-015JPのスペック
  • CPU   Core i7 8750H 2.2GHz/6コア
  • Memory 16GB
  • SSD   512GB
  • GPU   GeForce GTX 1060 Max-Q Design
  • Weight  1.88㎏   


前回PS42のマニュアルはできているので,この通り作業すればいいよと学生への課題として与えました。

しかし。。。



Ubuntu18.04がインストールできない


これまで何度かインストールに使ってきたインストール用USBでUbuntu18.04が上手く起動しません。
BIOSの設定を確認しながら何度か挑戦してもだめ。。。
18.04はあっさりあきらめてUbuntu16.04をインストールすることに変更。

週末の土曜日の午後。
PS42の上位機種なのに18.04がインストールできないことに違和感を持ちながら作業へ。

16.04のインストールが終了し,いつの持て順でCUIモードに移行してNVIDIAのドライバーのインストールへ
しかし,今度はまったくドライバーがインストールできない状況に。。。

何度か試している途中に,”do-release-upgrade”を使ってアップグレードするようにメッセージが表示されていることに気づきました。
ここで転機が。。。


これしかないね。


もう手がないな。

do-release-upgradeやってみようかということでメッセージの指示に従い。
まず,Ubuntu16.04のアップグレードとアップデートを実行。

$ sudo apt upgrade

$ sudo apt update

次に,Ubuntu18.04へのアップグレードを実行。

$ sudo do-release-upgrade

約1時間弱かかってダウンロード,途中すべてyesと入力。
その後,文字バケバケの何かを選択する画面が表示され,とりあえず選択されているディフォルトで続行。
(続行かどうかも文字が化けてて分からない)
とりあえず順調に進んでいるようなので,既に4時間以上だったので放置して帰ることに。


上手くいってる。


月曜日の朝。
P65の真っ黒な画面の最後に[y/n]と評されている。
何も考えずにyと入力。
再起動がはじまり,Ubuntu18.04起動完了。

土曜の作業はまったく上手くいっていないかったのでダメかもともっていましたが,インストールされていました。
よかった,よっかた。




残作業を無事終了


PS42へのUbuntu18.04のインストールに従い以下の作業を行いました。
参照:Pyゼミ0.03

1)NVIDIAドライバーのインストール

①CUIモードで作業するため,ランレベルを3に設定します。


$ sudo telinit 3

②Ctl + Alt + F1 でCUIモードに移行します。


③とりあえずアップデートを行います。

$ sudo apt-get upgrade
$ sudo apt-get update

④レポジトリの追加をします。

$ sudo add-apt-repository ppa:xorg-edgers/ppa -y
$ sudo apt-get update

⑤インストール可能なNVIDIAドライバの一覧を検索します。

$ apt-cache search "^nvidia-[0-9]{3}$"

⑥一覧の中から最新のドライバ「nvidia-390」を選び,インストールします。

$ sudo apt-get install -y nvidia-390

⑦インストールにしばらく時間がかかります。インストール終了後、再起動します。

$ reboot

⑧再起動後,ターミナルで次のコマンドを入力してNVIDIAのドライバーが正しくインストールされたことを確認します。


「GeForce GTX 106...」と表示され、GPUを認識していることが確認できます。

$ nvidia-smi
Tue Jun 18 16:42:35 2019       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 390.116                Driver Version: 390.116                   |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 106...  Off  | 00000000:01:00.0 Off |                  N/A |
| N/A   47C    P3    17W /  N/A |    218MiB /  6078MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0      1393      G   /usr/lib/xorg/Xorg                           121MiB |
|    0      1600      G   /usr/bin/gnome-shell                          95MiB |
+-----------------------------------------------------------------------------+


2)CUDAのインストール

①CUDA9.1をインストールします。

$ sudo apt install nvidia-cuda-toolkit nvidia-driver-390

②再起動後にCUDAが認識されているか確認します。

$ nvcc –V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2017 NVIDIA Corporation>
Built on Fri_Nov__3_21:07:56_CDT_2017
Cuda compilation tools, release 9.1, V9.1.85

3)cuDNNのインストール

NVIDIAからcudnnのファイルをダウンローします。
 ファイル名:cudnn-9.0-linux-x64-v7.4.2.24.tgz

①Ubuntuのホームディレクトリにファイルを置き,解凍します。cudaディレクトリが作成されます。

$ tar -zxf cudnn-9.0-linux-x64-v7.4.2.24.tgz

②cudaディレクトリに移動し,ファイルを所定の場所にコピーします。

$ cd cuda
$ sudo cp lib64/libcudnn* /usr/lib/cuda/lib64/
$ sudo cp include/cudnn.h /usr/lib/cuda/include/

③~/.bashrcファイルをテキストエディタで開き,最後の行に下記の内容を追加します。

$ gedit ~/.bashrc
ファイルの最後の行に次の2行を追加します。
# cuDNN
export PATH=/usr/lib/cuda/bin:${PATH}
export LD_LIBRARY_PATH=/usr/lib/cuda/lib64:${LD_LIBRARY_PATH}

4)Ancondaのインストール

Anaconda DistributionからLinuxのPython3.Xをダウンロードします。
ファイル名:Anaconda3-2019.03-Linux-x86_64.sh

①Anacondaをインストールします。

$ bash Anaconda3-2019.03-Linux-x86_64.sh
※インストール中すべてyesと入力

②pythonを起動して確認します。PythonとAnacondaのバージョンが表示されます。

$ python
Python 3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:09:58)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

5)Chainerのインストール

①CUDA9.1用のcupyのインストールします。

$ pip install cupy-cuda91

②chainerをインストールします

$ pip install chainer

③Pythonを起動して,Chainerとcupyが正しくインストールされたことを確認します。

$ python
Python 3.7.3 (default, Mar 27 2019, 22:11:17)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import chainer
>>> chainer.__version__
'5.4.0'
>>>
>>> import cupy
>>> cupy.__version__
'6.0.0'

6)Opencvのインストール

①pipのアップグレードとOpencvのインストールを行います。

$ pip install –upgrade pip
$ pip install opencv-python

②Pythonを起動し,Opnecvのバージョンを確認します。

$ python
Python 3.7.3 (default, Mar 27 2019, 22:11:17)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> cv2.__version__
'4.1.0'

7)Pydicomのインストール

①pipでpydicomをインストールします。

$ pip install  pydicom

②Pythonを起動してpydicomのバージョンを確認します。

$ python
Python 3.7.3 (default, Mar 27 2019, 22:11:17)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pydicom
>>> pydicom.__version__
'1.2.2'

8)DICOM Tool kitのインストール

①DICOM Tool Kitをインストールします。

$ sudo apt install dcmtk

②インストールを確認します。

・DICOM画像の解凍を行うdcmdjpegコマンドのバージョンを確認します。
$ dcmdjpeg --version
$dcmtk: dcmdjpeg v3.6.2 2017-07-14 $

dcmdjpeg: Decode JPEG-compressed DICOM file

Host type: Debian
Character encoding: UTF-8

External libraries used:
- ZLIB, Version 1.2.11
- IJG, Version 6b  27-Mar-1998 (modified)





ということで無事終了。







2019年6月12日水曜日

Pyゼミ0.02 Windows10でAIしたい

Windows10でSpiderやJupiter Noteを使ってAIやってみる。


Keyword:Windows10, WinPython, Spider、Jupiter Note、Chainer、Python

ゼミナールではUnix上で開発や実験をしていましが,やはり初学者が最初に取り掛かるには少し敷居が高いかもしれません。

本学の実習室のOSはWindowsなので,学内で簡単にPythonやAIを試せる環境を構築できないか検討していたところ,WinPythonを見つけました。

WinPythonはPythonによる開発環境を提供してくれます。もちろん必要なライブラリも簡単にインストールして利用可能です。すばらしい。

また,USBメモリに入れて持ち運ぶことができます。ますます素晴らしい。

私が試したところではWindows7ではPython開発環境のSpyderなど起動できませんでした。おすすめはWindows10になります。


ダウンロードしてUSBメモリに保存する


すでにAI開発環境をインストールしたWinPython(約880MB)をダウンロードします。


ダウンロードしたファイルを解凍します。

解凍後のフォルダをUSBメモリに保存して,他のPCに挿して実行できます。

とても便利です。

Winディレクトリに中に以下のディレクトリがあります。

  • 1.01readDicom~3.04dicomNN...Pythonのソースコードと関連するファイルが保存されています。
  • dcomdir1...テスト用のDICOM画像が保存されています。
  • dicmdir2...訓練・テスト用のDICOM画像が4つの部位(Head,Chest,AbdomenとLung)に分けて保存されています。
  • WinPython63bit3.7 / WPy-3710...Pythonの開発環境用のプログラムが保存されています。



USBメモリから起動する


WinPythonには人気のあるPython開発環境が実装されています。

WinPython63bit3.7 / WPy-3710の中には以下のようなプログラムがあります。



人気のSpiderやJupter NoteをつかってPythonで開発がすぐできます。


必要なライブラリをあらかじめインストールしました。


Pyゼミナールで使う以下のライブラリをインストールしてあります。
  • Anaconda(numpy 1.14.6,PyQt5など) 
  • Chainer 5.2.0
  • OpneCV 4.0.0
  • pydicom 1.2.2

Pyゼミナールで提供するソースコードを使ってすぐに実験可能です。


DICOM画像を表示してみる


PyQtで作成したGUIを使ってDICOM画像を表示します。

例えばSpyderを使って,2.01PyQt dicomDisplayのdicomDisplay.pyを開いて実行ボタンをクリックすると画像が表示できます。


DICOM画像の部位を分類のタスクを学習しテストする


頭部,胸部,腹部と肺野の4つの部位を学習し,分類のテストを行います。

dcmdir2ディレクトリ内の画像を使って,3.04dicomNNディレクトリ内のdicomNNtrain.pyを使って部位分類の訓練を行います。

学習したモデルを使い,dicomNNtest.pyでテストをおこないます。

テスト結果の平均正答率がコンソールに表示されます。

また,推論結果がresultディレクトリにJPEG画像が保存されます。

JPEG画像ファイル名は,推論した部位名から始まるファイル名で保存されています。


まとめ


Windows10でのAI環境をWinPython使って作成しました。

簡単な実験が行え,USBメモリに入れて持ち運べるので便利です。

私はオープンキャンパスに使っています。



2019年4月28日日曜日

Pyゼミ0.03 MSI ノートPC(PS42 8RC-009JP)にAI環境を構築する

MSI ノートPC、PS42 8RC-009JPをUbuntuとWindowsのDual BootでAI開発環境も入れる


Keyword:MSI PS42,Dual boot,Windows 10, Ubuntu18.04, Geforce GTX1050,Chainer


2019.06.17 NVIDIAドライバーのインストールの誤記を修正しました。



出張中にもGPU使って簡単な実験できる環境がほしいと、GPU搭載のノートPCを探していました。
価格は20万円、質量は2キロ超を覚悟していましたが、たまたまツクモに寄って見たノートPCがこれでした。



スペックは次の通りです。
 CPU  Corei7-8550U
 メモリ 8GB
 SSD  500GB
 GPU  Geforce GTX1050(4GB)
 質量  1.18Kg


GTX1050のメモリが4GB搭載しているのが嬉しいです。
価格が132,800円(税抜き)、コストパフォーマンス高いと思います。


Ubuntu 16.04のインストール失敗


これまでLinuxでのAI開発環境はUbuntu 16.04を基本にマニュアル化されていました。

今回はインストールに3週間ハマりました。問題がいろいろ発生しました。
・インストール時Wifiが認識しない。
 →有線ネットワークで対応しました(有線LAN→USB変換)
・GPUが認識しない
 →強引にNVIDIAのドライバーを入れてnvidia-smiコマンドでGPUを認識しているように見えましたが、GTX1050と表示されません。
 デバイスを正しく認識してくれませんでした。

結局16.04のインストールは諦めました。


Ubuntu 18.04のインストール成功


18.04を試してみるとなんの問題もなくWfiが接続できました。
また、16.04で試した内容が役に立ち、スキマ時間を使いながら2日でインストールとセットアップが完成しました。


Ubuntu18.04をPS42にインストールする

インストールの手順を紹介します。
1)Ubntuインストール用のUSBメモリの準備
2)ノートPCのSSDの領域の縮小
3)USBメモリからUbuntuのインストールとDual bootの確認
4)NVIDIAドライバのインストール
5)CUDAのインストール
6)cuDNNのインストール
7)Ancondaのインストール
8)Chainerのインストール
9)Opencvのインストール
10)Pydicomのインストール
11)DICOM Tool Kitのインストール
12)Windowsの時刻同期の設定変更
13)Dropboxのインストール(オプション)



1)Ubntuインストール用のUSBメモリの準備

USBメモリにUbuntu18.04のisoイメージを入れてインストール用USBを作成します。
それぞれのリンク先を参考にしてください。
①Ubuntu 16.04 LTSのisoイメージをここからダウンロードします。
https://www.ubuntulinux.jp/News/ubuntu1804-ja-remix
②ツールを使ってインストール用USBにisoイメージファイルを焼きます。
https://linuxfan.info/make-linux-install-usb-on-windows

2)ノートPCのSSDの領域の縮小

・500GBはCドライブとDドライブにそれぞれ半分ずつ割り振られています。
・Cドライブの約120GをUbuntu用に確保してインストールします。
・DドライブはWindowsのデータ領域(Dropboxで共有)に使用しました。


手順
①Windowsファイルマネージャーの「PC」のアイコンを右クリックして,「管理」を選択します。

②「記憶域/ディスクの管理」をクリックします。

③Cドライブを選択し,右クリックして「ボリュームの縮小」を選択します。

④「縮小する領域サイズ(MB)」に「120000」(MB)と入力して,「縮小」ボタンをクリックします。
→Cドライブに約120GBの「未割当」領域ができます。

⑤「コンピュータの管理」を閉じ,コンピュータをシャットダウンします。

3)USBメモリからUbuntuのインストール

3-1)BIOSの設定を行います。

①PC起動時にドラゴンが現れたら「DELETE」キーを連打し,BIOSを起動します。

②「Security / Secure Boot / Secure」を「Disabled」に変更します。

③ (1)「Boot / Fast Boot」を「Disabled」に,(2)「Boot / Boot mode select」を「UEFI」に,(3)「Boot / FIXED BOOT ORDER Priorities」を「USB Hard Drive」に変更します。

④「Save & Exit」で「Save Change and Reset」を選択して設定を保存して終了します。(Windowsも終了して電源オフの状態にします)

3-2)Ubuntuのインストール

①上記1)で作成したUSBメモリをPCに挿入して電源を入れると,Ubuntuの起動・インストールのメニューが現れます。「Try Ubuntu without Installing」を選択します。

②Ubuntuが起動,ネットワークの接続(Wifi)を確認し,ディスクトップ上の左上にあるインストールアイコン「Ubuntu 18.04LTSのインストール」をダブルクリックします。

③「ようこそ」画面で「日本語」を選択し,「続ける」ボタンをクリックします。

④「Ubuntuのインストール準備」で「Ubuntuのインストール中のアップデートをダウンロードする」と「グラフィック,Wifi機器,・・・」の2つにチェックを入れて,「続ける」ボタンをクリックします。

⑤「インストールの種類」は「それ以外」を選択し,「続ける」ボタンをクリックすします。

⑥上記2)④で作成した120GBの未割当領域(空き領域)をクリックし,左下の「+」をクリックして,パーティション編集を行います。
・はじめにスワップ領域8GB を作成ます。パラメータは以下の通りです。
    サイズ           8000 MB
    新しいパーティションタイプ 論理パーテション
     新しいパーティションの場所 この領域の終点
    利用方法          スワップ領域



・次に残りの空き領域をクリックして,「+」ボタンをクリックしてインストール領域を確保します。
    サイズ           残りのサイズ 127977MB
    新しいパーティションタイプ 基本パーテション
    新しいパーティションの場所 この領域の始点
    利用方法          ext4ジャーナリングファイルシステム
    マウントポイント       /



⑦「インストール」ボタンをクリックしてインストールを開始します。
⑧インストールが終了したら,PCをシャットダウンすて,USBメモリを抜きます。

3-3)デュアルブートの確認

①PC起動時にドラゴンが現れたら「DELETE」キーを打ちBIOSを起動します。

②「Boot / UEFI Hard Disk Drive BBS Priorities/Boot Option #1」を「ubuntu」に設定します。(こうしないとOSの選択メニューが現れないでWindowsが起動する)



③「Save & Exit / Save Changes and Reset」で設定を保存して終了します。

④OSの起動メニューが現れ,「Ubuntu」を選択するとUbuntuが起動する。正常にUbuntuが起動できることを確認します。

⑤Ubuntuを再起動すします。

⑥OSの起動メニューが現れ,「Windows Boot Manager」を選択し,正常にWindows10が起動することを確認します。

4)NVIDIAドライバのインストール

・NIVIDIAのGPUボードを認識して利用するために必須なドライバーです。

参考 「環境構築 - Ubuntu 16.04 に NVIDIA ドライバをインストールする方法について」

①Ubuntuを起動します。
CUIモードで作業するため,ランレベルを3に設定します。
$ sudo telinit 3

②Ctl + Alt + F1 でCUIモードに移行します。

③Unbuntuのアップデートを行います。
$ sudo apt-get upgrade
$ sudo apt-get update

④レポジトリの追加をします。
$ sudo add-apt-repository ppa:xorg-edgers/ppa -y
$ sudo apt-get update

⑤インストール可能なNVIDIAドライバの一覧を検索します。
$ apt-cache search "^nvidia-[0-9]{3}$"

⑥一覧の中から最新のドライバ「nvidia-390」(20190416現在)を選び,インストールします。
$ sudo apt-get install -y nvidia-390

⑦インストールにしばらく時間がかかります。インストール終了後、再起動します。
$ reboot

⑧再起動後,ターミナルで次のコマンドを入力してNVIDIAのドライバーが正しくインストールされたことを確認します。
$ nvidia-smi
Wed Apr 10 16:01:46 2019       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 390.116                Driver Version: 384.130                   |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 105...  Off  | 00000000:03:00.0 Off |                  N/A |
| N/A   53C    P0    N/A /  N/A |    332MiB /  4041MiB |      6%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0      1019      G   /usr/lib/xorg/Xorg                           195MiB |
|    0      1962      G   compiz                                       136MiB |
+-----------------------------------------------------------------------------+

5)CUDAのインストール

・CUDAはNVIDIA社の並列コンピューティングアーキテクチャです。

参考:Ubuntu18.04にNvidia Driver, CUDA, cuDNNを入れる

①CUDA9.1をインストールします。
$ sudo apt install nvidia-cuda-toolkit nvidia-driver-390


②再起動後にCUDAが認識されているか確認します。
$ nvcc –V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2017 NVIDIA Corporation>
Built on Fri_Nov__3_21:07:56_CDT_2017
Cuda compilation tools, release 9.1, V9.1.85

6)cuDNNのインストール

・NVIDIAが公開しているDeep Learning用のライブラリです。
NVIDIAからcudnnのファイルをダウンローします。
 ファイル名:cudnn-9.0-linux-x64-v7.4.2.24.tgz

①Ubuntuのホームディレクトリにファイルを置き,解凍します。cudaディレクトリが作成されます。
$ tar -zxf cudnn-9.0-linux-x64-v7.4.2.24.tgz

②cudaディレクトリに移動し,ファイルを所定の場所にコピーします。
$ cd cuda
$ sudo cp lib64/libcudnn* /usr/lib/cuda/lib64/
$ sudo cp include/cudnn.h /usr/lib/cuda/include/

③~/.bashrcファイルをテキストエディタで開き,最後の行に下記の内容を追加します。
$ gedit ~/.bashrc

ファイルの最後の行に次の2行を追加します。
# cuDNN
export PATH=/usr/lib/cuda/bin:${PATH}
export LD_LIBRARY_PATH=/usr/lib/cuda/lib64:${LD_LIBRARY_PATH}


7)Ancondaのインストール

・Python本体とよく利用されるライブラリをセットにしたPythonパッケージです。
・高度な数値計算やデータ分析、機械学習などが簡単にできるようになります。

参考 「Ubuntuを初めて触るド素人がAnacondaを入れてみた


Anaconda DistributionからLinuxのPython3.Xをダウンロードします。
ファイル名:Anaconda3-2019.03-Linux-x86_64.sh

①Anacondaをインストールします。
$ bash Anaconda3-2019.03-Linux-x86_64.sh

※インストール中すべてyesと入力

②pythonを起動して確認します。PythonとAnacondaのバージョンが表示されます。
$ python
Python 3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:09:58)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

③Anacondaのライブラリを確認します。リストが長いので結果は割愛します。
$ conda list


8)Chainerのインストール

・日本製の深層学習フレームワークです。
・ニューラルネットワークをPythonで柔軟に記述し、学習させることができます。
①CUDA9.0用のcupyのインストールします。
$ pip install cupy-cuda90

②chainerをインストールします
$ pip install chainer

③Pythonを起動して,Chainerとcupyが正しくインストールされたことを確認します。
$ python
Python 3.7.3 (default, Mar 27 2019, 22:11:17)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import chainer
>>> chainer.__version__
'5.4.0'
>>> 
>>> import cupy
>>> cupy.__version__
'5.4.0'


Version 5.4.0がインストールされています。


9)Opencvのインストール

・画像処理に関するさまざまな関数を備えたライブラリです。

①pipのアップグレードとOpencvのインストールを行います。
$ pip install –upgrade pip
$ pip install opencv-python

②Pythonを起動し,Opnecvのバージョンを確認します。
$ python
Python 3.7.3 (default, Mar 27 2019, 22:11:17)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> cv2.__version__
'4.1.0'


10)Pydicomのインストール

PydicomはDICOM画像を扱うのに必須のライブラリです。

①pipでpydicomをインストールします。
$ pip install  pydicom

②Pythonを起動してpydicomのバージョンを確認します。
$ python
Python 3.7.3 (default, Mar 27 2019, 22:11:17)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pydicom
>>> pydicom.__version__
'1.2.2'


11)DICOM Tool kitのインストール

DICOM Tool kitはJPEG圧縮されたDICOM画像を解凍するときなどに使うDICOM関連のツール類です。

①DICOM Tool Kitをインストールします。
$ sudo apt install dcmtk

②インストールを確認します。
・DICOM画像の解凍を行うdcmdjpegコマンドのバージョンを確認します。
$ dcmdjpeg --version
$dcmtk: dcmdjpeg v3.6.2 2017-07-14 $

dcmdjpeg: Decode JPEG-compressed DICOM file

Host type: Debian
Character encoding: UTF-8

External libraries used:
- ZLIB, Version 1.2.11
- IJG, Version 6b  27-Mar-1998 (modified)

表示できたらOKです。

12)Windowsの時刻同期の設定変更

・Linuxとのデュアルブートの環境では,Linux終了後Windowsを起動すると時間がずれる現象が発生します(日本の場合は9時間ずれます)。詳細は参考をご覧ください。

参考:【Windows10】起動時にWindows Time Serviceを起動させる。


①Windows Timeサービスを「自動 (遅延開始)」に設定します。
・ディスクトップの左下の[スタート]をクリックしてプログラムの中から[Windows管理ツール/サービス]を選択します。
・一覧の中の[Windows Time]を右クリックして[プロパティ]を選択します。



・スタートアップの種類を[自動(遅延開始)]を選択し,[適用]をクリックして終了します。



②タスクスケジューラの「SynchronizeTime」タスクを「無効」にします。
・ディスクトップの左下の[スタート]をクリックしてプログラムの中から[Windows管理ツール/タスクスケジューラ]を選択します。
・[タスクスケジューラライブラリ / Microsoft / Windows / Time Synchronization]を選択します。


・[SynchronizeTime]を右クリックして[無効]を選択して終了します。

これで,Linux終了後にWindowsを起動しても時刻がずれることはなくなるはずです。これまで手動での時刻の修正は必要なくなります。

13)Dropboxのインストール(オプション)

開発環境を他のPC(main PCなど)と共有するのにとても便利なツールです。無償で2GB使えます。WindowsはもちろんLinuxやAndroidでも利用可能です。
ここでは,UbuntuにDropboxをインストールして利用する方法を紹介します。

①Dropboxのアカウントを作成する
 あらかじめDropboxのアカウントを作成しておきましょう

参考:Dropbox Basic (無料版) で使える容量は?プラン別の容量と無料で容量を増やす方法

②Dropboxをインストールする
 ターミナルから以下のコマンドを入力します。
$ cd ~ && wget -O - "https://www.dropbox.com/download?plat=lnx.x86_64" | tar xzf -

③Dropboxを起動する。
 ターミナルから以下のコマンドを入力します。
$ ~/.dropbox-dist/dropboxd

【注意】他のPCにあるファイルを編集状態で,自PCで同じファイルを起動し編集,保存すると不整合が発生します。Dropboxは「競合ファイル」として自動的に新しいファイルを別名で他のPCに保存しますが,基本的に同期がかかっているディレクトリのファイルは作業終了後、必ず閉じるように注意しましょう。


おわりに


今回,MSIのノートPCをWindowsとLinuxのDual boot環境を作成しました。
当初Ubuntu16.04で試みていましたが,過去の成功がじゃまして無駄な時間を使ってしまいました。
しかし,失敗の中にも効率的にインストール方法が見つけることができ,そのおかげでUbuntu18.04ではスムースにインストールすることができました。

Googleが提供するCollaboratoryなどクラウドでのAI開発環境など様々な環境がありますが,PCの中のローカルな環境にAIを構築することにこだわるのは医療情報を扱う場合,閉ざされた環境で実験,開発する必要があるからです。

今回,GPU搭載の1.18㎏のノートPCにAI環境を構築できたことで,いつでも開発・テストができる環境を得たことは大きいです。(もちろんデータは自分のサンプル画像しか入っていませんが)

2019年2月25日月曜日

Pyゼミ3.07 ChainerでGPUを使って学習してみる

GPUによる高速な学習を試してみる。


keyword: Chainer, GPU, cupy


大量のデータを使って学習するにはGPU(Graphics Processing Uni)は必須です。
CPUの演算より約10倍も早くなります。
いろいろパラメータやデータなどを変えて実験するにはGPUはなくてはならないハードウェアです。

これまで作成したAIのプログラム(Pyゼミ3.05など)の修正点について説明します。
GPUを使うための環境の構築については説明しません。
環境の構築はインターネット上にたくさんの情報がりますので,OS,CUDAやその他のドライバーなどのバージョンを確認しながら構築してください。


前回Pyゼミ3.05のプログラムをGPU対応にする


ChainerのプログラムをGPU対応するには次の3点の修正を加えます。

  1. numpyの代わりにcupyを使う
  2. modelをGPUに対応させる
  3. UpdaterをGPUに対応させる

これだけです。それぞれについて具体的に説明します。

1. numpyの代わりにcupyを使う

cupyはGPUで演算させるためのライブラリで,行列演算などを効率的に行うnumpyと互換性を持っています。
つまり,numpyで記述しているプログラムをcupyに書き換えればよいわけです。

numpyは次のようにnpという名前でインポートしています。
    import numpy as np

また,GPUの使用,未使用をスイッチするために,uses_device変数を初期化します。
未使用のときはー1を,使用時は0以上の値(通常は0)を設定します。
    uses_device = 0     # GPU 0〜、CPU -1

さて,GPU使用時にnumpyをcupyに書き換える方法ですが,
次のように,GPU使用時(>=0)に,cupyをcpとしてインポートし,
npにcpを代入するだけで,プログラムはGPU用に書き換えられました。
    if uses_device >= 0:
        import cupy as cp
        np = cp


2. modelをGPUに対応させる

ニューラルネットワークのモデルをいつも通りに生成します。
    model = Alexnet(nlbl)

このモデルをGPUに対応させます。
GPU使用時(>=0)には以下の行を実行し,モデルがGPUを使って演算できるようにします。
    if uses_device >= 0:
      chainer.cuda.get_device_from_id(0).use()
      chainer.cuda.check_cuda_available()
      model.to_gpu()


3. UpdaterをGPUに対応させる


パラメータ更新を担当するUpdaterをGPUに対応させます。
引数にdevice=uses_deviceを追加するだけです。
    updater  = training.StandardUpdater(t_iterator, self.opt, device=uses_device) 

また,Pyゼミ3.05のプログラム中のEvaluatorも同様に引数にdevice=uses_deviceを追加します。
    trainer.extend(extensions.Evaluator(v_iterator, self.model, device=uses_device))

以上,3つの追加修正によりGPUに対応したプログラムになります。


プログラムを入力して実行してみよう


このプログラムはPy3.05が基本です。これに3つの修正が加わっています。

dicomAlexnetTrainGPU.py

import os, sys
import numpy as np
import cv2
import chainer
from chainer import Function, report, training, utils, Variable
from chainer import datasets, iterators, optimizers, serializers
from chainer import Link, Chain, ChainList
import chainer.functions as F
import chainer.links as L
from chainer.datasets import tuple_dataset
from chainer.training import extensions
from chainer.datasets import split_dataset_random #new
import pydicom

uses_device = 0     # GPU 0〜、CPU -1

if uses_device >= 0:
    import cupy as cp
    np = cp

def constractTrainData( fname , resz ):
    image = []
    label = []
    for i, line in enumerate(open(fname, 'r')):
        data = line.split(",")        
        print("Train",i, data[0], "Label#",data[1])
        img = readDicom2png(data[0], "tmp.png")
        if resz > 0:                        
            img = cv2.resize(img,(resz, resz))      
        img = img.astype(np.float32)  
        img = (img - 128)/128           #画素値0-255を-1.0から+1.0に変換
        image.append([img])
              
        t = np.array(int(data[1]), dtype=np.int32)
        label.append(t)
              
    train = tuple_dataset.TupleDataset(image, label)      
    return train
  
def readDicom2png(dcmfnm, tmpfnm):
    ds  = pydicom.read_file(dcmfnm)    
    wc  = ds.WindowCenter              
    ww  = ds.WindowWidth                        
    img = ds.pixel_array                        
    max = wc + ww / 2                    
    min = wc - ww / 2    
    img = 255 * (img - min)/(max - min)  
    img[img > 255] = 255                
    img[img < 0  ] = 0                  
    img = img.astype(np.uint8)
    cv2.imwrite(tmpfnm, img)
    return img  

def getLabelName(fname):
    lblnm = []
    for line in open(fname, 'r'):
        data = line.split(",")
        lblnm.append(data[1].strip())
        #lblnm[int(data[0])]= data[1]  
    print("LabelName:",lblnm)            
    return len(lblnm), lblnm
      
class Alexnet(Chain):  ### AlexNet ###
    def __init__(self, n_label, n_channel=1):
        super(Alexnet, self).__init__(
            conv1 = L.Convolution2D(n_channel, 96, 11, stride=4),
            conv2 = L.Convolution2D(96, 256, 5, pad=2),
            conv3 = L.Convolution2D(256, 384, 3, pad=1),
            conv4 = L.Convolution2D(384, 384, 3, pad=1),
            conv5 = L.Convolution2D(384, 256, 3, pad=1),
            fc6 = L.Linear(None, 4096),
            fc7 = L.Linear(4096, 4096),
            fc8 = L.Linear(4096, n_label),
        )

    def __call__(self, x):
        h = F.max_pooling_2d(F.local_response_normalization(
            F.relu(self.conv1(x))), 3, stride=2)
        h = F.max_pooling_2d(F.local_response_normalization(
            F.relu(self.conv2(h))), 3, stride=2)
        h = F.relu(self.conv3(h))
        h = F.relu(self.conv4(h))
        h = F.max_pooling_2d(F.relu(self.conv5(h)), 2, stride=2)
        h = F.dropout(F.relu(self.fc6(h)))
        h = F.dropout(F.relu(self.fc7(h)))
        return self.fc8(h)

class Classifier():
    def __init__(self, mdlnm, nlbl):
        model = Alexnet(nlbl)
        
        if uses_device >= 0:  # GPUを使う
            chainer.cuda.get_device_from_id(0).use()
            chainer.cuda.check_cuda_available()
            model.to_gpu() # GPU用データ形式に変換

        self.model = L.Classifier(model,lossfun=F.softmax_cross_entropy)
        self.opt   = optimizers.Adam()
        self.opt.setup(self.model)      
        self.modelName = mdlnm      
          
    def doTrain(self, nLbl, train, batch_size, n_epoch, modelName):
        trn, vld = split_dataset_random(train, int(len(train)*0.8), seed=0)      
        t_iterator = iterators.SerialIterator(trn, batch_size, shuffle=True)
        v_iterator = iterators.SerialIterator(vld, batch_size, repeat=False, shuffle=False)
        updater  = training.StandardUpdater(t_iterator, self.opt, device=uses_device) #GPU
        trainer  = training.Trainer(updater, (n_epoch, 'epoch'), out='result')

        trainer.extend(extensions.Evaluator(v_iterator, self.model, device=uses_device))  #GPU            
        trainer.extend(extensions.LogReport())
        trainer.extend(extensions.PrintReport(['epoch', 'main/loss', 'main/accuracy', 'validation/main/accuracy']))
        trainer.extend(extensions.ProgressBar())
        trainer.extend(extensions.PlotReport(['main/loss', 'validation/main/loss'], x_key='epoch', file_name='loss.png'))
        trainer.extend(extensions.PlotReport(['main/accuracy', 'validation/main/accuracy'], x_key='epoch', file_name='accuracy.png'))
      
        trainer.run()
              
        print("学習モデル",self.modelName,"を保存します... .   .")
        chainer.serializers.save_hdf5( self.modelName, self.model )
        print("\t... .  .学習モデルを保存しました")

if __name__=='__main__':
    trainfnm = "./TrainTestList/TrainData.0.3.csv"
    labelfnm = "./TrainTestList/labelName.csv"
    resize   = 128       #画像リサイズ 0のときそのままの画素サイズ
    modelnm  = "Alexnet.0.3.hdf5"  #モデル保存名  
    batch_size = 10  # バッチサイズ
    n_epoch    = 30  # エポック数
  
    nLbl, lblName = getLabelName(labelfnm)
    train = constractTrainData(trainfnm, resize)
    print("\ntrain\n")
    print("画像データ数:",len(train),"画像/ラベル:",len(train[0]))
    print("画像Chanel数:",len(train[0][0]),"高さ:",len(train[0][0][0]),"幅:",len(train[0][0][0][0]))
  
    dl = Classifier(modelnm, nLbl)
    dl.doTrain(nLbl, train, batch_size, n_epoch, modelnm)



実行結果


プログラムは,実効時間を計測するためtimeコマンドを付けて実行します。

プログラムはラベルファイルを読み込みラベル名を表示し,DICOM画像を読み込みます。

学習がはじまり30回のエポック終了後に実行時間が表示されます。
timeコマンドは,プログラムの起動から終了までの時間をrealの値で示しています。
わたしのGefoece GTX 1060では0m29.636sでした。約30秒でした。
(実行環境:OS Ubuntu 16.04 LTS,CPU Core i5-6400 2.7GHz, メモリ 8GB,GPU GeForce GTX 1060 6GB)

$ time␣python␣dicomAlexnetTrainGPU.py⏎
LabelName: ['Lung', 'Abdomen', 'Chest', 'Head']
Train 0 ../dcmdir2/Lung/W5305890 Label# 0
Train 1 ../dcmdir2/Lung/K5303593 Label# 0
Train 2 ../dcmdir2/Lung/R5305640 Label# 0
Train 5 ../dcmdir2/Lung/D5304468 Label# 0
     ・・・・・
Train 139 ../dcmdir2/Head/G5221187 Label# 3
Train 140 ../dcmdir2/Head/Q5221671 Label# 3
Train 141 ../dcmdir2/Head/E5221093 Label# 3

train

画像データ数: 142 画像/ラベル: 2
画像Chanel数: 1 高さ: 128 幅: 128
epoch       main/loss   main/accuracy  validation/main/accuracy
1           1.8203      0.283333       0.481481                
2           1.06403     0.545455       0.725926                
3           0.741141    0.581818       0.759259                
4           0.735347    0.7            0.725926                
5           0.639062    0.581818       0.825926
         ・・・・・
28          0.00639354  1              1                        
29          0.0178032   0.990909       1                        
30          0.0548964   0.981818       0.933333                
学習モデル Alexnet.0.3.hdf5 を保存します... .   .
 ... .  .学習モデルを保存しました

real 0m29.636s
user 0m31.316s
sys 0m17.240s


GPU使用・未使用の演算時間の比較


GPU使用時では約30秒の実行時間でしたが,
uses_device = -1 にして,GPUを使用しないで実行時間を計測すると
6分18秒でした。
その差は約12倍です。
明らかに実行速度が向上しています。


real 6m18.688s
user 9m16.004s
sys 8m6.400s



GPUの稼働状況はNVIDIAのツールで確認できます。
引数はハイフン,エルです。

$ nvidia-smi␣-l⏎




中段の右にGPU-Utilが96%と表示され,GPUがフル稼働しているのが分かります。


まとめ


今回はGPUに対応したプログラムの作り方について説明しました。
次の3つについて修正するだけで簡単にGPUが使えることがわかりました。
  1. numpyの代わりにcupyを使う
  2. modelをGPUに対応させる
  3. UpdaterをGPUに対応させる
まだ,GPU未導入の方はぜひ導入を検討するとよいでしょう。

また,使っていない古い読影端末やワークステーションにGPU搭載しているものがあります。
とりあえずこれを流用する方法もあると思います。

2019年2月23日土曜日

Pyゼミ3.06 ChainerのTrainer Extensionを使ってみる

Chainerを使った学習(訓練)時の進捗状況を見てみよう


Keyword:DICOM,Chainer,Trainer, Extension,Extend

これまでPyゼミ3.04,3.05ではニューラルネットワークについて説明してきました。
そして学習時に精度と損失の変化を観察して,学習の進捗状況を知る方法を知っています。

本章では学習の進捗状況をグラフ化したり,学習を検証したりする方法を説明します。



学習の進捗を見る


Pytゼミ3.05など既に学習の進捗を見るTrainerのextend関数を使ってきました。
既に使ってきた3つの関数を見てみましょう。

LogReportは各エポックごとに損失や精度などをログに記録します。
ログファイルはresultディレクトリの中に作成されます。
    trainer.extend(extensions.LogReport())

PrintReportは引数で指定した内容(ここでは,エポック数,損失と精度)をターミナルに表示ます。
    trainer.extend(extensions.PrintReport(['epoch' , 'main/loss' , 'main/accuracy']))

ProgressBarは,学習の進捗状況を進捗バーを用いてパーセンテージで表示します。
大量のデータを使って長時間学習するような場合にどこまで,進んだのか進捗を見るのに便利です。
また,1エポックにかかる時間や終了までにかかる時間を示すので,時間のかかる学習には便利です。
     trainer.extend(extensions.ProgressBar())


学習データを訓練用と検証用に分けて学習する


学習中に過学習を起こしていないか確認するために学習データの一部を検証データにします。
学習に用いるもとのデータはtrainに格納されています。
このデータを学習用と検証用の2つに分けます。
データを8割を学習用trnに,残り2割を検証用にvldに,split_dataset_randomを使ってランダムに振り分けます。
seedは乱数のシードで,毎回,同じシード値を用いることで同じ乱数系が生成されます。
つまり毎回同じ組み合わせのtrnとvldのデータが生成されます。
    trn, vld = split_dataset_random(train, int( len(train)*0.8 ), seed = 0) 

学習用trnと検証用vldそれぞれのiteratorを作成します。   
    t_iterator = iterators.SerialIterator(trn, batch_size, shuffle = True)
    v_iterator = iterators.SerialIterator(vld, batch_size, repeat = False, shuffle = False)

検証用のデータを評価に用いるために次の一行を追加します。
    trainer.extend(extensions.Evaluator(v_iterator, self.model))  


学習の進捗と検証データの評価を確認する。


PrintReportに'validation/main/accuracy'を追加することで,学習の進捗と同時に検証の結果もターミナルに表示することができます。
    trainer.extend(extensions.PrintReport(['epoch', 'main/loss', 'main/accuracy', 'validation/main/accuracy']))

進捗状況をグラフにプロットする


学習や検証の進捗状況をPlotReportを使ってグラフにプロットしてリアルタイムで参照することができます。

損失の変化がloss.pngという画像ファイルとしてresultディレクトリ内に記録されます。
画像ファイルは1エポックごとに更新されます。
loss.pngを表示しておくと,エッポックごとに損失の変化を観察することができます。
    trainer.extend(extensions.PlotReport(['main/loss', 'validation/main/loss'], x_key='epoch', file_name='loss.png'))

精度の変化はaccuracy.pngに記録されます。
    trainer.extend(extensions.PlotReport(['main/accuracy', 'validation/main/accuracy'], x_key='epoch', file_name='accuracy.png'))


プログラムを入力実行してみよう。


下に示すプログラムはPyゼミ3.05のAlexnetの一部(先に説明した部分,doTrain関数内)を変更しました。
違いを確かめながら入力し実行してみましょう。



import os, sys
import numpy as np
import cv2
import chainer
from chainer import Function, report, training, utils, Variable
from chainer import datasets, iterators, optimizers, serializers
from chainer import Link, Chain, ChainList
import chainer.functions as F
import chainer.links as L
from chainer.datasets import tuple_dataset
from chainer.training import extensions
from chainer.datasets import split_dataset_random #new
import pydicom

def constractTrainData( fname , resz ):
    image = []
    label = []
    for i, line in enumerate(open(fname, 'r')):
        data = line.split(",")          # 画像ファイル名と正解データに分割
        print("Train",i, data[0], "Label#",data[1])
        img = readDicom2png(data[0], "tmp.png")
        if resz > 0:                    #画像のリサイズ        
            img = cv2.resize(img,(resz, resz))        
        img = img.astype(np.float32)    
        img = (img - 128)/128           #画素値0-255を-1.0から+1.0に変換
        image.append([img])
                
        t = np.array(int(data[1]), dtype=np.int32)
        label.append(t)
               
    train = tuple_dataset.TupleDataset(image, label)        
    return train
    
def readDicom2png(dcmfnm, tmpfnm):
    ds  = pydicom.read_file(dcmfnm)      
    wc  = ds.WindowCenter                
    ww  = ds.WindowWidth                         
    img = ds.pixel_array                         
    max = wc + ww / 2                      
    min = wc - ww / 2      
    img = 255 * (img - min)/(max - min)   
    img[img > 255] = 255                  
    img[img < 0  ] = 0                    
    img = img.astype(np.uint8)
    cv2.imwrite(tmpfnm, img)
    return img   

def getLabelName(fname):
    lblnm = []
    for line in open(fname, 'r'):
        data = line.split(",")
        lblnm.append(data[1].strip())
        #lblnm[int(data[0])]= data[1]   
    print("LabelName:",lblnm)              
    return len(lblnm), lblnm
        
class Alexnet(Chain):  ### AlexNet ###
    def __init__(self, n_label, n_channel=1):
        super(Alexnet, self).__init__(
            conv1 = L.Convolution2D(n_channel, 96, 11, stride=4),
            conv2 = L.Convolution2D(96, 256, 5, pad=2),
            conv3 = L.Convolution2D(256, 384, 3, pad=1),
            conv4 = L.Convolution2D(384, 384, 3, pad=1),
            conv5 = L.Convolution2D(384, 256, 3, pad=1),
            fc6 = L.Linear(None, 4096),
            fc7 = L.Linear(4096, 4096),
            fc8 = L.Linear(4096, n_label),
        )

    def __call__(self, x):
        h = F.max_pooling_2d(F.local_response_normalization(
            F.relu(self.conv1(x))), 3, stride=2)
        h = F.max_pooling_2d(F.local_response_normalization(
            F.relu(self.conv2(h))), 3, stride=2)
        h = F.relu(self.conv3(h))
        h = F.relu(self.conv4(h))
        h = F.max_pooling_2d(F.relu(self.conv5(h)), 2, stride=2)
        h = F.dropout(F.relu(self.fc6(h)))
        h = F.dropout(F.relu(self.fc7(h)))
        return self.fc8(h)

class Classifier():
    def __init__(self, mdlnm, nlbl):
        model = Alexnet(nlbl)
        self.model = L.Classifier(model,lossfun=F.softmax_cross_entropy)
        self.opt   = optimizers.Adam()
        self.opt.setup(self.model)        
        self.modelName = mdlnm       
            
    def doTrain(self, nLbl, train, batch_size, n_epoch, modelName):
        trn, vld = split_dataset_random(train, int(len(train)*0.8), seed=0)       
        t_iterator = iterators.SerialIterator(trn, batch_size, shuffle=True)
        v_iterator = iterators.SerialIterator(vld, batch_size, repeat=False, shuffle=False)
        updater  = training.StandardUpdater(t_iterator, self.opt)
        trainer  = training.Trainer(updater, (n_epoch, 'epoch'), out='result')
 
        trainer.extend(extensions.Evaluator(v_iterator, self.model))                 
        trainer.extend(extensions.LogReport())
        trainer.extend(extensions.PrintReport(['epoch', 'main/loss', 'main/accuracy', 'validation/main/accuracy']))
        trainer.extend(extensions.ProgressBar())
        trainer.extend(extensions.PlotReport(['main/loss', 'validation/main/loss'], x_key='epoch', file_name='loss.png'))
        trainer.extend(extensions.PlotReport(['main/accuracy', 'validation/main/accuracy'], x_key='epoch', file_name='accuracy.png'))
       
        trainer.run()
                
        print("学習モデル",self.modelName,"を保存します... .   .")
        chainer.serializers.save_hdf5( self.modelName, self.model )
        print("\t... .  .学習モデルを保存しました")

if __name__=='__main__':
    trainfnm = "./TrainTestList/TrainData.0.3.csv"
    labelfnm = "./TrainTestList/labelName.csv"
    resize   = 128       #画像リサイズ 0のときそのままの画素サイズ
    modelnm  = "Alexnet.0.3.hdf5"  #モデル保存名    
    batch_size = 10  # バッチサイズ
    n_epoch    = 30  # エポック数
    
    nLbl, lblName = getLabelName(labelfnm)
    train = constractTrainData(trainfnm, resize)
    print("\ntrain\n") 
    print("画像データ数:",len(train),"画像/ラベル:",len(train[0]))
    print("画像Chanel数:",len(train[0][0]),"高さ:",len(train[0][0][0]),"幅:",len(train[0][0][0][0]))
    
    dl = Classifier(modelnm, nLbl)
    dl.doTrain(nLbl, train, batch_size, n_epoch, modelnm)



実行結果


プログラムを実行すると,最初にラベル名のファイルを読み込みLungなど4つのラベル名を表示します。

その後DICOM画像を読み込み,画像ファイル名とラベル番号が表示されます。

そして,学習が始まります。
今回,検証用のvalidation/main/accuracyの項目の値も一緒に表示されています。


$ python␣dicomAlexnetTrainExtd.py⏎
LabelName: ['Lung', 'Abdomen', 'Chest', 'Head']
Train 0 ../dcmdir2/Lung/W5305890 Label# 0
Train 1 ../dcmdir2/Lung/K5303593 Label# 0
Train 2 ../dcmdir2/Lung/R5305640 Label# 0
Train 3 ../dcmdir2/Lung/T5304000 Label# 0
Train 4 ../dcmdir2/Lung/A5304343 Label# 0
Train 5 ../dcmdir2/Lung/D5304468 Label# 0
     ・・・・・
Train 137 ../dcmdir2/Head/D5221046 Label# 3
Train 138 ../dcmdir2/Head/F5221140 Label# 3
Train 139 ../dcmdir2/Head/G5221187 Label# 3
Train 140 ../dcmdir2/Head/Q5221671 Label# 3
Train 141 ../dcmdir2/Head/E5221093 Label# 3

train

画像データ数: 73 画像/ラベル: 2
画像Chanel数: 1 高さ: 128 幅: 128
epoch       main/loss   main/accuracy  validation/main/accuracy
1           3.06279     0.341667       0.481481                  
2           1.19522     0.536364       0.725926                  
3           0.943893    0.6            0.725926                  
4           0.729782    0.675          0.7                       
5           0.612126    0.7            0.488889                 
     ・・・・・
26          0.0378268   0.981818       1                         
27          0.00758042  1              1                         
28          0.0108063   0.990909       1                         
29          0.0802395   0.981818       1                         
30          0.0146818   0.990909       1                      
学習モデル Alexnet.0.3.hdf5 を保存します... .   .
 ... .  .学習モデルを保存しました          


resultディレクトリの中に精度と損失の変化を記録した画像ファイルが保存されます。
学習の進捗をグラフに自動的にプロットできるのはとても助かります。


精度の変化を観察できる。


損失の変化を観察できる。


まとめ


TrainerのExtensionを使った学習の進捗を観察する方法を説明しました。
Extensionにはまだたくさんの機能が提供されています。
たとえば,学習途中のスナップショットを保存したり,学習率をコントロールしたりすることができます。
いろいろ調べて使いこなせるようになると,より最適な学習が可能になると思います。

2019年2月20日水曜日

Pyゼミ3.05 MNISTのネットワークをAlexnetに対応させる

Alexnetに対応する



Keyword:DICOM,Chainer,Alexnet



前章Pyゼミ3.04ではMNISTのネットワークをDICOM画像に適用できるようにしました。
この章ではだ表的なDeep LearningのネットワークのAlexnetに対応したプログラムを作成します。


Alexnetとは


画像認識のコンペティションであるILSVRC(ImageNet Large Scale Visual Recognition Challenge)の2012年の大会で他者を圧倒する認識率をこのAlexnetが出しました。

Alexnetの構造は5層の畳み込み層(CNN, Constitutional Neural Network)と3層の全結合層(FC,Full Connection)からなっています。
特徴としては以下の点が挙げらます。

  • ReLU活性化関数
  • マルチGPU(Graphics Processing Unit)での学習
  • Data augmentation(データ拡張)
  • Dropout

論文[1]より。Alexnetの構成を示す。


一般的に活性関数はシグモイド関数を用いていました。
この関数は大きな入力値に対して出力が1に飽和してしまいますが,ReLU関数は線形変換により大きな入力に対しては大きな値を返ます。
この特徴によりAlexnetは学習が早く進むといわれています。

Alexnetを紹介する論文[1]では2つのGPUを使い,畳み込み演算を同時並行で行い,全結合層で出力を統合して結果を出力しています。

データ拡張はデータにランダムな移動や回転,あるいは反転やノイズなどを加えることでデータの多様性を増します。
これにより未知のデータに対する良い結果を出す汎化性能の向上が期待されるといわれています。

Dropoutは訓練時にランダムに半分のノードしか使わない手法です。
Dropoutは訓練時に過学習を防ぐために用いられています。


訓練プログラム(dicomAlexnetTrain.py)の概要


前章Pyゼミ3.04で作成したdicomNNtrain.pyのネットワークモデルを定義したMyModelクラスをこの章ではAlenxnetクラスに置き換えただけです。
したがって,ネットワークモデルを定義したクラスと入力チャンネル数(n_channel = 1)とモデルの設定(model = Alexnet(nlbl))以外は前回と変わっていません。

前章のプログラムの作成は苦労したかもしれませんが,今後新しいネットワークの設計と定義は,ネットワーク定義のクラスを新しく作るだけです。
インターネット上の様々なネットワークを試してみることができます。


Alexnetクラス


ini関数

ini関数にはネットワークの定義が記述されています。
この関数の引数は2つです。

  • n_label:出力するラベル数です。
  • n_channel:入力データ(画像)のチャンネル数です。CT画像はモノクログレイなので1をディフォルトで設定しています。

実際のネットワークの定義ではChainerのLinkの畳み込みニューラルネットワークを構成するConvolution2Dを用いています。
この関数の引数は次のようになります。
    Convolution2D( 入力チャンネル数,
            出力チャンネル数,
            フィルタサイズ,
            ストライドサイズ,
            パッドサイズ )

  • 入力チャンネル数:前ネットワークからの入力のチャンネル数
  • 出力チャンネル数:現ネットワークから出力されるチャンネル数
  • フィルタサイズ:畳み込みフィルタのサイズ
  • ストライドサイズ:フィルタ処理から次のフィルタ処理までの移動量
  • パッドサイズ:入力の辺縁のパディング処理のサイズ

はじめの畳み込み関数を見てみます。
入力チャンネル数はn_channelでここでは最初に初期化した1になります。
出力チャンネル数は96チャンネルです。
チャンネル数とは,フィルタの数と考えます。
このフィルタの重みを最適化するるように訓練が行われます。
論文の図では,2つのGPUに分散して処理しているため,48チャンネルが2つのGPUで処理されています。
畳み込みフィルタのサイズは11で,11×11のサイズのフィルタを意味しています。
ストライドは4で4画素おきに処理が行われます。
    conv1 = L.Convolution2D(n_channel, 96, 11, stride=4),
         
2つめの畳み込み層は前層conv1の値を受け取るため,
入力チャンネル数は96になります。
出力チャンネル数は256です。
フィルタのサイズは5(5x5)です。
パッドサイズは2で,入力データの周囲に2画素パディング(埋め込み)して5×5のフィルタ処理が無駄なくできるようにしています。
    conv2 = L.Convolution2D(96, 256, 5, pad=2),

以下,同様に入力出力チャンネル数,フィルタサイズとパッドサイズが設定されています。         
    conv3 = L.Convolution2D(256, 384, 3, pad=1),      
    conv4 = L.Convolution2D(384, 384, 3, pad=1),
    conv5 = L.Convolution2D(384, 256, 3, pad=1),
この畳み込み層の最終出力は256チャンネルになります。

次に全結合層です。
同じくChainerのLinkの全結合の処理を行うLinearについて説明します。
    Linear(入力サイズ,出力サイズ)

  • 入力サイズ:入力ベクトルの次元数
  • 出力サイズ:出力ベクトルの次元数


それでは全結合のネットワークfc6を見てみます。
入力ベクトルの次元はNoneになっています。ネットワーク定義のこの段階で次元数は未定です。
しかし,最初のデータが順伝播する際に次元数は初期化されます。
そして,出力ベクトルの次元数は4096になります。
    fc6 = L.Linear(None, 4096),

以下同様にしてfc7,fc8が定義され,最終的にn_labelの次元数のベクトルが出力されます。
    fc7 = L.Linear(4096, 4096),
    fc8 = L.Linear(4096, n_label),


call関数


次にcall関数です。
先にini関数で定義したネットワークを順伝播する形態に接続を記述します。
はじめにChainerのFunctionのmax_pooling_2d関数が使われています。
    max_pooling_2d( 入力変数,
             プーリングウィンドウサイズ,
                                      ストライドサイズ )

  • 入力変数:ChanerのVariable型の変数,実際はベクトルになります。
  • プーリングウィンドウサイズ:k×kの画素中の最大値をとるのがmax_poolingです。このkの値を設定します。
  • ストライドサイズ:プーリング処理を何画素おきに行うのか設定します。

次にlocal_response_normalizationです。
これは,局所近傍のチャンネルの正規化を行います。チャンネルによっては大きな値ばかりになったり,その逆の場合などを正規化して補正します。
     local_response_normalization( 入力変数 )

  • 入力変数:ChanerのVariable型の変数,実際はベクトルになります。


最後に,reluです。reluは活性化関数でRectified Linear Unit functionの略で,次式で表されます。
       f(x)=max(0,x)
入力x(Variable型)と0と比較して大きな値を返します。つまり,xが0未満では0を,0以上ではxの値を返す関数です。
活性化関数によく利用されるSigmoid関数は大きな入力に対して最大1を返しますが,relu関数は入力xに比例した値が返されます。

それでは3つの関数が理解できたところで,入力xについての出力hを見てみましょう。
    h = F.max_pooling_2d(F.local_response_normalization(
                       F.relu(self.conv1(x))), 3, stride=2)
最初に入力xは畳み込みニューラルネットワークのself.conv1に与えらえます。
self.conv1の出力はF.reluの活性化関数に与えられます。
活性化関数F.reluの出力はF.local_response_normalizationに与えられ,局所正規化されます。
正規化されたチャンネルは F.max_pooling_2dに与えられ,この時のプーリングサイズは3ⅹ3で,ストライドが2で処理が行われます。
各注目する3x3の画素中の最大値がプーリング値となり,出力hを得ます。

次は前ネットワークの出力hをself.conv2の入力にして,同じように活性化関数relu, 局所正規化とマックスプーリング処理を行って出力hを得ます。
    h = F.max_pooling_2d(F.local_response_normalization(
                       F.relu(self.conv2(h))), 3, stride=2)

前ネットワークの出力hをself.conv3の入力にして,活性化関数を介して出力hを得たものを次のネットワークself.conv4の入力になり,同様に出力hを得ています。
    h = F.relu(self.conv3(h))
    h = F.relu(self.conv4(h))

次に最後の畳み込みニューラルネットself.conv5に前ネットワークの出力hを与えて,新たな出力hを得ています。
    h = F.max_pooling_2d(F.relu(self.conv5(h)), 2, stride=2)

次のネットワークからは全結合のself.conv6~8の処理を行います。
ここではChainerのFunctionの中のdropoutが使われています。
Dropoutは学習時に重みの半分をないものとして学習を行います。
これにより過学習を予防する効果があるといわれています。
    h = F.dropout(F.relu(self.fc6(h)))
    h = F.dropout(F.relu(self.fc7(h)))
前ネットワークの出力hをself.conv6に与え,活性化関数を介した後,dropoutで学習して出力を得ています。
同様にself.conv7にその出力を与えて新たな出力hを得ています。

最後に全結合のself.conv8に前出力hを与えて得た新たな出力をcall関数は返しています。
    return self.fc8(h)

このようにcall関数はネットワークの定義(conv1~conv8)を使って順伝播を記述しています。


プログラムを入力して実行してみよう


プログラム全体の構成はPyゼミ3.04と同じです。
Pyゼミ3.04の「プログラムのポイント」に記述されているように,訓練データファイル(訓練画像ファイル名とラベル番号のリスト)が存在することが前提です。
参考までにPyゼミ3.04のMyModelクラスをソース中に残してあります。
Pyゼミ3.04から変更されている点は以下の通りです。

  • MyModelクラスの代わりにAlenxnetクラスに置き換わっています。
  • 入力データのチャンネル数を1に設定しています(n_channel = 1)。
  • ClassifyImageクラス内でmodelをAlexnetに設定しています(model = Alexnet(nlbl))。

dicomAlexnetTrain.py


import os, sys
import numpy as np
import cv2
import chainer
from chainer import Function, report, training, utils, Variable
from chainer import datasets, iterators, optimizers, serializers
from chainer import Link, Chain, ChainList
import chainer.functions as F
import chainer.links as L
from chainer.datasets import tuple_dataset
from chainer.training import extensions
import pydicom

def constractTrainData(fname , resz):
    image,label = [], []
    for i, line in enumerate(open(fname, 'r')):
        data = line.split(",")          # 画像ファイル名と正解データに分割
        print("Train",i, data[0], "Label#",data[1])
        img = readDicom2png(data[0], "tmp.png")
        if resz > 0:                    # 画像のリサイズ       
            img = cv2.resize(img,(resz, resz))       
        img = img.astype(np.float32)   
        img = (img - 128)/128           # 画素値0-255を-1.0から+1.0に変換
        image.append([img])
               
        t = np.array(int(data[1]), dtype=np.int32)
        label.append(t)
    train = tuple_dataset.TupleDataset(image, label)       
    return train
   
def readDicom2png(dcmfnm, tmpfnm):
    ds  = pydicom.read_file(dcmfnm)     
    wc  = ds.WindowCenter               
    ww  = ds.WindowWidth                        
    img = ds.pixel_array                        
    max = wc + ww / 2                     
    min = wc - ww / 2     
    img = 255 * (img - min)/(max - min)  
    img[img > 255] = 255                 
    img[img < 0  ] = 0                   
    img = img.astype(np.uint8)
    cv2.imwrite(tmpfnm, img)
    return img  

def getLabelName(fname):
    lblnm = []
    for line in open(fname, 'r'):
        data = line.split(",")
        lblnm.append(data[1].strip())
        #lblnm[int(data[0])]= data[1]  
    print("LabelName:",lblnm)             
    return len(lblnm), lblnm
"""
class MyModel(Chain):                   # MNISTで使ったネットワーク(参考)
    def __init__(self, nlbl):          
        super(MyModel, self).__init__(
            l1 = L.Linear(784,100),      
            l2 = L.Linear(100,100),           
            l3 = L.Linear(100,nlbl),
        )
       
    def __call__(self, x):              # 順伝播計算
        h1 = F.relu(self.l1(x))         # 活性化関数にReLuを定義
        h2 = F.relu(self.l2(h1))
        return self.l3(h2)
"""
class Alexnet(Chain):  ### AlexNet ###
    def __init__(self, n_label, n_channel=1):
        super(Alexnet, self).__init__(
            conv1 = L.Convolution2D(n_channel, 96, 11, stride=4),
            conv2 = L.Convolution2D(96, 256, 5, pad=2),
            conv3 = L.Convolution2D(256, 384, 3, pad=1),
            conv4 = L.Convolution2D(384, 384, 3, pad=1),
            conv5 = L.Convolution2D(384, 256, 3, pad=1),
            fc6 = L.Linear(None, 4096),
            fc7 = L.Linear(4096, 4096),
            fc8 = L.Linear(4096, n_label),
        )

    def __call__(self, x):
        h = F.max_pooling_2d(F.local_response_normalization(
            F.relu(self.conv1(x))), 3, stride=2)
        h = F.max_pooling_2d(F.local_response_normalization(
            F.relu(self.conv2(h))), 3, stride=2)
        h = F.relu(self.conv3(h))
        h = F.relu(self.conv4(h))
        h = F.max_pooling_2d(F.relu(self.conv5(h)), 2, stride=2)
        h = F.dropout(F.relu(self.fc6(h)))
        h = F.dropout(F.relu(self.fc7(h)))
        return self.fc8(h)
       
class ClassifyImage():
    def __init__(self, mdlnm, nlbl):
        model = Alexnet(nlbl)
        self.model = L.Classifier(model,lossfun=F.softmax_cross_entropy)
        self.opt   = optimizers.Adam()
        self.opt.setup(self.model)       
        self.modelName = mdlnm      
           
    def doTrain(self, nLbl, train, batch_size, n_epoch, modelName):
        iterator = iterators.SerialIterator(train, batch_size, shuffle=True)
        updater  = training.StandardUpdater(iterator, self.opt)
        trainer  = training.Trainer(updater, (n_epoch, 'epoch'), out='result')
        trainer.extend(extensions.LogReport())    
        trainer.extend(extensions.PrintReport(['epoch', 'main/loss', 'main/accuracy']))     
        trainer.extend(extensions.ProgressBar())
               
        trainer.run()
               
        print("学習モデル",self.modelName,"を保存します... .   .")
        chainer.serializers.save_hdf5( self.modelName, self.model )
        print("\t... .  .学習モデルを保存しました")

if __name__=='__main__':
    trainfnm = "./TrainTestList/TrainData.0.3.csv"
    labelfnm = "./TrainTestList/labelName.csv"
    resize   = 64                   # 画像リサイズ
    modelnm  = "Alexnet.0.3.hdf5"   # モデル保存名   
    batch_size = 10                 # バッチサイズ
    n_epoch    = 10                 # エポック数
   
    nLbl, lblName = getLabelName(labelfnm)
    train = constractTrainData(trainfnm, resize)
    print("\nTrain data information\n")
    print("画像データ数:",len(train))
    print("画像/ラベル:",len(train[0]),"\n画像Chanel数:",len(train[0][0]))
    print("高さ:",len(train[0][0][0]),"\n幅 :",len(train[0][0][0][0]))
   
    ci = ClassifyImage(modelnm, nLbl)
    ci.doTrain(nLbl, train, batch_size, n_epoch, modelnm)



実行結果


プログラムを実行すると,ラベル名のリスト(配列)が表示されます。
インデックス番号がラベル番号になります。
ラベル番号0は’Lung’,ラベル番号3は’Head’になります。

次に142枚の訓練画像がラベル番号と伴に読み込まれます。
画像サイズを64x64にリサイズしています。

学習が始まると,損失(main/loss)は減少し,精度(main/accuracy)は徐々に高くなり,10回目には0.985714になっています。

最終的に学習したパラメータはファイルAlexnet.0.3.hdf5に保存されます。


$ python␣dicomAlexnetTrain.py⏎
LabelName: ['Lung', 'Abdomen', 'Chest', 'Head']
Train 0 ../dcmdir2/Lung/W5305890 Label# 0
Train 1 ../dcmdir2/Lung/K5303593 Label# 0
Train 2 ../dcmdir2/Lung/R5305640 Label# 0
Train 3 ../dcmdir2/Lung/T5304000 Label# 0
Train 4 ../dcmdir2/Lung/A5304343 Label# 0
Train 5 ../dcmdir2/Lung/D5304468 Label# 0

      ・・・・・

Train 136 ../dcmdir2/Head/K5221390 Label# 3
Train 137 ../dcmdir2/Head/D5221046 Label# 3
Train 138 ../dcmdir2/Head/F5221140 Label# 3
Train 139 ../dcmdir2/Head/G5221187 Label# 3
Train 140 ../dcmdir2/Head/Q5221671 Label# 3
Train 141 ../dcmdir2/Head/E5221093 Label# 3

Train data information

画像データ数: 142
画像/ラベル: 2 
画像Chanel数: 1
高さ: 64 
幅 : 64
  epoch       main/loss   main/accuracy
1           1.75138     0.42           
2           0.786748    0.657143       
3           0.685697    0.685714       
4           0.589711    0.778571       
5           0.306332    0.907143       
6           0.329208    0.94           
7           0.0920463   0.992857       
8           0.0648474   0.978571       
9           0.251898    0.935714       
10          0.046363    0.985714       
学習モデル Alexnet.0.3.hdf5 を保存します... .   .
 ... .  .学習モデルを保存しました






テストプログラム(dicomAlexnetTest.py)の概要


このテストプログラムも基本的にPyゼミ3.04のdicomNNtest.pyと同じですが,
Alexnetクラスに置き換わっていること,
入力データ(画像)のチャンネル数が1であること,
ClassfyImageクラス内でmodelにAlexnetを設定していることが異なっています。


プログラムを入力して実行してみよう


Pyゼミ3.04のdicomNNtest.pyと異なる点を修正するだけで実行できます。
  • Alexnetクラスの追加
  • Alexnetクラスのini関数の引数にn_channel = 1の追加
  • model = Alexnet(nlbl)へ変更


dicomAlexnetTest.py



import os, sys
import numpy as np
import cv2
import chainer
import chainer.links as L
import chainer.functions as F
from chainer import optimizers, Chain, Variable, initializers
from chainer.training import extensions
import pydicom

def constructTestData(fname , resz):
    test = []
    for i, line in enumerate(open(fname, 'r')):
        data = line.split(",")
        print("Test",i, data[0], "Label#",data[1])
        img = readDicom2png(data[0], "tmp.png")  
        if resz > 0:                         
            img = cv2.resize(img,(resz, resz))
        img = img.astype(np.float32)   
        img = (img - 128)/128
        test.append([[img], int(data[1]), data[0]])
    return test
   
def readDicom2png(dcmfnm, tmpfnm):
    ds  = pydicom.read_file(dcmfnm)     
    wc  = ds.WindowCenter               
    ww  = ds.WindowWidth                        
    img = ds.pixel_array                        
    max = wc + ww / 2                     
    min = wc - ww / 2     
    img = 255 * (img - min)/(max - min)  
    img[img > 255] = 255                 
    img[img < 0  ] = 0                   
    img = img.astype(np.uint8)
    cv2.imwrite(tmpfnm, img)
    return img  

def getLabelName(fname):
    lblnm = []
    for line in open(fname, 'r'):
        data = line.split(",")
        lblnm.append(data[1].strip()) 
    print("LabelName:",lblnm)             
    return len(lblnm), lblnm

class Alexnet(Chain):  ### AlexNet ###
    def __init__(self, n_label, n_channel=1):
        super(Alexnet, self).__init__(
            conv1 = L.Convolution2D(n_channel, 96, 11, stride=4),
            conv2 = L.Convolution2D(96, 256, 5, pad=2),
            conv3 = L.Convolution2D(256, 384, 3, pad=1),
            conv4 = L.Convolution2D(384, 384, 3, pad=1),
            conv5 = L.Convolution2D(384, 256, 3, pad=1),
            fc6 = L.Linear(None, 4096),
            fc7 = L.Linear(4096, 4096),
            fc8 = L.Linear(4096, n_label),
        )

    def __call__(self, x):
        h = F.max_pooling_2d(F.local_response_normalization(
            F.relu(self.conv1(x))), 3, stride=2)
        h = F.max_pooling_2d(F.local_response_normalization(
            F.relu(self.conv2(h))), 3, stride=2)
        h = F.relu(self.conv3(h))
        h = F.relu(self.conv4(h))
        h = F.max_pooling_2d(F.relu(self.conv5(h)), 2, stride=2)
        h = F.dropout(F.relu(self.fc6(h)))
        h = F.dropout(F.relu(self.fc7(h)))
        return self.fc8(h)

class ClassifyImage:
    def __init__(self, modelname, nlbl):
        model = Alexnet(nlbl)                                                          
        self.model = L.Classifier(model)
               
        print("modelファイル ",modelname," を読み込みます...")
        chainer.serializers.load_hdf5( modelname, self.model )
        print("...ファイルを読み込みました")

    def predict(self, testimg):
        img = np.array([testimg])
        #print(">img>>",type(img),len(img),len(img[0]),len(img[0][0]),len(img[0][0][0]))
        x = Variable(img)
        y = self.model.predictor(x)
        y = F.softmax(y)
        answer = y.data
        answer = np.argmax(answer, axis=1)
        return answer[0], y[0].data
   
if __name__=='__main__':
    testfnm  = "./TrainTestList/TestData.0.3.csv"
    labelfnm = "./TrainTestList/labelName.csv"
    resize   = 64                 # 画像リサイズ
    modelnm  = "Alexnet.0.3.hdf5" # モデル保存名
   
    nLbl, lblName = getLabelName(labelfnm)
    lblCnt = np.zeros(nLbl)    # 各ラベルの正答数を計数
    lblAll = np.zeros(nLbl)    # 各ラベルの総数を計数
   
    data = constructTestData(testfnm, resize)
    print("data:",len(data),len(data[0]))
    print("img :",len(data[0][0]), len(data[0][0][0]),len(data[0][0][0][0]))
   
    ci = ClassifyImage(modelnm, nLbl)  # モデルの読み込み

    ok = 0
    np.set_printoptions(formatter={'float': '{: 0.5f}'.format})
    for i, line in enumerate(data):
        testimg, ans, fnm = line
        prediction, softmax = ci.predict(testimg)  # 推論
        print("\n#",i,"Softmax:",softmax)
        result = "File:"+fnm+"\tAns:"+lblName[ans]+"→ Predict:"+lblName[prediction]
        lblAll[ans] += 1                # ラベルがansの数の計数
        if prediction == ans:           # 推論結果と答えが一致したら
            print(result + "\tOK")
            ok += 1
            lblCnt[prediction] += 1
        else:
            print(result + "\tNG")
   
    print("\n平均正答率:",ok/len(data)) 
    for i in range(nLbl):
        print(str(i), lblName[i],"\t",lblCnt[i]/lblAll[i])



実行結果


実行するとはじめにラベル名が表示されます。

その後,テスト画像とラベル番号が読み込まれます。

73枚のテスト画像が読み込まれた後,dicomAlexnetTrain.pyで保存した学習モデルのファイルAlexnet.0.3.hdf5を読み込みます。

各画像についてテストを行います。
Softmax:の後には各ラベルに対する推測結果が4次元ベクトルで表されます。
ベクトルの最も大きな値のインデックス番号が推測したラベル番号になります。
また,教師データのAnsと推測結果のPredictが一致しているとOK表示され,異なっている場合はNGが表示されます。

最後に平均正答率と,各ラベルの正答率が表示されます。


$ python␣dicomAlexnetTest.py⏎
LabelName: ['Lung', 'Abdomen', 'Chest', 'Head']
Test 0 ../dcmdir2/Lung/E5306265 Label# 0
Test 1 ../dcmdir2/Lung/M5305390 Label# 0
Test 2 ../dcmdir2/Lung/C5304421 Label# 0
Test 3 ../dcmdir2/Lung/O5303765 Label# 0
Test 4 ../dcmdir2/Lung/P5305546 Label# 0
Test 5 ../dcmdir2/Lung/Q5305593 Label# 0

     ・・・・・

Test 68 ../dcmdir2/Head/B5220953 Label# 3
Test 69 ../dcmdir2/Head/P5221625 Label# 3
Test 70 ../dcmdir2/Head/L5221421 Label# 3
Test 71 ../dcmdir2/Head/W5222046 Label# 3
Test 72 ../dcmdir2/Head/X5222093 Label# 3
data: 73 3
img : 1 64 64
modelファイル  Alexnet.0.3.hdf5  を読み込みます...
...ファイルを読み込みました

# 0 Softmax: [ 1.00000  0.00000  0.00000  0.00000]
File:../dcmdir2/Lung/E5306265 Ans:Lung→ Predict:Lung OK

# 1 Softmax: [ 1.00000  0.00000  0.00000  0.00000]
File:../dcmdir2/Lung/M5305390 Ans:Lung→ Predict:Lung OK

# 2 Softmax: [ 1.00000  0.00000  0.00000  0.00000]
File:../dcmdir2/Lung/C5304421 Ans:Lung→ Predict:Lung OK

# 3 Softmax: [ 1.00000  0.00000  0.00000  0.00000]
File:../dcmdir2/Lung/O5303765 Ans:Lung→ Predict:Lung OK
      ・・・・・
# 70 Softmax: [ 0.00000  0.00000  0.00000  1.00000] File:../dcmdir2/Head/L5221421 Ans:Head→ Predict:Head OK # 71 Softmax: [ 0.00002  0.00000  0.00000  0.99998] File:../dcmdir2/Head/W5222046 Ans:Head→ Predict:Head OK # 72 Softmax: [ 0.00000  0.00000  0.00000  1.00000] File:../dcmdir2/Head/X5222093 Ans:Head→ Predict:Head OK 平均正答率: 1.0 0 Lung 1.0 1 Abdomen 1.0 2 Chest 1.0 3 Head 1.0

10回の学習で得たモデルを使って,テストでは良い正答率が出ています。
これは同一人物のデータなので当然の結果です。
いろいろなDICOM画像データで試してみるとよいでしょう。


まとめ


Pyゼミ3.05ではニューラルネットワークを記述したクラスを交換することで簡単にAlexnetに対応することができました。
このようにニューラルネットワークのみを変更することで様々なDeep Neural Networkに発展させることができます。
また,ネットワーク定義においてチャンネル数を変えたり,畳み込み層を増やしたりいろいろと自分オリジナルのネットワークを作ることができます。


参考文献

[1] Alex Krizhevsky, Ilya Sutskever, Geoffrey E. Hinton :ImageNet Classification with Deep Convolutional Neural Networks. Advances in Neural Information Processing Systems 25 (NIPS 2012)