【連載企画】GPUコンテナ活用 【全6回】 |
---|
GPUコンテナとは何か?何が便利なのか?【第1回:GPUコンテナで速攻環境構築】 |
TensorFlowとKerasによるディープラーニング①【第2回:GPUコンテナで画像解析〜準備編〜】 |
TensorFlowとKerasによるディープラーニング②【第3回:GPUコンテナで画像解析〜実践編〜】 |
Chainerを使ったディープラーニング【第4回:GPUコンテナで機械学習する】 |
PyTorchで機械学習【第5回:GPUコンテナでテンソルの基本を理解する】 |
AI初学者にとってハードルのひとつになっているGPU環境構築をできるだけ効率的に行うために、コンテナを有効活用することを目的に連載をスタートしました。前回はChainer(チェイナー)でscikit-learn付属の『Iris plants dataset』を使い、3種類のあやめ(アイリス)の花の測定値から、どの品種に属するかを分類する機械学習モデルを構築しました。
目次
この連載について
本連載は、全6回のシリーズを通してできるだけ効率的に、GPUの環境構築を行うためにコンテナの活用を行っていきます。「機械学習やディープラーニングをGPUで実行してみたいけど難しそう…」など導入にハードルを感じられている方に、コンテナを活用することで、環境構築に要する工数を圧倒的に削減し、即座に課題に取り組むことができるメリットを感じていただきます。そのために必要な知識や操作方法を、当社のGPUサーバーを使い解説していきます。
連載を読み終えるころには、TensorFlowやPyTorchなどのメジャーなフレームワークを使った演習ができるようになっているはずです。
本連載は、こちらの手順で進めています
・GPUコンテナとは何か?何が便利なのか?(第1回)
AI初学者がGPUを使って機械学習やディープラーニングに取り組みたい場合、環境構築に想像以上の工数が発生することがあります。セットアップ作業に要する時間を極力削減するためにコンテナ技術を適用し、コンテナ内からGPUを利用するための準備と手順について紹介します。
●GPUコンテナとは何か?何が便利なのか?(第1回)
https://www.kagoya.jp/howto/cloud/gpu-container1/
・TensorFlowとKerasによるディープラーニング①(第2回)
OSS(オープンソースソフトウエア)の機械学習ライブラリの中からTensorFlow(テンソルフロー)、Keras(ケラス)を取り上げ、これらが稼働するコンテナを作成し、コンテナ内からGPUを指定する方法について紹介します。TensorFlowはKerasを取り込む形で公開されていて、ディープラーニングをする際の使い勝手の良さから、多くのユーザーに利用されています。この回ではコンテナ内のTensorFlowでGPUを利用できる状態まで確認します。
●GPUコンテナで画像解析?準備編?(第2回)
https://www.kagoya.jp/howto/cloud/gpu-container2/
・TensorFlowとKerasによるディープラーニング②(第3回)
TensorFlow(テンソルフロー)やTheano(テアノ)/CNTK(Cognitive Toolkit)の複数のバックエンドとして利用可能なKeras(ケラス)を取り上げ、TensorFlowとKerasを使ったディープラーニングを行います。ここではAI初学者の方が親しみやすい課題を扱うことを意図し、TensorFlowの公式ガイドに記載されている「初心者のための TensorFlow 2.0 入門」のチュートリアルを取り上げました。
●GPUコンテナで画像解析?実践編?(第3回)
https://www.kagoya.jp/howto/cloud/gpu-container3/
・Chainerを使ったディープラーニング(第4回)
ディープラーニングのフレームワークとして有名なChainer(チェイナー)の利用方法を紹介します。コンテナからPythonのプログラム実行結果を通して確認します。C言語に比べて処理時間がかかると言われているPythonですが、数値計算を効率的に行うための拡張モジュールであるNumPy(ナムパイ)も利用します。今回扱う題材としてChainerのチュートリアルを取り上げます。
●GPUコンテナで機械学習する(第4回)
https://www.kagoya.jp/howto/cloud/gpu-container4/
・PyTorchで機械学習(第5回)今回の記事
Pythonの機械学習用フレームワークであるPyTorch(パイトーチ)を取り上げます。PyTorchではTensor(テンソル)という型で行列を表現します。Tensorは多次元配列を扱うためのデータ構造であり、GPUをサポートしていることから、PyTorchが稼働するコンテナを利用し、PyTorchのチュートリアルに沿ってGPUを使った計算処理の方法を紹介します。
・OpenPoseによる関節点抽出・姿勢推定(第6回)
カメラ画像のAI画像認識と言えば「顔認証」を思い浮かべる人が多いと思いますが、最近は一歩進み、人が映った静止画や動画から関節点抽出・姿勢推定に取り組むケースが増えています。人体、顔、手足などのキーポイントを画像から検出する技術がディープラーニングにより、実用レベルまで向上しているからです。この回ではOpenPose(オープンポーズ)というライブラリをGPU上で動かすコンテナを使い、動画ファイルの関節点抽出手順を紹介します。
PyTorchが動くコンテナを使った機械学習
今回はPyTorchのコンテナから行う機械学習の導入部分を説明します。特にTensor(テンソル)の扱い方に重点を置いた内容になっています。そのために先ずはコンテナの準備とGPUの動作確認を行い、そのあとにPytorch公式チュートリアルに沿って基本的な考え方を理解しましょう。
PyTorch(パイトーチ)とは
今回、とりあげる PytorchはPython向けのオープンソース機械学習ライブラリです。過去の連載記事でとりあげたTensorFlow、Keras、Chainerなど他のライブラリと同様にポピュラーなものになっています。Pythonの数値計算を効率的に行うための拡張モジュールであるNumPy(ナムパイ)と使い方が似ていることも人気のひとつの要因と言えそうです。そして何と言っても、GPUを利用して高速に演算処理ができる点がGPUユーザーにとって大きなメリットです。
今回行うことの概要
AI初学者の方が親しみやすい課題を扱うことを意図し、公式チュートリアル1.7.1 (英語版) WELCOME TO PYTORCH TUTORIALSに記載されている Deep Learning with PyTorch: A 60 Minute Blitz を取り上げました。
チュートリアルの内容を補足しながら進めていきますので、下記の記載事項もあわせて見ていただけると理解が深まると思います。
【参考サイト:https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html】
PyTorchのコンテナを準備する
チュートリアルに取り掛かる前にコンテナの準備を行いましょう。
docker hubからPyTorchのコンテナイメージをpullします。
Dockerhub 参照サイト:https://hub.docker.com/r/pytorch/pytorch/
$ docker pull pytorch/pytorch
Using default tag: latest
latest: Pulling from pytorch/pytorch
171857c49d0f: Pull complete
419640447d26: Pull complete
(略)
Digest: sha256:9cffbe6c391a0dbfa2a305be24b9707f87595e832b444c2bde52f0ea183192f1
Status: Downloaded newer image for pytorch/pytorch:latest
docker.io/pytorch/pytorch:latest
コンテナイメージを確認します。
$ docker images pytorch/pytorch
REPOSITORY TAG IMAGE ID CREATED SIZE
pytorch/pytorch latest 349148663741 4 weeks ago 5.61GB
pyTorchのコンテナ内からnvidia-smiを実行し、GPU周辺情報について確認します。
$ docker run --gpus all pytorch/pytorch nvidia-smi
Tue Feb 16 06:47:17 2021
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 460.32.03 Driver Version: 460.32.03 CUDA Version: 11.2 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 Tesla P40 Off | 00000000:03:00.0 Off | 0 |
| N/A 29C P8 10W / 250W | 2MiB / 22919MiB | 0% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
| No running processes found |
+-----------------------------------------------------------------------------+
上記のようにドライバーやCUDAのバージョン情報などが表示されました。
インストール済のパッケージの確認
GPUコンテナから一度抜けたので、あらためてPyTorchのコンテナイメージからbashをたちあげ、pip3 listコマンドでインストール済のパッケージ一覧を表示します。
$ docker run --gpus all -it pytorch/pytorch:latest bash
root@d72e335c3962:/workspace# pip3 list
Package Version
---------------------- -------------------
backcall 0.2.0
beautifulsoup4 4.9.3
brotlipy 0.7.0
certifi 2020.12.5
cffi 1.14.4
chardet 3.0.4
(略)
soupsieve 2.1
torch 1.7.1
torchelastic 0.2.0
torchtext 0.8.0a0+0f911ec
torchvision 0.8.2
tqdm 4.51.0
traitlets 5.0.5
typing-extensions 3.7.4.3
urllib3 1.25.11
wcwidth 0.2.5
wheel 0.35.1
root@d72e335c3962:/workspace#
「torch」「torchvision」などPytorchでよく使うライブラリがインストールされたことが確認できました。
次にGPUが利用可能な状態かを確認します。
root@d72e335c3962:/workspace# python3
Python 3.7.9 (default, Aug 31 2020, 12:42:55)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import torch
>>> a = torch.cuda.is_available()
>>> print(a)
True
このように True と表示されればPyTorchのコンテナからGPUが利用可能な状態ですが、False と表示された場合はNVIDIAドライバーが古い、CUDAのバージョンが適切でない等の理由でGPUが利用できない状態なので、ドライバーの更新やCUDA再インストールなどの対応が必要になります。
GPUが利用可能な状態であることがわかりましたので、実際にGPUを使った演算を行います。
>>> ten1 = torch.tensor([0, 1]).cuda()
>>> ten2 = torch.tensor([2, 3]).cuda()
>>> ten1 + ten2
tensor([2, 4], device='cuda:0')
GPUを使ったベクトルの計算結果が表示されました。
では、チュートリアルの内容に沿って演習をしていきましょう。
PyTorchの公式チュートリアルを実践する
前述したように公式チュートリアルの内容に沿って、GPUコンテナ内からPyTorchを使います。英語版の公式サイトのバージョンは、この記事を書いている2021年2月時点で1.7.1になっていますので、そちらもあわせて参照してください。
参考サイト:WELCOME TO PYTORCH TUTORIALS
今回の演習では、上記の公式チュートリアルの中の以下のパートをとりあげました。
参考サイト:DEEP LEARNING WITH PYTORCH: A 60 MINUTE BLITZ
PyTorchって何?
PyTorchはPythonベースの科学計算パッケージで、次の2つの大きな役割りを担います
- Numpyの代替品としてGPUやその他アクセラレータの演算パワーを引き出す
- ニューラルネットワークの実装に役立つ自動微分ライブラリ
このチュートリアルのゴール
- PyTorchのTensorライブラリとニューラルネットワークを理解すること
- 画像を分類するための小さなニューラルネットワークをトレーニングできること(今回の記事では取り扱いません)
TENSORS(テンソル)
テンソルは、配列や行列に非常によく似た特殊なデータ構造をしています。PyTorchではテンソルを通してモデルの入力と出力ならびにモデルのパラメーターを記述します。
テンソルはNumPyのndarrayに似た多次元配列ですが、GPUで実行することでより高速に演算処理を行うことが可能です。ndarrayに精通している場合はTensorAPIをすぐに使用できますが、そうでない場合は以下の内容を参照しテンソルの基本操作について理解しましょう。
>>> import torch
>>> import numpy as np
このチュートリアルでは numpy も使用しますので、PyTorchといっしょうにインポートします。PyTorchは上記のように numpy を呼び出すの同じような感覚で torch をインポートすることで利用可能になります。
テンソルの初期化
テンソルは以下に示すように、いくつかの方法で作成することが可能です。では実際にみてみましょう。
データから直接 Tensor を作成する方法
テンソルはデータから直接作成することができます。この際に数値の型を指定しなければ、データ型は自動的に決まります。
>>> data = [[1, 2],[3, 4]]
>>> x_data = torch.tensor(data)
out:
>>> print(data)
[[1, 2], [3, 4]]
>>> print(x_data)
tensor([[1, 2],
[3, 4]])
上の例では torch.tensor 関数を使い、直接数値を指定してテンソルを作成しています。
【補足】
データ型を指定するときは、dtypeを引数として使用することで可能です。
>>> data = torch.tensor([[1, 2], [3, 4.]], dtype=torch.float64)
out:
>>> print(data)
tensor([[1., 2.],
[3., 4.]], dtype=torch.float64)
テンソル作成時に、引数として64ビットの浮動小数点数を示す float64 と指定しています。
出力結果をみると、各々の値に小数点がついていることがわかります。
NumPy配列から
テンソルはNumPy配列から作成することも可能です(逆も同様です。Bridge with NumPy を参照してください)。
>>> np_array = np.array(data)
>>> x_np = torch.from_numpy(np_array)
NumPy 配列から変換する場合は上の例のように torch.from_numpy 関数を利用します。このときのデータ型は NumPy の型を継承します。
out:
>>> print(x_np)
tensor([[1., 2.],
[3., 4.]], dtype=torch.float64)
別のテンソルから:
新しいテンソルは、明示的に上書きされない限り、引数で指定したテンソルの属性を継承します。
>>> x_ones = torch.ones_like(x_data) # x_dataの属性を保持
print(f"Ones Tensor: \n {x_ones} \n")
x_rand = torch.rand_like(x_data, dtype=torch.float) # x_dataのデータ型を上書き
print(f"Random Tensor: \n {x_rand} \n")
out:
Ones Tensor:
tensor([[1, 1],
[1, 1]])
Random Tensor:
tensor([[0.4687, 0.4422],
[0.6415, 0.8786]])
テンソル作成時に明示的にデータ型を指定しなかったケースでは、前出の x_data の属性を継承し、x_ones のデータ型は整数でしたが、
明示的にデータ型を torch.float と指定したケースでは、x_rand のデータ型は浮動小数点数になっていることがわかります。
乱数や定数を使う場合
shape という名前のタプルを引数にした下記の関数の出力結果を比較しましょう。
>>> shape = (2,3,)
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)
print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")
out:
Random Tensor:
tensor([[0.5838, 0.7678, 0.6982],
[0.9962, 0.2261, 0.6554]])
Ones Tensor:
tensor([[1., 1., 1.],
[1., 1., 1.]])
Zeros Tensor:
tensor([[0., 0., 0.],
[0., 0., 0.]])
1番目の出力結果は、torch.rand 関数を使い、乱数で2×3のテンソルを生成しています。
2番目の出力結果は、torch.ones 関数を使い、1でテンソルを生成しています。
3番目の出力結果は、torch.zeros 関数を使い、0でテンソルを生成しています。
テンソル属性
テンソル属性は、それらのシェイプ、データ型ならびにテンソルが格納されているデバイスについて示します。
>>> tensor = torch.rand(3,4)
print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")
out:
Shape of tensor: torch.Size([3, 4])
Datatype of tensor: torch.float32
Device tensor is stored on: cpu
テンソル演算
ここでは、転置、インデクシング、スライシング、ランダムサンプリングなど100を超えるテンソル演算の中からいくつかについて説明します。詳細についてはこちらの情報を参照してください。
これらのテンソル演算はGPUで実行可能です(通常CPUよりも高速です)。Google Colabを使用している場合はGPUを割り当て実行します。
# We move our tensor to the GPU if available
if torch.cuda.is_available():
tensor = tensor.to('cuda')
以下の演算処理を実行しましょう。NumPy APIに精通しているかたであれば Tensor APIも簡単に使用できるはずです。
Numpyライクなインデクシングとスライシング:
tensor = torch.ones(4, 4)
tensor[:,1] = 0
print(tensor)
out:
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
torch.ones 関数を使い、定数1で埋める4×4のテンソルを作成し、各行の2番目の値に0を代入する操作を行っています。
テンソルの結合 torch.cat 関数を使ってテンソルを他のテンソルと結合させることができます。torch.stack 関数もあわせて参照することで双方の違いについて理解しましょう。(torch.stackで使うテンソルはすべて同サイズであること など)
t1 = torch.cat([tensor, tensor, tensor], dim=1)
print(t1)
out:
tensor([[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.]])
前出の tensor をtorch.catの引数として渡すことで、それらを結合したテンソル t1 を返してくれます。結合する軸は dim=1 によって指定しました。
テンソルの乗算
# This computes the element-wise product
print(f"tensor.mul(tensor) \n {tensor.mul(tensor)} \n")
# Alternative syntax:
print(f"tensor * tensor \n {tensor * tensor}")
out:
tensor.mul(tensor)
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
tensor * tensor
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
2つのテンソル間の行列乗算を実行します
print(f"tensor.matmul(tensor.T) \n {tensor.matmul(tensor.T)} \n")
# Alternative syntax:
print(f"tensor @ tensor.T \n {tensor @ tensor.T}")
out:
tensor.matmul(tensor.T)
tensor([[3., 3., 3., 3.],
[3., 3., 3., 3.],
[3., 3., 3., 3.],
[3., 3., 3., 3.]])
tensor @ tensor.T
tensor([[3., 3., 3., 3.],
[3., 3., 3., 3.],
[3., 3., 3., 3.],
[3., 3., 3., 3.]])
インプレース操作 _ 接尾辞(アンダースコアサフィックス)が付いた操作はインプレース(in-place:その場ですぐに実行する)演算になります。例えば x.copy_(y), x.t_()のような記述です。
print(tensor, "\n")
tensor.add_(5)
print(tensor)
out:
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
tensor([[6., 5., 6., 6.],
[6., 5., 6., 6.],
[6., 5., 6., 6.],
[6., 5., 6., 6.]])
注意
インプレース(in-place)演算はメモリをいくらか節約しますが、履歴がすぐに失われるため計算時に問題が発生する可能性があります。したがってメモリ消費を極力回避したい場合などを除き、インプレース(in-place)演算の使用はお勧めしません。
NumPyとのブリッジ
テンソルがCPU上にある場合、Torch TensorとNumPy 配列はメモリ上の同じ領域に配置されていることから、一方を変更すると他方も変更されます。
Tensor から NumPy 配列 へ
t = torch.ones(5)
print(f"t: {t}")
n = t.numpy()
print(f"n: {n}")
out:
t: tensor([1., 1., 1., 1., 1.])
n: [1. 1. 1. 1. 1.]
torch.ones 関数を使い1で埋めた1×5 テンソルを作成し、tに代入したものをNumPy 配列に引き渡しています。
テンソルの変化はNumPy配列に反映されます。
t.add_(1)
print(f"t: {t}")
print(f"n: {n}")
out:
t: tensor([2., 2., 2., 2., 2.])
n: [2. 2. 2. 2. 2.]
add_() 関数で、tに1を加算しました。
その結果、Torch TensorとNumPy 配列はメモリアドレスを共有しているため、テンソルの変更によってNumPy配列も変更されたことがわかりました。
逆についても、同様の結果になります。
NumPy 配列からTensor へ
n = np.ones(5)
t = torch.from_numpy(n)
np.ones() 関数により、1で埋められた配列nからtorch.from_numpy 関数で NumPy配列 からテンソルに値が引き渡されました。
NumPy配列の変更は、テンソルに反映されます。
np.add(n, 1, out=n)
print(f"t: {t}")
print(f"n: {n}")
out:
t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
n: [2. 2. 2. 2. 2.]
np.add() 関数で、nに1を加算し、戻り値の値をnに上書きしました。
その結果、NumPy配列の変更によってテンソルも変更されたことがわかりました。
補足:GPUでの演算処理
CUDA Tensors(CUDA テンソル)
PyTorch Tensorでは .to メソッドを使用して、任意のデバイスにテンソルを移動できます。CUDAを利用することでGPUで演算処理を行うことが可能です。
CUDAが利用可能な場合、以下のスクリプトが実行されます。
>>> if torch.cuda.is_available():
... device = torch.device("cuda") # CUDAをdeviceとして定義
... y = torch.ones_like(x, device=device) # GPU上に直接テンソルを作成
... x = x.to(device) # .to() で、xをGPUに転送。.to("cuda")の記述も可
... z = x + y
... print(z)
... print(z.to("cpu", torch.double)) # .to で、CPUに転送し、いっしょにデータ型も変更
...
out:
tensor([2.6759], device='cuda:0')
tensor([2.6759], dtype=torch.float64)
出力結果 device=’cuda:0′ からGPUでCUDAテンソル演算ができたことがわかります。
CPU転送時にdtype=torch.doubleとしていることから、出力結果として倍精度浮動小数点数である dtype=torch.float64 が表示されています。
今回はPyTorchの公式チュートリアルに沿って、テンソルの基本的な利用方法についてみてきました。
Numpyと同じような操作が可能であることが実感できたのではないかと思います。
また、PyTorchが稼働するコンテナからGPUを使った計算処理の方法を紹介しました。
次回予告 第6回:OpenPoseによる関節点抽出・姿勢推定
OpenPose(オープンポーズ)というライブラリを使い、人が映った静止画や動画から関節点抽出・姿勢推定の方法を紹介します。
【連載企画】GPUコンテナ活用 【全6回】 |
---|
GPUコンテナとは何か?何が便利なのか?【第1回:GPUコンテナで速攻環境構築】 |
TensorFlowとKerasによるディープラーニング①【第2回:GPUコンテナで画像解析〜準備編〜】 |
TensorFlowとKerasによるディープラーニング②【第3回:GPUコンテナで画像解析〜実践編〜】 |
Chainerを使ったディープラーニング【第4回:GPUコンテナで機械学習する】 |
PyTorchで機械学習【第5回:GPUコンテナでテンソルの基本を理解する】 |