PyTorchのデータやパラメーターなどは、独自の「テンソル」形式で取り扱う。テンソルはNumPyライクに取り扱えるので難しくはない。テンソルの基本的な操作方法と、テンソル内の値のデータ型について、すばやく内容を確認できるチートシート形式で紹介する。
前回は、PyTorchの核となる部分の説明を行った。今回は、PyTorchの基礎部分といえる「テンソルとデータ型」をチートシート形式で確認する。
なお、本連載の第1回〜第3回までは続きものとなっている。
全3回の大まかな流れは以下の通りである。
(1)ニューロンのモデル定義
(2)フォワードプロパゲーション(順伝播)
(3)バックプロパゲーション(逆伝播)と自動微分(Autograd)
(4)PyTorchの基礎: テンソルとデータ型
(5)データセットとデータローダー(DataLoader)
(6)ディープニューラルネットのモデル定義
(7)学習/最適化(オプティマイザ)
(8)評価/精度検証
このうち、(1)〜(3)は前回説明済みである。今回は、(4)を説明する。それではさっそく説明に入ろう。※脚注や図、コードリストの番号は前回からの続き番号としている(第1回〜第3回は、切り離さず、ひとまとまりの記事として読んでほしいため連続性を持たせている)。
PyTorchのテンソルについては、前回でも既に何度か登場しており、例えばリスト1-3ではtorch.tensor([0.8])というコードでテンソル(torch.Tensor値)を作成した。PyTorchでデータや数値を扱うには、このテンソル形式にする必要がある。
とはいっても、この例で分かるように、Pythonの数値やリスト値をtorch.Tensor値に変換したり数値計算したりするのは、(数学計算ライブラリ「NumPy」に使い方が似せられているのもあって)難しくない。また、下記リンク先にある公式チュートリアルの内容も易しいので、これを見てつまずく人はほとんどいないと思われる。
よってここでは、詳しい説明は公式チュートリアルに任せ、基本的に同じ内容をまとめた「テンソルを作成/変換する基本的なコードのチートシート」を示すだけにする。ポイントなどは、コードのコメントとして記載した。
チートシートの内容で分からないところがあれば、上記の公式チュートリアルやAPIドキュメントの説明を参照してほしい(※英語のページではあるが、第1回の冒頭でも紹介したようにChromeの[日本語に翻訳]機能を使えば十分に日本語で読めるだろう)。
まずは、テンソルの中に含める数値について紹介しよう。PyTorchには、(NumPyのように)独自のデータ型(torch.dtypes)がある(表4-1)。
データ型 | dtype属性への記述 | Python/NumPy(np)の対応データ型 |
---|---|---|
Boolean(真偽値) | torch.bool | bool / np.bool |
8bitの符号なし整数 | torch.uint8 | int / np.uint8 |
8bitの符号付き整数 | torch.int8 | int / np.int8 |
16bitの符号付き整数 | torch.int16 / torch.short | int / np.uint16 |
32bitの符号付き整数 | torch.int32 / torch.int | int / np.uint32 |
64bitの符号付き整数 | torch.int64 / torch.long | int / np.uint64 |
16bitの浮動小数点 | torch.float16 / torch.half | float / np.float16 |
32bitの浮動小数点 | torch.float32 / torch.float | float / np.float32 |
64bitの浮動小数点 | torch.float64 / torch.double | float / np.float64 |
表4-1 PyTorchのデータ型 |
ただし、ここでいうデータ型とは「あるテンソルに含まれる全要素に共通の統一的なデータ型」であることに注意しよう。例えばtorch.floatの場合は、テンソル内の全要素の数値が「32bitの浮動小数点」として扱われるので注意してほしい。
種類は豊富だが、基本的に32bitのtorch.floatかtorch.intしか使わない。
注意が必要なのが、NumPyの浮動小数点の数値が(環境によっては)デフォルトで64bitになっているという点だ。そのため、NumPyの多次元配列値からPyTorchのテンソルに変換した際に、テンソルの中の数値がtorch.double型になってしまう。PyTorchのクラス内ではtorch.float型が前提となっているメソッドなどが多くあるため、このままではエラーになってしまう可能性が高い。よって、NumPyから変換したテンソルは、そのfloat()メソッドを呼び出してデータ型の変換を行う必要がある。この方法については、後述の『リスト4-7 チートシート「テンソルのデータ型の変換」』で示している。
以下では、シンプルにテンソルの使い方だけを示すため、print(x)などの出力に関するコードは極力、省略した。コードを実行して出力を確認したい場合は、例えばx = torch.empty(2, 3)というコードの後にprint(x)を追記してほしい。また、x.size()というコードは、print(x.size())のようにprint関数を適宜、自分で補ってほしい。
テンソルの新規作成は、第1回で既に説明した「数値を直接指定する方法」の他、empty()/rand()/zeros()/ones()といったメソッドでも行える。また、既存のテンソルと同じデータ型やサイズのものを新規作成する方法も提供されている。
import torch
import numpy as np
# テンソルの新規作成
x = torch.empty(2, 3) # 2行×3列のテンソル(未初期化状態)を生成
x = torch.rand(2, 3) # 2行×3列のテンソル(ランダムに初期化)を生成
x = torch.zeros(2, 3, dtype=torch.float) # 2行×3列のテンソル(0で初期化、torch.float型)を生成
x = torch.ones(2, 3, dtype=torch.float) # 2行×3列のテンソル(1で初期化、torch.float型)を生成
x = torch.tensor([[0.0, 0.1, 0.2],
[1.0, 1.1, 1.2]]) # 1行×2列のテンソルをPythonリスト値から作成
# 既存のテンソルを使った新規作成
# 「new_*()」パターン
y = x.new_ones(2, 3) # 2行×3列のテンソル(1で初期化、既存のテンソルと「同じデータ型」)を生成
# 「*_like()」パターン # 既存のテンソルと「同じサイズ」のテンソル(1で初期化、torch.int型)を生成
y = torch.ones_like(x, dtype=torch.int)
テンソルのサイズ取得やテンソルの次元数取得は、NumPy風に行えるので、NumPyユーザーにとっては楽である。テンソルのサイズ変更も簡単に行える。
# テンソルサイズの取得
x.size() # 「torch.Size([2, 3])」のように、2行3列と出力される
x.shape # NumPy風の記述も可能。出力は上と同じ
len(x) # 行数(=データ数)を取得する際も、NumPy風に記述することが可能
x.ndim # テンソルの次元数を取得する際も、NumPy風に記述が可能
# テンソルのサイズ変更/形状変更
z = x.view(3, 2) # 3行2列に変更
数学計算も、+などの演算子を使った四則演算の他、add()(足し算)やpow()(累乗)やmean()(平均)といったメソッドが用意されている。メソッド名は直感的な数学用語なので、特に迷うことはないだろう。入力して目的の計算メソッドが存在しなければ、「APIドキュメント: Math operations」を探すとよい。
# テンソルの計算操作
x + y # 演算子を使う方法
torch.add(x, y) # 関数を使う方法
torch.add(x, y, out=x) # outパラメーターで出力先の変数を指定可能
x.add_(y) # 「*_()」パターン。xを置き換えて出力する例(上の行と同じ処理)
PyTorchのテンソルでは、メソッド名の最後にアンダースコア(_)がある場合(例えばadd_())、「テンソル内部の置換(in-place changes)が起こること」を意味する。一方、アンダースコア(_)がない通常の計算の場合(例えばadd())は、計算元のテンソル内部は変更されずに、戻り値として「新たなテンソル」が取得できる仕様だ。
テンソルにインデックス指定でアクセスしたり、スライス機能でデータを抽出したりする方法は、PythonリストやNumPy多次元配列と同じである。
# インデクシングやスライシング(NumPyのような添え字を使用可能)
print(x) # 元は、2行3列のテンソル
x[0, 1] # 1行2列目(※0スタート)を取得
x[:2, 1:] # 先頭〜2行(=0行目と1行目)×2列〜末尾(=2列目と3列目)の2行2列が抽出される
「テンソルの要素値をPythonの数値に変換したい」という場面は非常に多いので、この機能はよく使うことになる。これには、(あまり直感的ではないが)テンソルのitem()メソッドを呼び出すだけである。
# テンソルの1つの要素値を、Pythonの数値に変換
x[0, 1].item() # 1行2列目(※0スタート)の要素値をPythonの数値で取得
「PyTorchのテンソルとNumPyの多次元配列値を相互に変換したい」というニーズはさらに多い。これも簡単で、(直感的な)<テンソル>.numpy()メソッドやtorch.from_numpy(<多次元配列値>)を呼び出すだけだ。
# PyTorchテンソルを、NumPy多次元配列に変換
b = x.numpy() # 「numpy()」を呼び出すだけ。以下は注意点(メモリ位置の共有)
# ※PyTorchテンソル側の値を変えると、NumPy多次元配列値「b」も変化する(トラックされる)
print ('PyTorch計算→NumPy反映:')
print(b); x.add_(y); print(b) # PyTorch側の計算はNumPy側に反映
print ('NumPy計算→PyTorch反映:')
print(x); np.add(b, b, out=b); print(x) # NumPy側の計算はPyTorch側に反映
# -----------------------------------------
# NumPy多次元配列を、PyTorchテンソルに変換
c = np.ones((2, 3), dtype=np.float64) # 2行3列の多次元配列値(1で初期化)を生成
d = torch.from_numpy(c) # 「torch.from_numpy()」を呼び出すだけ
# ※NumPy多次元配列値を変えると、PyTorchテンソル「d」も変化する(トラックされる)
print ('NumPy計算→PyTorch反映:')
print(d); np.add(c, c, out=c); print(d) # NumPy側の計算はPyTorch側に反映
print ('PyTorch計算→NumPy反映:')
print(c); d.add_(y); print(c) # PyTorch側の計算はNumPy側に反映
なお注意点として(メリットともいえるが)、そうやって変換されたテンソル/多次元配列は同じメモリ位置を共有するため、片方の数値を変更すれば、もう片方に即座に反映することになる。
先ほどの「データ型」で説明したように、NumPyの多次元配列からPyTorchのテンソルに変換した場合、float()メソッドを呼び出して「torch.float64」から「torch.float32」にデータ型を変換することが必要な場面は多い。
# データ型の変換(※変換後のテンソルには、NumPyの計算は反映されない)
e = d.float() # 「torch.float64」から「torch.float32」
なお、NumPyからPyTorchテンソルへの変換では、データ型は基本的に次のように変換される。
PyTorchでは、テンソルの計算にGPU(CUDAデバイス環境)を手軽に適用できる。
# NVIDIAのGPUである「CUDA」(GPU)デバイス環境が利用可能な場合、
# GPUを使ってテンソルの計算を行うこともできる
if torch.cuda.is_available(): # CUDA(GPU)が利用可能な場合
print('CUDA(GPU)が利用できる環境')
print(f'CUDAデバイス数: {torch.cuda.device_count()}')
print(f'現在のCUDAデバイス番号: {torch.cuda.current_device()}') # ※0スタート
print(f'1番目のCUDAデバイス名: {torch.cuda.get_device_name(0)}') # 例「Tesla T4」
device = torch.device("cuda") # デフォルトのCUDAデバイスオブジェクトを取得
device0 = torch.device("cuda:0") # 1番目(※0スタート)のCUDAデバイスを取得
# テンソル計算でのGPUの使い方は主に3つ:
g = torch.ones(2, 3, device=device) # (1)テンソル生成時のパラメーター指定
g = x.to(device) # (2)既存テンソルのデバイス変更
g = x.cuda(device) # (3)既存テンソルの「CUDA(GPU)」利用
f = x.cpu() # (3')既存テンソルの「CPU」利用
# ※(2)の使い方で、GPUは「.to("cuda")」、CPUは「.to("cpu")」と書いてもよい
g = x.to("cuda")
f = x.to("cpu")
# ※(3)の引数は省略することも可能
g = x.cuda()
# 「torch.nn.Module」オブジェクト(model)全体でのGPU/CPUの切り替え
model.cuda() # モデルの全パラメーターとバッファーを「CUDA(GPU)」に移行する
model.cpu() # モデルの全パラメーターとバッファーを「CPU」に移行する
else:
print('CUDA(GPU)が利用できない環境')
以上で、PyTorchの基礎部分「テンソルとデータ型」の確認が終わった。次回は、PyTorchによる基本的な実装方法と一連の流れを説明する。ここからが本連載のメインである。
Copyright© Digital Advantage Corp. All Rights Reserved.