Chapter 03

PyTorch の基本

ろぐみ
ろぐみ
2023.02.02に更新

PyTorch

この章では Deep Learning の構造や学習がどうなっているのかを説明するよりも前に、まず PyTorch の基本的な使い方を学びます。
PyTorch は Python 用の機械学習ライブラリです。有名な Python 数値計算ライブラリに NumPy がありますが、PyTorch も NumPy とよく似たインターフェースをしています。
さらに PyTorch は GPU を使った高速な計算ができるように設計されています。

テンソル(Tensor)

テンソルという言葉を聞いたことはありますか?
Tensorflow の Tensor と同じです。

行列あるいはベクトル、スカラーという言葉は聞いたことがありますか?ありますね?
テンソルはその行列やベクトルの考え方をより拡張させたものです。
0階のテンソルがスカラー、1階のテンソルがベクトル、2階のテンソルが行列に相当します。
3階のテンソルは行列の集まり、4階のテンソルは3階のテンソルの集まりというように、階数が増えるごとに次元が増えていきます。

ぐえ〜〜となりそうですが、待ってください。プログラムを組んだことがある方なら、このような概念と既に出会ったことがあるはずです。

scalar = 3  # ただの量(1つの次元を持たない数字)

vector = [3, 1, 2]  # 1次元の配列?

matrix = [  # 2次元の配列?
  [2, 9, 4],
  [7, 5, 3],
  [6, 1, 8],
]

tensor = [  # 3次元の配列?
  [
    [2, 9, 4],
    [7, 5, 3],
    [6, 1, 8],
  ],
  [
    [2, 9, 4],
    [7, 5, 3],
    [6, 1, 8],
  ],
  [
    [2, 9, 4],
    [7, 5, 3],
    [6, 1, 8],
  ],
]

プログラミングに馴染みのある方であれば、テンソルを多次元配列と捉える[1]ことで理解しやすくなると思います。

テンソルの "Shape"

概念を理解していただいたところで、数え方について確認をします。
例えば以下のような行列を想定しましょう。
これはまだ数式じゃない、ただの数の集まりなのでセーフ。

\begin{pmatrix} 2 & 9 \\ 7 & 5 \\ 6 & 1 \\ \end{pmatrix}

このような行列は 3\times2 の行列です。3\times2 というのは、行が3つ、列が2つあるという意味です。

3\times2 の行列だ、と言われたらこのような形を想像してください。

逆に 2\times3 の行列だ、と言われたら

\begin{pmatrix} 2 & 9 & 4 \\ 7 & 5 & 3 \\ \end{pmatrix}

こんな形を想像してくれると嬉しいです。

このような n\times m のような表現を Shape と呼びます。

行列は2階のテンソルとみなせるのでした。
では3階のテンソルの場合はどうでしょうか?

例えば 2\times3\times4 のテンソルは、2\times3 の行列が4つあると考えることができるので、以下のような形が思い浮かびそうです。

でもちょっと待って下さい。2つの 3\times4 行列があるとも考えられそうです。(なんかこういう考え方しちゃだめって小学校で教えられそう)

3階以上のテンソルの形を図示する場合の解釈は少し難しいです。
PyTorch ではどのように表現されているのか、実際に見てみましょう。

import torch

# まずは 2x3x4 = 24 個の数字を持つベクトルを作成
vector = torch.tensor([i for i in range(24)])

# このベクトルを 2x3x4 の順番でテンソルに変換
tensor_pattern = vector.view(2, 3, 4)
print(tensor_pattern)

Python に慣れていない方は [i for i in range(24)] という書き方に戸惑うかもしれません。これは Python のリスト内包表記と呼ばれるもので、以下と同じ意味です。

vector = []
for i in range(24):
  vector.append(i)

つまり 0~23 の数字を持つベクトルを作成しているということです。
リスト内包表記は Python ではよく使われるので、覚えておくと便利です。

次に tensor_pattern = vector.view(2, 3, 4) という行を見てみましょう。
view() というメソッドは、テンソルの形を変換するためのメソッドです。
引数には、変換後のテンソルの形を渡せばよいです。

今回は view(2, 3, 4) という引数を渡しているので、順に考えると 2 \times 3 \times 4 のテンソルに変換されます。

では結果を見てみましょう。

tensor(
[[[ 0,  1,  2,  3],
  [ 4,  5,  6,  7],
  [ 8,  9, 10, 11]],

 [[12, 13, 14, 15],
  [16, 17, 18, 19],
  [20, 21, 22, 23]]]
)

