はじめに
このシリーズは、Pythonの機械学習用フレームワークであるPyTorchの学習のまとめとして記述します。
今回はPyTorchの基礎中の基礎といった部分のまとめを記事とさせていただきました。伝えるためのものというよりも、要点をおさえて書くようにしているので読み物としては読みづらいかもしれませんが、ポイントの理解などに利用して頂ければと思います。
PyTorchとは
先にも述べましたが、PyTorchはPythonの機械学習用フレームワークです。機械学習用フレームワークといえば、Googleが開発したTensorFlowであったり、Kerasあるいはchainerなどがあります。
個人的主観が入りますが、これらをざっくりと分けると以下の感じかなという印象です。
フレームワーク | 主な特徴 |
---|---|
TensorFlow, Keras | 産業向け |
PyTorch, Chainer | 研究向け(カスタマイズ性が高い) |
また、機械学習を始めてみようと思った際に大半の人がTensorFlowから入ると思いますが、define and run
というデータ構造の定義と実行を別々に記述するという少し変わったルールがあるため、いまいち馴染めずにフェードアウトする方もいらっしゃるかと思います。
一方でPyTorchやChainerはdefine by run
を採用しているため、普段コーディングしているPythonのコードと同じようにコーディングすることができるため、親しみやすい(好みはありますが)のが特徴です。
ただし、少しとっつきにくいdefine and run
の仕組みは、最適化が容易であったり、先に定義している分計算速度が速いなどのメリットもあるそうです。一長一短あるわけですね。
それでは、実際にPyTorchの中身について見ていきましょう。
PyTorchの構成
PyTorchは以下のような構成になっています。
構成内容 | 説明 |
---|---|
torch | メインのネームスペースでTensorや様々な数学関数がこのパッケージに含まれる。NumPyの構造を模している |
torch.autograd | 自動微分のための関数が含まれる。自動微分のon/offを制御するコンテキストマネージャのenable_grad/no_gradや独自の微分可能関数を定義する際に使用する基底クラスであるFunctionなどが含まれる |
torch.nn | ニューラルネットワークを構築するための様々なデータ構造やレイヤーが定義されている。例えばConvolutionやLSTM、ReLUなどの活性化関数やMSELossなどの損失関数も含まれる |
torch.optim | 確率的勾配降下(SGD)を中心としたパラメータ最適化アルゴリズムが実装されている |
torch.utils.data | SGDの繰り返し計算を回す際のミニバッチを作るためのユーティリティ関数が含まれている |
torch.onnx | ONNX(Open Neural Network Exchange)1の形式でモデルをエクスポートするために使用する。 |
Tensorの生成と変換
Tensorの生成
Tensorの生成方法はいくつかあります。
import numpy as np
import torch
# 入れ子のlistを渡して作成
t = torch.tensor([[1, 2], [3, 4]])
# deviceを指定してGPUにTensorを作成
t = torch.tensor([[1, 2], [3, 4]], device = "cuda:0")
# dtypeを指定して倍精度のTensorを作る
t = torch.tensor([[1, 2], [3, 4]], dtype = "torch.float64")
# 0~9の数値で初期化された1次元のTensor
t = torch.arange(0, 10)
# 全値が0の100x100のTensorを作成し、toメソッドでGPUに転送
t = torch.zeros(100,100).to("cuda:0")
# 100x100のランダムなTensor
t = torch.random(100, 100)
Tensorの変換方法
TensorはNumPyのndarrayに簡単に変換できます。ただし、GPU上のTensorはそのままでは変換できず、一度CPU上に移す必要があります。
# numpyメソッドを使用してndarrayに変換
t = torch.tensor([[1, 2], [3, 4]])
nd_arr = t.numpy()
# GPU上のTensorをCPU上に移す
t = torch.tensor([[1, 2], [3, 4]], device = "cuda: 0")
t_cpu = t.to("cpu")
nd_arr = t_cpu.numpy()
# 上記を続けて記入
t = torch.tensor([[1, 2], [3, 4]], device = "cuda: 0")
nd_arr = t.to("cpu").numpy()
Tensorのインデクシング操作
Tensorでもndarrayと同様ににインデクシング操作2をサポートしています。指定可能な方法は以下。
- スカラーによる指定
- 添字リスト
- ByteTensorによるマスク配列3での指定
t = torch.tensor([[1, 2, 3], [4, 5, 6]])
# スカラーの添字で指定
t[0, 2] # tensor(3)
# スライスで指定
t[:, :2] # tensor([[1, 2], [4, 5]])
# 添字のリストで指定
t[:, [1, 2]] # tensor([[2, 3], [5, 6]])
# マスク配列を使用して3より大きい部分のみ選択
t[t > 3] # tensor([4, 5, 6])
# [0, 1]要素を100に置換
t[0, 1] = 100 # tensor([[1, 100, 3], [4, 5, 6]])
# スライスを使用した一括代入
t[:, 1] = 200 # tensor([[1, 200, 3], [4, 200, 6]])
# マスク配列を使用して特定条件の要素のみ置換
t[t > 10] = 20 # tensor([[1, 2, 3], [4, 5, 6]])
Tensorの演算
Tensorは四則演算や数学関数、線形代数計算などを行うことができます。ndarrayでもできることですが、特に行列積や特異値分解4などの線形代数計算はGPUが使用可能ということもあって大規模データの場合にはNumPy/SciPyを使用するよりも良いパフォーマンスが多々あります。
Tensorの四則演算
Tensorの四則演算の可否は以下の通りです。
Tensor | Tensor(同じ型) | 〇 |
Tensor | Tensor(異なる型) | ✕ |
Tensor | Pythonのスカラー | 〇 |
Tensor | ndarray | ✕ |
Tensor同士での四則演算でも同じ型である必要がある点が注意すべきポイントです。
四則演算を行う際には、ベクトルとスカラーや行列とベクトル間の演算でも自動的に次元が補完されます(ブロードキャスト)。
# ベクトル
v = torch.tensor([1, 2, 3])
w = torch.tensor([4, 5, 6])
# 行列
m = torch.tensor([[0, 1, 2], [10, 20, 30]])
n = torch.tensor([[3, 4, 5], [40, 50, 60]])
# ベクトル - スカラー
v_pl = v + 10 # tensor([11, 12, 13])
v_mi = v - 10 # tensor([-9, -8, -7])
v_mu = v * 10 # tensor([10, 20, 30])
v_di = v / 10 # tensor([0, 0, 0])
# ベクトル - ベクトル
v_pl = v + w # tensor([5, 7, 9])
v_mi = v - w # tensor([-3, -3, -3])
v_mu = v * w # tensor([4, 10, 18])
v_di = v / w # tensor([0, 0, 0])
# 行列とベクトル
v_pl = m + v # tensor([[ 1, 3, 5], [11, 22, 33]])
v_mi = m - v # tensor([[-1, -1, -1], [ 9, 18, 27]])
v_mu = m * v # tensor([[ 0, 2, 6], [10, 40, 90]])
v_di = m / v # tensor([[ 0, 0, 0], [10, 10, 10]])
# 行列と行列
v_pl = m + n # tensor([[3, 5, 7], [50, 70, 90]])
v_mi = m - n # tensor([[-3, -3, -3], [-30, -30, -30]])
v_mu = m * n # tensor([[0, 4, 10], [400, 1000, 1800]])
v_di = m / n # tensor([[0, 0, 0], [0, 0, 0]])
除算の際に出力が0となる箇所がありますね。お分かりかとは思いますが、演算の結果が小数点以下の値を示しても、tensorの型がintとなっているためです。回避したい場合はあらかじめfloatなどを指定しておきましょう。
PyTorchで利用可能な数学関数
PyTorchではTensorに対して様々な数学関数を用意しています。
Tensorの各値に作用する数学関数
関数名 | 概要 |
---|---|
abs | 絶対値 |
sin | 正弦 |
cos | 余弦 |
exp | 指数関数 |
log | 対数関数 |
sqrt | 平方根 |
集計関数
関数 | 概要 |
---|---|
sum | tensor内の値の合計 |
max | tensor内の最大値 |
min | tensor内の最小値 |
mean | tensor内の値の平均値 |
std | tensor内の値の標準偏差 |
線形代数の演算子
関数 | 概要 |
---|---|
dot | ベクトルの内積 |
mv | 行列とベクトルの積 |
mm | 行列と行列の積 |
matmul | 引数の種類によって自動的にdot、mv、mmを選択して実行 |
gesv | LU分解による連立方程式の解 |
eig, symeig | 固有値分解。symeigは対象行列用 |
svd | 特異値分解 |
どのような結果が出るのか確認してみましょう(結果はかなり長い値になるので表示していません)。
m = torch.randn(100, 10) # 100x10の行列テストデータを作成
v = torch.randn(10)
# 内積
torch.dot(v, v)
# 行列とベクトルの積
torch.mv(m, v)
# 行列積
torch.mm(m.t(), m)
# 特異値分解
u, s, v = torch.svd(m)
その他よく使う関数
関数名 | 概要 |
---|---|
view | Tensorの次元を変更する |
cat, stack | Tensor同士を結合する |
transpose | 次元を入れ替える |
自動微分
Tensorの持つrequires_grad
をTrue
にすると、自動微分を行うフラグが有効になります。ニューラルネットワークを扱う場合のパラメータやデータはこのフラグがすべて有効になっています。
requires_grad
が有効なTensorに様々な演算を積み重ねていくことで、計算グラフが構築され、backwardメソッドを呼ぶことでその情報から自動微分されます。
おわりに
今回は主にPyTorchの構成や機械学習を扱う上で必須となるTensorについてまとめました。最後までお読みいただいた皆さん、ありがとうございました。
次回以降少しずつニューラルネットワークの構築などを行っていこうと思いますので、ご興味ある方はぜひご覧頂ければと思います。
参考
杜世橋(2018年)『現場で使える!PyTorch開発入門 深層学習モデルの作成とアプリケーションへの実装』