2019/9/29 投稿
2019/11/8 やや見やすく編集(主観)
2020/2/1 Tensor型の微分計算の解説Link追加
0. この記事の対象者
- pythonを触ったことがあり,実行環境が整っている人
- pyTorchを初めて触る人
- pyTorchのTensor型をしっかり理解したい人
- pyTorchでの機械学習でTensor型dataをどう使っているかを知りたい人
1. はじめに
昨今では機械学習に対しpythonという言語が主に使用され,さらにmoduleとしてpyTorchというものが使用されることがある.
今回はそのpyTorchを使用するための前準備としてTensor型というものの説明をしていく.
ただしあくまで参考程度にしてほしいということと,簡潔に言うために間違った表現や言い回しをしている場合があるかもしれないが,そこはどうかご了承していただきたい.
2. Tensor型とは
正確に言えば「torch.Tensor」というもので,ここではpyTorchが用意している特殊な型と言い換えてTensor型というものを使用する.
実際にはnumpyのndarray型ととても似ており,ベクトル表現から行列表現,それらの演算といった機能が提供されている.
何が違うかというとTensor型はGPUを使用して演算等が可能である点だ.
多数の機能を持ち,それらを紹介しきることはとても難しいため詳しくは以下Linkを見てほしい.
3. pyTorchのインストール
pyTorchを初めて使用する場合,pythonにはpyTorchがまだインストールされていないためcmdでのインストールをしなければならない.
下記のLinkに飛び,ページの下の方にある「QUICK START LOCALLY」で自身の環境のものを選択し,現れたコマンドをcmd等で入力する(コマンドをコピペして実行で良いはず).
4. Tensor型のdata宣言
早速Tensor型のdataを作っていくのだが,まずはpyTorchを使用するためにimportをする.
以下からはcmd等ではなくpythonファイルに書き込んでいく.
import torch
続いて以下からは大きく分けてよく使う
- 0または1埋めされたdata
- 初期値を自分で決めたdata
- ランダムなdata
の3つを紹介する.
ただし,注意してほしいのがここで書いた方法以外の書き方も存在しているので自分でやりやすいものを使ってほしい.
4-1. 0または1埋めされたdata
以下にコードとその出力を示す.
a = torch.zeros([2, 4], dtype=torch.int32)
b = torch.ones([2, 3, 3])
print(a)
print(b)
------'''以下出力結果'''--------
tensor([[0, 0, 0, 0],
[0, 0, 0, 0]], dtype=torch.int32)
tensor([[[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]],
[[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]]])
このように0または1のみのdataを作成できた.
dataは引数のlist型,[2,4]などから形が決まり,この場合は2×4の行列ができる.
もちろんbのように3次元以上のdataも作ることができ,この場合2×3×3のdataになっている.
aのように宣言時に「dtype」を決めれば各要素のtypeを決めることもできる.その場合出力にある通りtype情報が入る.
bのようにtype宣言をしなければtypeは自動でtorch.FloatTensorになる.
またTensor型の利点であるGPUの使用は以下のようにすればよい.
device = torch.device('cuda:0')
c = torch.ones([4], dtype=torch.float64, device=device)
d = torch.zeros([4, 1])
d = d.to(device)
print(c)
print(d)
------'''以下出力結果'''--------
tensor([1., 1., 1., 1.], device='cuda:0', dtype=torch.float64)
tensor([[0.],
[0.],
[0.],
[0.]], device='cuda:0')
1行目の「device = torch.device('cuda:0')」はcuda:0というGPUを使うことを宣言している.
もちろんCPUを使用したい場合はcpuとすれば使用できる.
またcのように宣言時に書き込む方法と,dのように「xxx.to(device)」とする方法があるが,どちらも結果に変わりはない.
また,この例のように行ベクトル,列ベクトルももちろん作成できる.
4-2. 初期値を自分で決めたdata
以下にコードと出力を示す.
import numpy as np
a = torch.tensor([1, 2, 3, 4, 5], dtype=torch.int32)
b = torch.tensor(np.array([[1, 2, 3], [4, 5, 6]]))
print(a)
print(b)
------'''以下出力結果'''--------
tensor([1, 2, 3, 4, 5], dtype=torch.int32)
tensor([[1, 2, 3],
[4, 5, 6]])
このようにlist型,またはnumpyによるndarrayもTensor型のdataに変換できる.
GPUの使用は同様にできる.
もちろん以下のように単一の値も生成できる.
c = torch.tensor(2.5)
print(c)
------'''以下出力結果'''--------
tensor(2.5000)
4-3. ランダムなdata
以下にコードと出力を示す.
a = torch.Tensor(3, 4)
b = torch.rand(2, 3, 3)
print(a)
print(b)
------'''以下出力結果'''--------
tensor([[2.5226e-18, 1.6898e-04, 1.0527e-11, 7.7195e-10],
[1.6785e-07, 4.1200e-11, 4.2891e-08, 2.9575e-18],
[6.7333e+22, 1.7591e+22, 1.7184e+25, 4.3222e+27]])
tensor([[[0.6788, 0.1821, 0.5919],
[0.0818, 0.9710, 0.5956],
[0.5885, 0.7288, 0.3226]],
[[0.6460, 0.5977, 0.0555],
[0.7144, 0.8958, 0.3947],
[0.0432, 0.8336, 0.2066]]])
どちらも形だけ指定すればランダムな値を持ったdataが返ってきている.
aは極小の値をランダムに生成し,bは[0,1)の一様分布からランダムに生成している.
また,指定した値までの整数による乱数は以下のようにする.
c = torch.randint(100, (3, 4))
d = torch.randint(10, 20, (5, ))
print(c)
print(d)
------'''以下出力結果'''--------
tensor([[58, 25, 5, 85],
[64, 78, 2, 17],
[49, 87, 92, 58]])
tensor([11, 15, 11, 18, 15])
最初の引数で乱数の上限を指定し,cのように1つの引数だけなら0からその値まで,dのように2つ引数があればその範囲での乱数となる.
5. dataの参照と取り出し
生成したdataは通常のarrayのように参照できる.
以下に例を示す.
a = torch.tensor(np.array([[1, 2, 3, 4], [5, 6, 7, 8]]))
print(a)
print(a[0])
print(a[0, 1])
print(a[0][1])
print(a[:,1])
print(a[1][1:3])
------'''以下出力結果'''--------
tensor([[1, 2, 3, 4],
[5, 6, 7, 8]])
tensor([1, 2, 3, 4])
tensor(2)
tensor(2)
tensor([2, 6])
tensor([6, 7])
この例のように[0,1]と[0][1]の参照はもちろん同じであるし,スライス[:]も使用できる.
注意すべきはa[1][1:3]は行列の1行目と1列,2列の要素を取り出しているのだが,1:3は1列目から3-1=2列目の要素を取り出し,その返り値は列ベクトルでなく行ベクトルで返ってきている.
また,この取り出した値,またはベクトルは全てTensor型になっている.
ただの値を取り出したい場合は以下のようにする.
a = torch.tensor(np.array([[1, 2, 3, 4], [5, 6, 7, 8]]))
print(a)
print(a[0][1].item())
------'''以下出力結果'''--------
tensor([[1, 2, 3, 4],
[5, 6, 7, 8]])
2
この「.item()」がただの値を取り出してくれる.
注意してほしいのは,ベクトルや行列はこの方法では取り出せないということである.
6. Tensor型のもう1つのメリットのgrad
Tensor型はGPUを使用できることがメリットだといったが,実はさらに勾配情報(gradient)というものが持てる.
これは機械学習にはとても重要な情報で,パラメータの更新の際に必要となる.なぜ必要かなどはここでは割愛するため,自身で調べてほしい.
以下に例を示す.
x = torch.tensor(2.5, requires_grad=True)
y = 4.5*x + 2
y.backward()
print(y)
print(x)
print(x.grad)
------'''以下出力結果'''--------
tensor(13.2500, grad_fn=<AddBackward0>)
tensor(2.5000, requires_grad=True)
tensor(4.5000)
まず,この例はy=4.5x+2という1次関数を考えている.
xは2.5という値を持ち,宣言時に勾配情報を持てるように「requires_grad=True」とする.
2行目でyに演算結果を代入し,3行目で勾配情報を計算させている.
この「xxx.backward()」がそのxxxの今までの演算を全て遡って,requires_grad=Trueである変数に勾配情報を持たせる.
yの出力よりgrad_fn=<AddBackward0>はyの演算過程にrequires_grad=Trueの変数があり,backward()可能であることを示している.
ただのxの出力を見たらわかるようにただのxには勾配情報はない.
勾配情報はx.gradとすることで初めて見れる.
ちなみに勾配とはbackward()したyをその変数(今回はx)で微分した時の値である.
微分周りの詳しい動作は以下のLinkを参照してほしい.
7. dataのsize,shape
作成したdataのsizeやshapeを確認することはデバック時に必要になる.
以下に例を示す.
a = torch.ones(2,3,5)
print(a.size())
print(a.shape)
print(a.shape[1])
------'''以下出力結果'''--------
torch.Size([2, 3, 5])
torch.Size([2, 3, 5])
3
size()とshapeはどちらも全く同じ出力を得ることができ,さらにその中の要素にも配列のように参照できる.
注意すべきは参照した値は一番最後の出力のようにTensor型ではないという点である.
8. ndarrayとTensorの行き来
Tensorが機械学習に優れていることはここまで話してきたが,ndarrayも同様に優れた点がある.
例えば画像処理やグラフの出力等はndarrayの方がいろいろなmoduleを使え,処理しやすい点がある.
なので,ndarrayとTensorを行き来できるようにしておくことが大切である.
以下にそれぞれについて説明をする.
8-1. ndarray ⇒ Tensor
以下に例を示す.
a = np.random.randn(2,3)
b = torch.from_numpy(a)
print(a)
print(b)
------'''以下出力結果'''--------
[[-1.41878985 0.56327099 1.13779486]
[ 0.33255026 0.75258005 1.15813658]]
tensor([[-1.4188, 0.5633, 1.1378],
[ 0.3326, 0.7526, 1.1581]], dtype=torch.float64)
ここで重要なことは,from_numpy()は引数のndarrayをTensor型に変換しているのだが,そのdtypeは自動で決定されている点だ.
この場合のdtype=torch.float64はtorch.DoubleTensorを表す.
今ここでは悪い影響がないように見えるが,機械学習をしていくとdtype=torch.float32であるtorch.FloatTensorでないとダメな時がよくある.
そのため,以下のように事前にtypeを指定してやればよい.
a = np.random.randn(2,3).astype(np.float32)
b = torch.from_numpy(a)
print(a)
print(b)
------'''以下出力結果'''--------
[[-1.41878985 0.56327099 1.13779486]
[ 0.33255026 0.75258005 1.15813658]]
tensor([[-1.4188, 0.5633, 1.1378],
[ 0.3326, 0.7526, 1.1581]])
これでdtypeの何も出力がないということはdtype=torch.float32である.
またもう1つ注意点として以下のようなことがある.
a = np.random.randn(3).astype(np.float32)
print(a)
b = torch.from_numpy(a)
b[0] = -1
print(a)
------'''以下出力結果'''--------
[ 0.19150276 -0.8219755 -0.05888553]
[-1. -0.8219755 -0.05888553]
このようにbの要素を変更してもaの要素が変わってしまう.これはbはaと同じメモリをシェアしているからである.
解決策としては以下のようにcopyを使う.
a = np.random.randn(3).astype(np.float32)
print(a)
b = torch.from_numpy(a.copy())
b[0] = -1
print(a)
------'''以下出力結果'''--------
[-1.3876773 1.7478094 -0.66189164]
[-1.3876773 1.7478094 -0.66189164]
8-2. Tensor ⇒ ndarray
以下に例を示す.
a = torch.tensor(np.array([[1, 2, 3, 4], [5, 6, 7, 8]]))
b = a.numpy()
print(a)
print(b)
------'''以下出力結果'''--------
tensor([[1, 2, 3, 4],
[5, 6, 7, 8]])
[[1 2 3 4]
[5 6 7 8]]
これも同様にbはaのメモリをシェアしているため以下のようにすればよい.
a = torch.tensor(np.array([1, 2, 3, 4]))
print(a)
b = a.clone().numpy()
b[0] = -1
print(a)
------'''以下出力結果'''--------
tensor([1, 2, 3, 4])
tensor([1, 2, 3, 4])
ndarray型と違いTensor型はclone()を使えばcopyされる.
ここで注意すべきは,Tensor型は勾配情報の保持とGPU使用が可能だったが,ndarray型はそんなことはできないという点だ.
以下に例を示す.
device = torch.device('cuda:0')
x = torch.ones([2, 2], device=device, requires_grad=True)
x2 = x.clone().numpy()
------'''以下出力結果'''--------
TypeError Traceback (most recent call last)
<ipython-input-84-813722481863> in <module>
1 device = torch.device('cuda:0')
2 x = torch.ones([2, 2], device=device, requires_grad=True)
----> 3 x2 = x.clone().numpy()
TypeError: can't convert CUDA tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory first.
まずxがGPUを使用しているからndarrayにできないとエラーが出る.
GPUからCPUにする方法はわかるから書き換えると以下のようになる.
device = torch.device('cuda:0')
x = torch.ones([2, 2], device=device, requires_grad=True)
device2 = torch.device('cpu')
x = x.to(device2)
x2 = x.clone().numpy()
------'''以下出力結果'''--------
RuntimeError Traceback (most recent call last)
<ipython-input-85-80f753c52561> in <module>
3 device2 = torch.device('cpu')
4 x = x.to(device2)
----> 5 x2 = x.clone().numpy()
RuntimeError: Can't call numpy() on Variable that requires grad. Use var.detach().numpy() instead.
今度のエラーはxが勾配情報を持つからndarrayにできないと言っている.
この勾配情報を無視してdataを取得する方法を以下に示す.
device = torch.device('cuda:0')
x = torch.ones([2, 2], device=device, requires_grad=True)
device2 = torch.device('cpu')
x = x.to(device2)
x2 = x.detach().clone().numpy()
print(x2)
------'''以下出力結果'''--------
[[1. 1.]
[1. 1.]]
この「detach()」はTensor型から勾配情報を抜いたものを取得する.
これでndarrayに変換できた.
9. ひとこと
今回はpyTorchが用意するTensor型の説明をした.
もちろんここで説明する以上にTensorにはたくさんの機能がある.
だが,ここでの基礎がわかれば他の操作も大体わかるのではないだろうかと思う.
読みづらい点も多かったと思うが読んでいただきありがとうございます.