このように 3\times4 の行列が2つある、という形になっています。
というわけで後者の扱い方である、2つの 3\times4 行列があるという解釈がこの世界ではされていそうです。算数の先生大激怒です。

テンソルの演算

ではテンソルの演算について見ていきましょう。

足し算

足し算は、要素ごとに足し算を行います。

import torch

# 2x3x4 のテンソルを作成
tensor1 = torch.tensor([i for i in range(24)]).view(2, 3, 4)
tensor2 = torch.tensor([i for i in range(24)]).view(2, 3, 4)

# 足し算
tensor3 = tensor1 + tensor2
print(tensor3)
tensor(
[[[ 0,  2,  4,  6],
  [ 8, 10, 12, 14],
  [16, 18, 20, 22]],

 [[24, 26, 28, 30],
  [32, 34, 36, 38],
  [40, 42, 44, 46]]]
)

このように同じ位置にある数字が足し算されていることがわかります。
引き算も同様です。

スカラー倍

スカラー倍(ただの2倍とか1.4倍とかそういうの)は、テンソルの各要素に対してスカラー倍を行います。

import torch

# 2x3x4 のテンソルを作成
tensor1 = torch.tensor([i for i in range(24)]).view(2, 3, 4)

# スカラー倍
tensor2 = tensor1 * 3
print(tensor2)
tensor(
[[[ 0,  3,  6,  9],
  [12, 15, 18, 21],
  [24, 27, 30, 33]],

 [[36, 39, 42, 45],
  [48, 51, 54, 57],
  [60, 63, 66, 69]]]
)

このように各要素に対して3倍されていることがわかります。
スカラーによる割り算も同様です。

テンソル同士の掛け算

PyTorch におけるテンソル同士の掛け算は複数の方法があります。

かなり難しいので、以下だけ押さえてください。

  • 以下の3つのメソッドがよく使われる(他にもある)
    • torch.dot()
    • torch.bmm()
    • torch.matmul()
      • こいつはとくにヤバいから逃げていい
  • 2つのテンソルの shape の組み合わせによっては計算できないことがある
    • 計算できないときは、例外が発生する
    • Deep Learning の世界では転置(transpose())や view() などを使って shape を変換することで計算できるようにすることがある

Deep Learning の入りは怖くないよ、ってことを体験する本なので、恐怖を覚えたら飛ばしてください

ちょっとくらい見てやるか、という方は以下を読みつつ、Colab で実行してみてください。

dot

1次元ベクトルの内積を計算します。

import torch

tensor1 = [2, 3]
tensor2 = [2, 1]

# dot
output = torch.dot(torch.tensor(tensor1), torch.tensor(tensor2))
print(output.shape)
print(output)
torch.Size([])
tensor(7)

ベクトル同士の内積はスカラーになります(高校数学)。
よって0階のテンソルとみなせるので shape は空になります。

bmm

bmm はバッチ行列積(batch matrix multiplication)を計算します。
つまりバッチ同士で行列積を計算します。
バッチとは、複数のデータのまとまり、みたいなものです。
例えば、画像のバッチとは、複数枚の画像のまとまりと言えます。

3階のテンソル (b \times m \times n)(b \times n \times p) の bmm を考えるとします。

ここで、行列の積の形は、(m \times n) \times (n \times p) = (m \times p) と変形します。

bmm では最初の次元をバッチの数とみなすので、残り後ろの2つの次元を行列とみなして行列積を計算します。

したがって、bmm は (b \times m \times n) \times (b \times n \times p) = (b \times m \times p) となります。

import torch

# 2x3x4 のテンソルをランダムに作成
tensor1 = torch.randn(2, 3, 4)
# 2x3x4 のテンソルをランダムに作成
tensor2 = torch.randn(2, 4, 3)

# bmm
tensor3 = torch.bmm(tensor1, tensor2)
print(tensor3.shape)
torch.Size([2, 3, 3])

matmul

これ、かなり複雑なのでドキュメントを読んでください(逃げ)
https://pytorch.org/docs/stable/generated/torch.matmul.html#torch.matmul

import torch

# 2x3x4 のテンソルをランダムに作成
tensor1 = torch.randn(2, 3, 4)
# 2x4x5 のテンソルをランダムに作成
tensor2 = torch.randn(2, 4, 5)

# matmul
tensor3 = torch.matmul(tensor1, tensor2)
print(tensor3.shape)
torch.Size([2, 3, 5])

恐ろしくなってきましたね。
ここまで読んでくれてありがとうございます。

脚注
  1. 厳密には別物らしいですが、ここでその議論をしても進まないので飲み込んでください。 ↩︎