機械学習システムの利用用途に応じた画像データ前処理(前編)

著者: 橋本恭佑, 伊藤雅博、株式会社 日立製作所

# はじめに

機械学習システムを構築する際は、まずPoC（Proof of Concept）で機械学習モデルの有用性を確認して、
その成果をもとにSEがシステム開発を行います。
特に画像分野では深層学習技術に注目が集まっており、昨今ではPoCで深層学習向けのフレームワークを採用する場面も多くみられます。

[機械学習における画像データ前処理の性能検証(前編)](https://qiita.com/kyosuke_hashimoto/items/cd0aaf347cbfde1917f0)と[機械学習における画像データ前処理の性能検証(後編)](https://qiita.com/kyosuke_hashimoto/items/90d7cd5dce0402da55ca)では機械学習システムを開発するSEを対象として、機械学習モデルの作成および推論の処理時間短縮を目的とした、前処理のチューニングの指針を示しました。
これらの記事は、前処理と学習処理を分離したシステムとして設計することを前提としていました。
しかし深層学習向けフレームワークは、前処理と学習処理は一体のシステムとして設計されます。
そのため深層学習向けフレームワークを使用する際は、単純に前処理だけを高速化しても学習処理がボトルネックとなり、全体の処理時間を短縮できない場合があります。

本連載では、深層学習向けフレームワークを活用した機械学習モデルを組み込んだシステムの設計を行うSEを対象として、
画像の前処理と学習処理とが一体となった機械学習システムにおける前処理の処理時間短縮に向けた設計指針と、
全体の処理時間短縮効果を検証した結果を2回に渡り示します。
検証結果を踏まえて、前処理と学習処理とを一体のシステムとして設計するか、分離したシステムとして設計するかについて設計指針を示します。

### 投稿一覧： 
1. [機械学習システムの利用用途に応じた画像データ前処理（前編）](https://qiita.com/private/12b836edfd778147d3e6)・・・本投稿
2. 機械学習システムの利用用途に応じた画像データ前処理（後編）

# 深層学習における前処理の設計上の検討事項

## 深層学習向けフレームワークを用いた機械学習システムの概要

深層学習向けフレームワークを用いた機械学習システムの概要を図1に示します。
PyTorchをはじめとする深層学習向けフレームワークにおいては、前処理と学習処理とを一体のシステムとして設計します。
前処理と学習処理との間にはキューがあり、前処理済のデータはキューに格納されます。
学習処理はキューから都度、前処理済データを取り出し、深層学習ネットワークを構築します。
前処理の完全な終了をまたずに学習処理を開始でき、モデル作成中は前処理と学習処理を並行して実行できることから、
元の画像セットから機械学習モデルを作成するまでの総処理時間短縮効果を期待できます。

![fig01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592367/e52c2428-a3b6-b1ce-ae2b-d73dfac16e07.png)


図1: 深層学習向けフレームワークを用いた機械学習システムの概要

## 深層学習における前処理の設計上の検討事項
深層学習における画像前処理の設計上の検討事項を2点示します。

### (1) 前処理へのGPU適用
画像の前処理には、画像の拡縮、回転、反転、ノイズ除去といった、GPUによる高速化が期待できる処理が多数あり、
処理時間短縮に向けて前処理にGPUを利用するか否かを検討する余地があります。

### (2) 前処理のチューニング目標値見積
一般的に深層学習の技術を使った学習処理はGPUで処理されます。
学習処理に利用可能なGPUが数台程度のシステムでは、GPUによる学習処理がボトルネックとなって総処理時間が長くなることがあります。
前処理を複数のプロセスで並列処理する場合に、前処理の処理時間が全体の処理時間にどのような影響を与えるか、
利用するリソース量がどのくらいかを考慮して、前処理のチューニングの大まかな目標値を決める必要があります。

## 深層学習における画像前処理の設計ポイント抽出に向けた検証項目
前節で述べた(1)の検討事項について下記の検証①と検証②を、また(2)の検討事項について検証③を通して深層学習における画像前処理の設計ポイントを示します。

### 検証①: 前処理のソースコード書換工数の見積
CPU向けに記述された前処理をGPU向けに変更する際に、どの程度のソースコードの改変やメンテナンスが必要なのかを示します。

### 検証②: プロセッサの種類(CPU/GPU)による前処理の処理時間
画像の前処理をCPUおよびGPUで実施した際の処理時間を比較します。

### 検証③: 前処理のチューニングが総処理時間に与える影響の確認
前処理の並列数を調整し、学習処理も含めた合計の処理時間、リソース利用率がどのように変化するかを確認します。
また、確認した結果を踏まえて、用途に応じた前処理のシステムの設計方針と前処理チューニングの重要性について考察します。

今回の投稿では、「検証①: 前処理のソースコード書換工数の見積」の検証結果を紹介し、「検証②: プロセッサの種類(CPU/GPU)による前処理の処理時間」、「検証③: 前処理のチューニングが総処理時間に与える影響の確認」については後編で紹介します。 

# 深層学習向け画像前処理システムの性能比較検証
深層学習フレームワークにPyTorchを利用した前処理を、複数のライブラリ及びプロセッサ上で実行した場合の処理時間を比較します。
さらに、前処理の処理時間短縮による、学習処理も含めた処理時間の短縮効果について検証します。

## 対象とするユースケースと前処理の概要
今回の検証では、前回と前々回の投稿と同様に機械学習用途のベンチマークプログラムの１つである[MLPerf](https://mlperf.org/)から、
物体検出を題材とします。機械学習エンジンにはPyTorchを用います。
MLPerfの物体検出における前処理の概要については
[機械学習における画像データ前処理の性能検証(前編)](https://qiita.com/kyosuke_hashimoto/items/cd0aaf347cbfde1917f0)を参照ください。


## 使用した前処理ライブラリ
機械学習エンジンとして用いるPyTorchと組み合わせて利用できる画像向け前処理ライブラリの一覧を表 1に示します。主に2つの選択肢があります。

表1: 機械学習エンジンに対応する画像向け前処理ライブラリ一覧

| 項番 | ライブラリ名 | 対象エンジン | GPUによる前処理可否 | 説明 |
| ------ | ------ | ------ | ------ | ------ |
| 1 | torchvision | PyTorchのみ | × | PyTorchと組み合わせることを前提とした画像処理ライブラリ。 |
| 2 | OpenCV | 制限なし | ○(4.0以降) | 画像処理分野における主要な画像処理ライブラリ。 | 


torchvisionは、PyTorchと組み合わせやすいようにAPIをそろえた画像処理ライブラリです。
しかし、2020年5月現在はtorchvisionにおいて、MLPerfの前処理を実装可能なGPU向けモジュールは開発されていません。
一方で、OpenCVは幅広い用途で利用されてきた実績のあるライブラリであり、
バージョン4.0以降ではGPUによる前処理を可能にするpythonライブラリがリリースされています。


## 検証環境
画像処理においては、CPUとGPUを搭載した1台のマシン上で前処理と学習処理を行うことが一般的です。
特に、実業務に用いられるマシンには高スペック多コアのCPUが搭載される場合が多くみられます。
今回の検証では高スペックの多コアのCPUとGPUを併せ持つマシンを利用し、CPUとGPUによる前処理の処理時間比較と、
前処理と学習処理のGPUの負荷測定を行います。検証環境のハードウェアスペックを表2に示します。
今回の検証では、OSSの最新バージョンへの追従が早く、OpenCVでのGPUを用いた処理に現時点で対応容易なOSとしてUbuntuを選択しました。

表2: 検証環境のハードウェアスペック

| 項目 | 詳細 |
| ------ | ------ |
| OS | Ubuntu 18.04.4 | 
| CPU, コア数, 物理的個数 | Intel(R) Xeon(R) CPU E5-2695 v2 @ 2.40GHz, 12 コア, 2 個 |
| GPU | GeForce GTX 1080 Ti | 
| Memory(GB) | 165 | 
| HDD(GB) | 56 | 
 
また、検証で利用している主なソフトウェアのバージョンは表3の通りです。

表3: 検証環境で利用する主なソフトウェアとそのバージョン

| 項番 | ソフトウェア | バージョン |
| --- | ------ | ------ |
| 1 | Python | 3.6.9 |
| 2 | Numpy | 1.16.4 |
| 3 | OpenCV | 4.2.0 |
| 4 | PyTorch | 1.3.1 |
| 5 | torchvision | 0.4.2 |
| 6 | cuda | 10.2 |
| 7 | CuDNN | 7.6.5 |
| 8 | cudatoolkit | 10.0.130 |

## 検証①: 前処理のソースコード書換工数の見積
MLPerfの物体検出における前処理はtorchvisionで実装されているため、
前処理を複数ライブラリ間で比較するためにはOpenCVへの書き換えが必要です。
OpenCVではバージョン4.0以降で、GPUによる画像処理を実行するPython向けライブラリが公開されており、
MLPerfにおける前処理をGPU向けに再実装可能です。
MLPerfの物体検出の前処理をGPU上のOpenCVによる前処理に書き換えるのに必要なコード量を見積もるため、
書き換えが必要な箇所を抽出した結果を表4に示します。
ここで表中のFとは、torchvision.transforms.functionalを指し、PILはPillowを指します。
表4の結果から、OpenCVへの書き換えに必要なコード量はそれほど多くないといえます。

表4: 既存ライブラリによるMLPerfの物体検出の前処理の書換工数の見積

| 項番 | 前処理内容  | torchvision(MLPerfの元コード) | OpenCV on GPU |
|----|---------|------|------|
| 1 | 画像読み込み | PIL.image.open(“x”).convert(“RGB”) | cv2.imread(“x”)cv2.cuda_GpuMat().upload() |
| 2 | サイズ取得 | image.size | image.shape[1::-1] |
| 3 | リサイズ | F.resize(image, image.size) | cv2.cuda.resize(image, image.shape[1::-1], interpolation) |
| 4 | 水平反転 | F.hflip(image) | cv2.flip(image, 1) |
| 5 | 明るさの調整 | enhancer=PIL.ImageEnhance.Brightness(image) <br> image=enhancer.enhance(brightness_factor) | table = np.array([ i*brightness_factor for i in range (0,256)]).clip(0,255).astype('uint8') <br> image=cv2.LUT(image, table) |
| 6 | コントラストの調整 | enhancer=ImageEnhance.Contrast(image) <br> image=enhancer.enhance(contrast_factor) | table = np.array([ (i-74)*contrast_factor+74 for i in range (0,256)]).clip(0,255).astype('uint8') <br> image = cv2.LUT(image,table) |
| 7 | サチュレーションの調整 | enhancer=ImageEnhance.Color(img) <br> image=enhancer.enhance(saturation_factor) | image=Image.fromarray(image) <br> enhancer=ImageEnhance.Color(image) <br> image=enhancer.enhance(saturation_factor) |
| 8 | 色相の調整 | input_mode = image.mode <br> if input_mode in {'L', '1', 'I', 'F'}:<br>    return image <br> h, s, v = image.convert('HSV').split() <br> np_h = np.array(h, dtype=np.uint8) <br> with np.errstate(over='ignore'):<br>    np_h += np.uint8(hue_factor * 255) <br> h = Image.fromarray(np_h, 'L') <br> image = Image.merge('HSV', (h, s, v)).convert(input_mode) <br> return image | image = Image.fromarray(image) <br> input_mode = image.mode <br> if input_mode in {'L', '1', 'I', 'F'}:<br>    return np.array(image) <br> h, s, v = image.convert('HSV').split() <br> np_h = np.array(h, dtype=np.uint8) <br> with np.errstate(over='ignore'):<br>    np_h += np.uint8(hue_factor * 255) <br> h = Image.fromarray(np_h, 'L') <br> image = Image.merge('HSV', (h, s, v)).convert(input_mode) <br> return np.array(img) |
| 9 | 正規化 | F.normalize(image, mean, std) | image = image_gpu.download() <br> cv2.normalize(np_image_data.astype(‘float’), None, -0.5, 5, cv2.NORM_MINMAX) |
| 10 | Tensorへ変換 | F.to_tensor(image) | np.asarray(image) |

# おわりに
本投稿では、画像の前処理と学習処理とが一体となった機械学習システムにおける、前処理の処理時間短縮を目的とした機械学習システム設計上の検討事項を示しました。
また、前処理に利用可能なライブラリを比較し、ライブラリやプロセッサを変更する場合の前処理の再実装工数を見積もりました。
次回の投稿では、プロセッサの変更や処理の並列化による前処理のチューニングと、前処理のチューニングが全体の処理時間に与える影響を実機検証した結果について紹介します。