PyTorch深層学習④テンソル:Tensor編
前回はNumPyを使ってテンソルに触れてみました。今回はPyTorchのTensorを取り扱います。
すでに学んだNumPyのテンソルの知識は、どのように役立つでしょうか。
画像データなどはNumPy形式のデータとして扱われることが多くPyTorchのTensorもNumPyのデータをインポート・エクスポートできるようになっています。Tensorは文字通りテンソルのライブラリです。PyTorchに標準で備わっています。
また、PyTorchのTensorはNumPyと非常に似た関数を持っており、NumPyをしているとTensorにもすぐになれることができます。だとしたら、なぜPyTorchはわざわざNumPyと似たようなテンソル機能を独自に持っているのでしょうか。他のライブラリのように直接に使うことはできなかったのでしょうか。
理由は二つあります。
PyTorchのTensorはGPUを利用して高速な計算ができるようになっています。NumPyではGPUを使うことはできません。
PyTorchのTensorには自動微分(Autograd)の機能があります。これはモデルの訓練中に使われる機能です。NumPyには自動微分の機能はありません。
GPUは必須ではありません。GPUがなければCPUを使って演算が可能です。しかし、GPUを利用できれば学習がより速く実行できます。なお、自動微分もモデルによる予測値の計算などをするだけなら必要はありません。しかし、学習を行う際には必須になります。
今回はGPUも自動微分も使いません。PyTorchのTensorでテンソルの演算などを実行して慣れるのが目的です。
Python環境の設定
Pythonの仮想環境を作ってPyTorchとNumPyとJupyterをインストールします。
# 適当にフォルダを作って移動する
mkdir learn_tensor
cd learn_tensor
# Pythonの仮想環境を作る
python3 -m venv venv
source venv/bin/activate
# pipを最新にしておく
pip install --upgrade pip
# PyTorchとNumPyとJupyterをインストール
pip install torch numpy jupyter
前回と同様にJupyterノートブックを立ち上げてPython3のノートブックを作成してください。Jupyterを使わずにコマンドラインからでも実行は可能ですが、Jupyterを使った方が便利だと思います。
jupyter notebook
前回の記事でJupyterノートブックの使い方を簡単に説明しているので、不安な方はそちらを参照してください。
まず、PyTorchをインポートします。また、NumPyもインポートしておきましょう、後でPyTorchとNumPyとの間でデータのやり取りをします。
import torch
import numpy as np
では、さっそくPyTorchのTensorを使ってみましょう。
Tensorを使ってみる
Pythonのリストで2次元テンソルを表現します。
# Pythonのリストで
values = [ [1, 2, 3], [4, 5, 6] ]
values
これをPyTorchのTensorに変換します。
x = torch.Tensor(values)
x
ここで値に小数点が付いています。Tensorではデフォルトのデータ型が32ビットのフロート(浮動小数点型)になっています。
x.dtype
この点は、NumPyの挙動と異なります。NumPyだと整数型の値を渡すと64ビット整数型になりました。
次のように各要素に数値を加算することができます。
x + 1
ただし、元のTensorの値は変わりません。
x
次のようにすると、Tensorそのものを変更します。
x += 1
x
次のようにしても同じです。
x = x + 1
2行3列目の値は0ベースのインデックスで次のように指定します。
x[1, 2]
これもNumPyと同じ方法です。
1行目の値を取り出すには次のようにします。
x[0]
ちなみに、2列目の値は次のように取り出せます。
x[:, 1]
Tensorのデータ型
Tensorのデータ型についてもう少し深掘りします。次のようにファンクションを読んでTensorを作ることができます。
x = torch.tensor(values)
x
ここで「おや?」と思った方は鋭いです。データ型をチェックします。
x.dtype
このように torch.tensor を使うとデータ型が入力されたデータによって整数型になりました。よって torch.tensor(ファンクション)と torch.Tensor (コンストラクト)で挙動が異なるので注意してください。
torch.tensor にフロート型の値を渡してみます。
float_values = [
[1., 2., 3.],
[4., 5., 6.]
]
x = torch.tensor(float_values)
x.dtype
32ビット浮動小数点型になりました。
次のようにデータ型を指定することもできます。
values = [
[1, 2, 3],
[4, 5, 6]
]
torch.tensor(values, dtype = torch.float32)
また、torch.FloatTensor(コンストラクタ)を使うこともできます。
x = torch.FloatTensor(values)
x
値に小数点がついていますね。データ型を確認しましょう。
x.dtype
torch.LongTensor(コンストラクタ)を使えば小数点の値がついた入力値でも整数型になります。
float_values = [
[1.5, 2.1],
[3.8, 9.2]
]
x = torch.LongTensor(float_values)
x
もちろん、データ型はビット64整数型です。
x.dtype
Tensorを作る様々な方法
torch.empty は指定されたサイズ(Shape)のTensorを作成します。メモリーを確保するだけで、値は初期化されません。
torch.empty((2, 3))
ここではたまたま全てゼロになっていますが、必ず毎回そうなるとは限りません。この機能を使う目的はテンソルを準備しておいて後から値を設定したい場合に、初期化する手間を省くためです。
torch.zeros はTensorを作る際に全ての要素を0に設定します。以下では、配列のサイズを(2, 3)と指定しており縦2横3の2次元テンソルになります。
torch.zeros((2, 3))
全ての要素値を0にする初期化が行われるので毎回必ず0になります。これを使うのは値を0に設定しておいて後からもっと値を加算・引き算する場合などです。
torch.ones はTensorを作る際に全ての要素を1に設定します。
torch.ones((2, 3))
torch.zeros_like は他のTensorと同じ形(Shape)で全ての要素がゼロに設定されたものを作ります。
values = [
[1, 2, 3],
[4, 5, 6]
]
x = torch.tensor(values)
torch.zeros_like(x)
torch.ones_like は他のTensorと同じ形(Shape)で全ての要素が1に設定されたものを作ります。
torch.ones_like(x)
torch.eye は対角行列で指定された数の1を対角線上に持つTensorを作ります。
乱数の生成
torch.rand は要素をランダムな数値に設定します。乱数は一様分布で、0以上1未満の値が返されます。
torch.rand(2, 3)
同じことを繰り返すたびに異なる値が返されます。
torch.random.manual_seed は乱数のシードを固定することで再現性を確保します。シードの値はなんでもよいのでここでは123としています。
torch.random.manual_seed(123)
torch.rand(2, 3)
何度繰り返しても同じ値が返されます。これは再現性を確保したいときに使います。
TensorのShape
shape はテンソルの各次元の軸数を返します。
x = torch.ones(2, 3)
x.shape
このTensorは2次元で、1次元目(縦)に2つの軸があり、2次元目(横)に3つの軸があります。そもそも torch.ones でオブジェクトを作成する際に指定しているので当たり前ですが。
よくテンソルを使った計算をしているときにShapeが噛み合わなくて計算ができなかったりすることがあります。その際にShapeを確認してデバッグしたりします。
なお、Shapeを変更することもできます。
x.reshape(3, 2)
これで2x3から3x2に変更されました。要素数が同じになるならばShapeを変更できます。
x.reshape(1, 6)
ここでは1x6に変更されましたが、要素数が6なので問題ありません。
最後に3次元テンソルを作ってみましょう。
x = torch.ones((2, 3, 4))
x
このテンソルのShapeは(2, 3, 4)です。つまり、(3, 4)の2次元テンソルが2つ入っていると考えることができます。
よって、最初の2次元テンソルは次のように返すことができます。
x[0]
このようにテンソルを生成する際の次元数はいくらでも増やすことができます。
NumPyとの連携機能
NumPy形式のデータをPyTorchのTensorに変換することができます。
x = np.ones((2, 3))
torch.from_numpy(x)
次のようにNumPyからTensorへ変換することもできます。
torch.tensor(x)
NumPyのデータ型を引き継いでTensorのデータ型が64ビット浮動小数点型になっています。PyTorchのモデルはデフォルトで32ビット型を扱うことが多いので、次のように32ビット型に変換することができます。
x = np.ones((2, 3))
x = torch.from_numpy(x).type(torch.float32)
x
データ型を確認すると32ビットの浮動小数点型になっています。
x.dtype
torch.from_numpy を使ってNumPyからTensorを作った場合、元のNumPyを参照しているのでNumPyの値を変更するとTensorの値も変わっているので注意してください。
x = np.ones((2, 3))
y = torch.from_numpy(x)
x[0, 0] = 10
y
y[0, 0] が 10 になっているのが確認できます。x を更新したのが y が作成された後であるにも関わらずなのにです。
なるべくコピーを減らして実行速度を速くする狙いだと思われますが、思わぬところでバグが発生する可能性があるので注意してください。
ところが、torch.tensor を使うとこのような挙動になりません。
x = np.ones((2, 3))
y = torch.tensor(x)
x[0, 0] = 10
y
y[0, 0] は元の値のままです。
PyTorchのTensorからNumPyへの変換は次のように行います。
x = torch.ones((2, 3))
y = x.numpy()
y
このNumPyオブジェクトはTensorから参照されているので、Tensor側を変更するとNumPyオブジェクトにも反映されます。
x = torch.ones((2, 3))
y = x.numpy()
x[0, 0] = 10
y
逆にNumPy側を変更するとTensorに反映されます。
x = torch.ones((2, 3))
y = x.numpy()
y[0, 0] = 10
x
Tensorで演算を行う
足し算
すでに加算の例を紹介しましたが、もっと他にいくつか演算をやってみましょう。
x = torch.ones(2, 3)
x + 1
Tensor同士で足し算もできます。
x1 = torch.tensor([[1, 2], [3, 4]])
x2 = torch.tensor([[5, 6], [7, 8]])
x1 + x2
要素ごとの足し算になっています。
また、次のようにも足し算を行えます。
torch.add(x1, x2)
Tensorを直接に変更するには次のように下線がついたメソッドを呼び出します。
x = torch.ones(2, 3)
x.add_(2)
x
Tensorそのものが変更されています。
Tensor同士の演算でも同様です。
x1 = torch.tensor([[1, 2], [3, 4]])
x2 = torch.tensor([[5, 6], [7, 8]])
x1.add_(x2)
x1
引き算
引き算も足し算と同様に行えます。
x = torch.tensor([[2, 3], [4, 5]])
x - 1
x1 = torch.tensor([[1, 2], [3, 4]])
x2 = torch.tensor([[5, 6], [7, 8]])
x2 - x1
次のようにメソッドを呼び出して引き算が行えます。
torch.sub(x2, x1)
Tensorを直接に更新するには次のようにします。
x = torch.ones(2, 3)
x.sub_(2)
x
Tensor同士の引き算も直接更新が行えます。
x1 = torch.tensor([[1, 2], [3, 4]])
x2 = torch.tensor([[5, 6], [7, 8]])
x2.sub_(x1)
x2
掛け算
掛け算は次のように行います。
x = torch.tensor([[1, 2], [3, 4]])
x * 2
要素ごとに掛け算されているのが確認できます。
Tensor同士の掛け算は次のように行います。
x1 = torch.tensor([[1, 2], [3, 4]])
x2 = torch.tensor([[5, 6], [7, 8]])
x1 * x2
これも要素ごとの掛け算になっているのがわかります。
掛け算のメソッドを使うこともできます。
torch.mul(x1, x2)
同じ結果になりました。mul は英語の multiply から来ています。
直接更新の掛け算は次のように行います。
x = torch.tensor([[1, 2], [3, 4]])
x.mul_(2)
x
Tensor同士での直接更新の掛け算は次のように行います。
x1 = torch.tensor([[1, 2], [3, 4]])
x2 = torch.tensor([[5, 6], [7, 8]])
x1.mul_(x2)
x1
割り算
割り算も掛け算同様で次のように行います。
x = torch.tensor([[1, 2], [3, 4]])
x / 2
Tensor同士でも同様です。
x1 = torch.tensor([[1, 2], [3, 4]])
x2 = torch.tensor([[5, 6], [7, 8]])
x1 / x2
割り算のメソッドも使えます。
x1.div(x2)
結果は同じです。div は英語の divide から来ています。
直接更新の割り算は div_ を使うのですが、データ型に注意してください。次のようにするとエラーになります。
x = torch.tensor([[1, 2], [3, 4]])
x.div_(2) # エラーになる
x
上記を実行するとエラーになります。
RuntimeError Traceback (most recent call last)
Cell In[83], line 3
1 x = torch.tensor([[1, 2], [3, 4]])
----> 3 x.div_(2)
5 x
RuntimeError: result type Float can't be cast to the desired output type Long
これはTensorが整数型なのに直接更新の割り算の結果が浮動小数点型になってしまうのでデータ型が合っていないためです。
よって、FloatTensorを使って同じ計算をしてみます。
x = torch.FloatTensor([[1, 2], [3, 4]])
x.div_(2)
x
今回は問題なく実行できました。
Tensor同士の割り算も行えます。
x1 = torch.FloatTensor([[1, 2], [3, 4]])
x2 = torch.FloatTensor([[5, 6], [7, 8]])
x1.div_(x2)
x1
内積
内積は二つのベクトル間の演算です。要素ごとに掛け算をして全ての値を足し合わせます。よって、二つのベクトルは同じ要素数である必要があります。
x1 = torch.FloatTensor([1, 2, 3])
x2 = torch.FloatTensor([5, 6, 7])
# x1[0] * x2[0] + x1[1] * x2[1] + x1[2] * x2[2]
torch.dot(x1, x2)
行列とベクトルの積
行列とベクトルの積では、次のように行列の各行(ベクトル)とベクトルの内積を計算します。
m1 = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
v1 = torch.tensor([1, 2, 3])
# | 1 2 3 | | 1 | | 14 |
# | 4 5 6 | | 2 | = | 32 |
# | 7 8 9 | | 3 | | 50 |
torch.mv(m1, v1)
行列同士の積
行列同士の席では@を演算子として使うことができます。
m1 = torch.tensor([[1, 2], [3, 4]])
m2 = torch.tensor([[2, 1], [4, 3]])
# | 1 2 | | 2 1 |
# | 3 4 | | 4 3 |
m1 @ m2
行列同士の積では行列 m1 の各行と行列 m2 の各列との内積を計算しています。よって、行列 m1 の列数と行列 m2 の行数が同じである必要があります。
次のようにメソッドも使えます。
torch.mm(m1, m2)
平均
以下は2次元テンソルに対して平均を計算しています。
x = torch.FloatTensor([[1, 2, 3], [4, 5, 6]])
x.mean()
計算されたのは全ての要素値の平均であることが確認できます。
$$
\frac{1 + 2 + 3 + 4 + 5 + 6}{6} = 3.5
$$
列ごとに平均を計算するには、次のように次元を指定します。
x.mean(dim = 0)
行ごとに計算するには次のようにします。
x.mean(dim = 1)
標準偏差
標準偏差は次のように計算します。
x = torch.FloatTensor([[1, 2, 3], [4, 5, 6]])
x.std()
これも列ごと(dim=0)、行ごと(dim=1)に計算することができます。
最小値と最大値
最小値は次のように計算します。
values = [
[10, 2, 9],
[ 4, 5, 6]
]
x = torch.tensor(values)
torch.min(x)
列ごとに最小値を求めることができます。
torch.min(x, dim=0)
このように dim=0 を指定した場合は、列ごとの最小値とそのインデックスが返されます。indices は index の複数形です。
次のようにそれぞれ別々に取り出すことができます。
行ごとの最小値は次のように計算します。
同様に最大値も計算できます。
最小値と最大値のインデックス
最小値のインデックスは次のように求めます。
torch.argmin(x)
インデックスはゼロから始まるので、1は2番目の意味です。ここでのインデックスはテンソルを平坦化した状態でのインデックスを指します。
これにインデックスの値1を指定すれば最小値を取り出せます。
列ごとに最小値のインデックスを求めることもできます。
values = [
[10, 2, 9],
[ 4, 5, 6]
]
x = torch.tensor(values)
torch.argmin(x, dim=0)
行ごとの最小値は次のように計算します。
最大値の計算は torch.argmax を使います。使い方は torch.argmin と同様になります。
その他の演算
Tensorを使った演算は他にもたくさんあります。いくつかここで紹介します。
fill_ を使うとTensorの全ての要素の値を設定することができます。
x = torch.empty((2, 3))
x.fill_(1)
x
sqrt を使うと平方根が計算できます。
x = torch.ones(2, 3) * 4
x.sqrt()
sqrt_ もあります。これはTensorを直接変更します。
torch.linspace では連続した値を生成できます。
torch.linspace(start=1.0, end=5.0, steps=5)
上記では、1.0 から 5.0 までの5つの数字を生成しています。
次のように生成する数字を増やすと、等間隔で指定された数だけの数字を生成します。
torch.linspace(start=1.0, end=5.0, steps=7)
clamp はTensorの要素値に対して最小値と最大値を指定します。
x = torch.randn(2, 3)
print(x)
x.clamp(0.2, 0.6)
clamp で 最小値が 0.2、最大値が 0.6 に制限されています。
なお torch.randn は乱数を標準正規分布に従って生成します。randn の最後の n はnormal distribution (正規分布)から来ています。
torch.cat は複数のTensorを連結します。
x1 = torch.ones(2, 2)
x2 = torch.zeros(2, 2)
torch.cat((x1, x2), dim=0)
dim=0 と指定したので縦に連結しています。
dim=1 ならば横に連結します。
torch.cat((x1, x2), dim=1)
3つ以上のTensorを連結することもできます。
x1 = torch.ones(2, 2)
x2 = torch.zeros(2, 2)
x3 = torch.ones(2, 2) * 4
torch.cat((x1, x2, x3), dim=0)
注意するべきなのは連結する次元以降での要素数が同じである必要があります。
例えば、次のような場合はエラーになります。
x1 = torch.zeros(10, 2)
x2 = torch.ones(2, 2)
torch.cat((x1, x2), dim=1)
dim=0 ならば問題ありません。
x1 = torch.zeros(10, 2)
x2 = torch.ones(2, 2)
torch.cat((x1, x2), dim=0)
次元がたくさんあっても指定された次元より後の各次元での要素があっていれば連結が可能です。
x1 = torch.zeros(2, 3, 4)
x2 = torch.zeros(3, 3, 4)
torch.cat((x1, x2), dim=0)
次の例では dim=1 で連結しています。
x1 = torch.zeros(2, 3, 4)
x2 = torch.zeros(2, 5, 4)
z = torch.cat((x1, x2), dim=1)
z
Shapeを確認するとどこが連結されたのかが分かりやすいです。
z.shape
まとめ
以上、PyTorch Tensorの機能をいくつか紹介しました。まだ、たくさんあるので必要に応じてPyTorchのドキュメントを参照するか、Google検索するか、Bing AIやChatGPTに尋ねるなどしていくと良いと思います。
torch — PyTorch 2.0 documentation
次回は、PyTorchによるデータセットの取り扱い方を解説します。
(続く)
この記事が気に入ったらチップで応援してみませんか?