ネットワークの重みを表示する

学習の過程で重みが更新されているかを確認したいときに、毎回とまどいながら設定していましたので、備忘的に書いておくことにします。

例として、torchvisionで読み込んだVGG16の重みを確認してみます。

学習済みモデルの読み込み

import numpy as np
import torch
import torchvision.models as models

# VGG16のpretrainモデルを読み込み
model = models.vgg16(pretrained=True)

まずは、読み込んだモデルの形状を確認します。

print(model)

VGG(
(features): Sequential(
(0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU(inplace=True)
(2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(3): ReLU(inplace=True)
(4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(6): ReLU(inplace=True)
(7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(8): ReLU(inplace=True)
(9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(11): ReLU(inplace=True)
(12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(13): ReLU(inplace=True)
(14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(15): ReLU(inplace=True)
(16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(18): ReLU(inplace=True)
(19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(20): ReLU(inplace=True)
(21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(22): ReLU(inplace=True)
(23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(25): ReLU(inplace=True)
(26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(27): ReLU(inplace=True)
(28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(29): ReLU(inplace=True)
(30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
(classifier): Sequential(
(0): Linear(in_features=25088, out_features=4096, bias=True)
(1): ReLU(inplace=True)
(2): Dropout(p=0.5, inplace=False)
(3): Linear(in_features=4096, out_features=4096, bias=True)
(4): ReLU(inplace=True)
(5): Dropout(p=0.5, inplace=False)
(6): Linear(in_features=4096, out_features=1000, bias=True)
)
)

VGG16は、「features」に13の畳み込み層、「classifier」に3つの全結合層が存在しているのがわかります。13+3=16なので、VGG16と呼ばれているわけですね。

重みの取り出し

では、ここに含まれている重みを見るにはどうすればいいでしょうか。重みは、モデルの「state_dict」メソッドに辞書形式で格納されています。
state_dictを取り出して、表示してみます。

# state_dictの呼び出し
state_dict = model.state_dict()

# 項目表示
print(state_dict.keys())

odict_keys([‘features.0.weight’, ‘features.0.bias’, ‘features.2.weight’, ‘features.2.bias’, ‘features.5.weight’, ‘features.5.bias’, ‘features.7.weight’, ‘features.7.bias’, ‘features.10.weight’, ‘features.10.bias’, ‘features.12.weight’, ‘features.12.bias’, ‘features.14.weight’, ‘features.14.bias’, ‘features.17.weight’, ‘features.17.bias’, ‘features.19.weight’, ‘features.19.bias’, ‘features.21.weight’, ‘features.21.bias’, ‘features.24.weight’, ‘features.24.bias’, ‘features.26.weight’, ‘features.26.bias’, ‘features.28.weight’, ‘features.28.bias’, ‘classifier.0.weight’, ‘classifier.0.bias’, ‘classifier.3.weight’, ‘classifier.3.bias’, ‘classifier.6.weight’, ‘classifier.6.bias’])

これがVGG16が保持・学習する重み群です。名前についている番号は、上で表示したmodelのレイヤ番号(カッコ内の項番)に対応します。

state_dictは辞書形式のため、例えば7番目のfeaturesの重みを確認したいときは、以下のように呼び出せます。

print(state_dict['features.7.weight'])

tensor([[[[ 2.5788e-02, -1.9852e-02, -1.0697e-02], [-1.6114e-02, -4.1759e-03, 8.4582e-03], [ 4.0309e-03, 1.8973e-02, 3.8059e-02]], [[-5.4261e-02, -2.9872e-02, 1.1506e-02], [-1.8266e-02, -1.5708e-02, -1.2726e-02], [ 2.0867e-02, -5.6425e-03, -5.1218e-04]], [[ 1.8961e-02, -2.3766e-03, 6.1427e-03], [-5.1761e-02, -1.7272e-02, 8.7075e-03], [-4.4383e-02, 2.3661e-02, 9.0710e-02]], …, (以下略)

辞書形式でテンソルが格納されているだけなので、ここまでできてしまえば、情報を収集するのは慣れたやり方でできます。

# 重みのサイズを確認
print(state_dict['features.7.weight'].size()) 

# 一部の重みだけを表示
print(state_dict['features.7.weight'][0,0,0,0])

torch.Size([128, 128, 3, 3])
tensor(0.0258)

中身の確認はこれでOKです。

乱数を固定する

CNNの既存コードを見ていると、torch.manual_seed()なんていう一文があります。
おまじないみたいなものだろうと全然気にしないでいたのですが、調べてみたら深い意味をもつものでしたので、備忘のために書いておきます。

RNG (Random Number Generator:乱数ジェネレータ)

PyTorchに限らず、ランダムな処理を行う場合には、RNGと呼ばれるジェネレータが乱数を生成し、その数字に基づきランダム処理を行っています。

例えば、データセットからバッチを生成するときに、データを取ってくるためのランダム性だったり、重みの初期化に使う乱数だったり。

この乱数は、seedと呼ばれる数字から生成しています。
(ちゃんと理解できていませんが、)seedが同じであれば、同じ乱数が生成されるそうなのです。

同じ乱数が生成されて何が嬉しいかというと、結果に再現性をもたせることができるのです。同じバッチを使って、同じ初期化重みを使えるので、学習は同じ経過をたどりますね。

最初、seedの意味が分からなかったので、何度学習しても同じ画像を同じタイミングで読み込んでいるのがずっと不思議でした。shuffleが機能していないのだと勘違いしたり……
ランダムに取り出すデータが固定されていたからなのですね。

RNGの動作

numpyの関数で、RNGの動作を確認してみます。

import numpy as np

for n in range(5):
    np.random.seed(0) # seedの設定
    x = np.random.randn(1) # 乱数の生成
    print(x)

[1.76405235]
[1.76405235]
[1.76405235]
[1.76405235]
[1.76405235]

出力結果を見ると、seedに同じ値が入っているため、生成される乱数が同一であることが分かります。

ちなみに、以下のようにseedの設定ループを前に出すと、繰り返すたびに乱数は変わります。毎回、同じseedの値を使うわけではなく、1度使ったseedの続きの値を使っているからだそうです。

import numpy as np

np.random.seed(0) # seedの設定
for n in range(5):
    x = np.random.randn(1) # 乱数の生成
    print(x)

[1.76405235]
[0.40015721]
[0.97873798]
[2.2408932]
[1.86755799]

RNGの固定

再現性のある学習を行うために、以下をコードのはじめに設定します。引数としてのseedは任意の数字を入れます。
様々な段階で乱数を使っていますので、完全再現のためには4種類、忘れずに設定しましょう。

np.random.seed(seed)
random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)

meshgrid

格子状の2次元配列を作るときに使います。前回の等差数列を作る関数と合わせて使うことも多いと思います。

【書式】X, Y = np.meshgrid(x1, x2)
 引数には2つの1次元配列を指定します。

文章だけだと説明しづらいため、実際に動かしてみます。
まず、元となる1次元配列を作ります。

x = np.arange(0,5)
y = np.arange(0,10)
print(x)
print(y)

# x
array([0, 1, 2, 3, 4])

# y
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

ここで作った配列を引数にして2次元配列を作ります。

xx, yy = np.meshgrid(x,y)
print(xx)
print(yy)

# xx
array([[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4],
[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]])

# yy
array([[0, 0, 0, 0, 0], [1, 1, 1, 1, 1], [2, 2, 2, 2, 2], [3, 3, 3, 3, 3], [4, 4, 4, 4, 4],
[5, 5, 5, 5, 5], [6, 6, 6, 6, 6], [7, 7, 7, 7, 7], [8, 8, 8, 8, 8], [9, 9, 9, 9, 9]])

2次元配列のイメージにすると、下図のようになります。
meshgridは2次元の配列の全組み合わせを作ってくれるということですね。

また、\(X=xx(i,j)\)、\(Y=yy(i,j)\)として、\((X, Y)\)をみると、\((i, j)\)と値が一致します。つまりどういうことかというと、座標情報を計算可能な形にできるということです。

PyTorch

pytorchでも同じようなことができます。

x = torch.arange(0,5)
y = torch.arange(0,10)
print(x)
print(y)     

# x
tensor([0, 1, 2, 3, 4])

# y
tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

同じように、torchのmeshgridで配列を作ります。

xx, yy = torch.meshgrid(x, y)
print(xx)
print(yy)

# xx
tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
[3, 3, 3, 3, 3, 3, 3, 3, 3, 3], [4, 4, 4, 4, 4, 4, 4, 4, 4, 4]])

# yy
tensor([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]])

numpyの時と形状が違いますね。
図にすると、下のような感じです。

メモリの使い方が関係あるようでよくわからないのですが、PyTorchの場合、y方向(画像だと高さ方向)が順序として先に扱うみたいです。

numpyと同じ形状にしたい場合は、xとyの並びを逆にします。この差は意外にハマりどころですので要注意です。

yy, xx = torch.meshgrid(y, x)
print(yy)
print(xx)    

# yy
tensor([[0, 0, 0, 0, 0], [1, 1, 1, 1, 1], [2, 2, 2, 2, 2], [3, 3, 3, 3, 3], [4, 4, 4, 4, 4], [5, 5, 5, 5, 5], [6, 6, 6, 6, 6], [7, 7, 7, 7, 7], [8, 8, 8, 8, 8], [9, 9, 9, 9, 9]])

# xx
tensor([[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]])

利用例

meshgridの使い道がいまいちわからないでいたのですが、画像の変形を行うときに使えるのだと気づきました。

PyTorchで画像変形を行うときのやり方のひとつに、grid_sampleという関数を使う方法があります。この関数は、変形元画像と変形パラメータとして、x, yの座標が求められます。

下図のように、変形パラメータの各gridには、変換後の画像の値が、元画像のどこの座標の値を使うのかを指定します。

なので、例えばaffine変換をかけたいよ、という場合、まずは等差数列+meshgridで座標位置が値として格納された配列を作ります。この配列に対してaffine変換をかけるのです。

その結果をもって、
warp_img = F.grid_sample(img, warp)
で変形できます。

等差数列を作る

同じstep数でインクリメントしていくような配列って、けっこう使用頻度が高いと思います。for文を回しても作れなくはないのですが、関数を使った2通りの作り方を見つけましたので、メモとして残しておきます。

linspace関数

配列の値の区間(start/stop)と何等分するか(N)を指定します。

numpy

【書式】 numpy.linspace(start, stop, N)

例えば、-1~+1までの区間を20等分したい場合は、

import numpy as np

# start:-1, stop:1, N:20の行列
a_np = np.linspace(-1,1,20)
print(a_np)

array([-1. , -0.89473684, -0.78947368, -0.68421053, -0.57894737, -0.47368421, -0.36842105, -0.26315789, -0.15789474, -0.05263158, 0.05263158, 0.15789474, 0.26315789, 0.36842105, 0.47368421, 0.57894737, 0.68421053, 0.78947368, 0.89473684, 1. ])

デフォルトでは”stop”の「+1」は区間に含まれるため、含みたくない場合は、「endpoint=False」を引数に入れます。

b_np = np.linspace(-1,1,20, endpoint=False)
print(b_np)             

array([-1. , -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1, 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])

pytorch

pytorchの場合も使い方はnumpyとほとんど同じです。

a_torch = torch.linspace(-1,1,20)
print(a_torch)

tensor([-1.0000, -0.8947, -0.7895, -0.6842, -0.5789, -0.4737, -0.3684, -0.2632, -0.1579, -0.0526, 0.0526, 0.1579, 0.2632, 0.3684, 0.4737, 0.5789, 0.6842, 0.7895, 0.8947, 1.0000])

stopも分割に含まれるところも同じですが、numpyと違い、endpoint=Falseは使えません。

b_torch = torch.linspace(-1,1,20,endpoint=False)                    

Traceback (most recent call last): File “”, line 1, in
TypeError: linspace() received an invalid combination of arguments – got (int, int, int, endpoint=bool), but expected one of:
(Number start, Number end, Tensor out, torch.dtype dtype, torch.layout layout, torch.device device, bool requires_grad)
(Number start, Number end, int steps, Tensor out, torch.dtype dtype, torch.layout layout, torch.device device, bool requires_grad)

同じことをやるならどうすればいいかを考えてみました。N+1の配列を作って、Nまでを取り出すのはひとつの手でしょうか。
もしかしたら、普通に関数があるのかもしれませんが……

n_21 = torch.linspace(-1, 1, 21)
n_20 = n_21[:-1]           

# N=21
tensor([-1.0000, -0.9000, -0.8000, -0.7000, -0.6000, -0.5000, -0.4000, -0.3000, -0.2000, -0.1000, 0.0000, 0.1000, 0.2000, 0.3000, 0.4000, 0.5000, 0.6000, 0.7000, 0.8000, 0.9000, 1.0000])

# N=20まで取り出し
tensor([-1.0000, -0.9000, -0.8000, -0.7000, -0.6000, -0.5000, -0.4000, -0.3000, -0.2000, -0.1000, 0.0000, 0.1000, 0.2000, 0.3000, 0.4000, 0.5000, 0.6000, 0.7000, 0.8000, 0.9000])

arange関数

配列の値の区間とstepの指定により数列を生成します。
(分割数(N)は指定しません)

numpy

【書式】numpy.linspace(start, stop, step=1)

stepはデフォルトで1ですので、下のように何も指定しない場合は整数の配列が生成されます。arangeの場合は、stopの値は配列に含まれないのですね。

x_np = np.arange(0,5)
print(x_np)

array([0, 1, 2, 3, 4])

pytorch

PyTorchの場合もnumpyと同じです。

x_torch = torch.arange(0,5)
print(x_torch)

tensor([0, 1, 2, 3, 4])

また、stepの数字を0.1にしてみると、0.1ごとにインクリメントする数列が生成されました。

x_torch = torch.arange(-1,1,0.1)
print(x_torch)

tensor([-1.0000, -0.9000, -0.8000, -0.7000, -0.6000, -0.5000, -0.4000, -0.3000, -0.2000, -0.1000, 0.0000, 0.1000, 0.2000, 0.3000, 0.4000, 0.5000, 0.6000, 0.7000, 0.8000, 0.9000])

まとめ

linspaceとarangeは両方とも等差数列を作る関数ですが、分割数が決まっている場合はlinspace、step数が決まっている場合はarangeを使うという棲み分けになっているようです。

作りたい数式の条件で使い分けが必要ですね。

ヤコビ行列

ヤコビ行列とは何ぞやということで、調べてみました。
なお、スーパー初心者が自分でどうにかかみ砕いた結果を書いていますので、数学的な正確性についての保証はありません。

まずはwikiの記述から。

数学、特に多変数微分積分学およびベクトル解析におけるヤコビ行列(やこびぎょうれつ、Jacobian matrix)あるいは単にヤコビアン[1]または関数行列(かんすうぎょうれつ、Funktionalmatrix)は、一変数スカラー値関数における接線の傾きおよび一変数ベクトル値函数の勾配の、多変数ベクトル値関数に対する拡張、高次元化である。

wikipedia

「接線の傾き」とか「勾配」という単語からも予想できるように、微分積分に関連しています。ヤコビ行列は特に、多変数の関数についてのものです。

置換積分

ヤコビ行列を理解するのに、まず、カギとなるのは「置換積分」です。
「置換積分」のおさらいをします。

そのままだと積分しにくい複雑な式について、式の一部をより単純な形の式で置き換えることによって積分しやすい形に変換するという手法です。
例えば、下のような定積分を考えます。
$$F(x) = \int_a^b f(x) dx$$
ここで、\(f(g(t))\) となる関数\(g(t)\)を定義します。変数を\(x\)から\(t\)に変えるわけです。
さらに、\(a \le x \le b \Rightarrow \alpha \le t \le \beta \) と積分範囲も式に合わせて変換します。

そうすると、最終的に以下の式で結果を求められるようになります。
これが「置換積分」です。
$$\int_a^b f(x) dx = \int_\alpha^\beta f(g(t))g'(t) dt$$
簡単な例として、以下のような式をイメージすることができます。
$$\int_a^b (2x+1)^2 dx$$
このとき、\(t = 2x+1\) とすると、\(f(x) = f(g(t))\)、\(g(t) = t\)と書き換えることができます。
なお、\(a \le x \le b \Rightarrow \frac{a}{2} \le t \le \frac{b}{2} \) です。

そのため、最終的に求めるべきは、
$$\int_\frac{a}{2}^\frac{b}{2} f(g(t))g'(t) dt
=\int_\frac{a}{2}^\frac{b}{2} t^2 \frac{dt}{2}$$
と、非常にシンプルな形に変わります。
また、 \(\frac{dt}{dx} = 2\) なので\(dx = \frac{dt}{2}\) です。

さて、上で求めたものは変数がひとつ(\(=x\))に対する積分でした。
では変数が複数、つまり多変数の場合どうすればよいか? といった時に登場するのが「ヤコビ行列」です。
wikiで「多変数ベクトル値関数に対する拡張、高次元化である。」と書かれているのはそのためですね。

多変数への拡張

簡単に2つの変数を持つ関数 \(f(x, y)\) を考えます。
この関数は、\(x = \phi (u, v), y = \psi (u, v)\) で表されるとします。

イメージしにくい場合は、\(xy\)平面から\(\theta\), \(r\)への極座標への変換
\(x =r cos\theta, y = rsin\theta\)
を考えてみるといいかもしれません。 ある座標から別の座標への変換ということですね。
微分しづらい座標系を、もっと簡単な座標系に投影して計算をするという意味合いでしょうか。

この場合、\(u\)と\(v\)が微笑値変わった時の\(x\)と\(y\)の値、つまり\(x\)と\(y\)の全微分はどのようになるでしょうか。

全微分

また新たな単語が出てきましたので、整理します。
「全微分」とは、多変数の関数において、各変数の\(偏微分\times 無限小\)をすべての変数について足し合わせたものを指します。
\(x\)と\(y\)からなる2変数\(f(x, y)\)の全微分は下の式で表せます。
$$f'(x, y) = \frac{\partial f}{\partial x}*dx + \frac{\partial f}{\partial y}*dy$$
\(\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y} \) :\(x, y\)それぞれの傾き
\(dx, dy\) :\(x, y\)の微小値

全部の変数が少しだけ変わった時の傾き=関数全体の傾きを求めていることになります。

ヤコビ行列の導入

というわけで、あらためて \(x = \phi (u, v)\), \(y = \psi (u, v)\)に対する全微分を考えます。全微分の式に従って、\(dx, dy\)は、以下のように書き表せます。

$$dx = \frac {\partial \phi}{\partial u}du + \frac {\partial \phi}{\partial v}dv$$
$$dy = \frac {\partial \psi}{\partial u}du + \frac {\partial \psi}{\partial v}dv$$

これを行列の形に整理して、
$$\begin{pmatrix}
dx\\
dy
\end{pmatrix}
= \begin{pmatrix}
\frac {\partial \phi}{\partial u} & \frac {\partial \phi}{\partial v}\\
\frac {\partial \psi}{\partial u} & \frac {\partial \psi}{\partial v }
\end{pmatrix}\begin{pmatrix}
du\\
dv
\end{pmatrix} $$

右辺の1つ目の行列を見ると、異なる座標系\(x, y\)と\(u, v\)の橋渡しをしてくれていることがわかります。これが「ヤコビ行列」です。(ようやくたどり着きました)

【ヤコビ行列】
$$ \begin{pmatrix}
\frac {\partial \phi}{\partial u} & \frac {\partial \phi}{\partial v}\\
\frac {\partial \psi}{\partial u} & \frac {\partial \psi}{\partial v }
\end{pmatrix} $$

ちなみに、3次元以上になっても同じ考えで行列式を作ることができます。

では、学習したヤコビ行列を胸に、次回は再びPyTorch Tutorialに戻ります。
ヤコビアンとかその他については、また必要に迫られた時に調べます。

テンソルを作る

では、さっそくPyTorchを動かしてみようと思います。

ライブラリのインポート

from __future__ import print_function
import torch

さっそく「__future__」とはなんぞや? という疑問が生じました。
(スーパー初心者ですので)

”__future__”というのは、python2系を動かす際、互換性のないpython3系も動かせるようにするためのものらしいです(参考)。

ということは、python3を使っている私の環境だと1行目は不要ということかもしれませんね。入れたらダメということはないと思いますので、チュートリアル通りに動かしてみることにします。

テンソルの作成

■ 初期化なし

初期化されていない\(5*3\)のマトリックスを作成します。

x = torch.empty(5, 3)
print(x)
# -----出力-----
tensor([[-3.1582e+35,  4.5570e-41, -3.1582e+35], 
        [ 4.5570e-41,  3.8235e-14,  7.2296e+31], 
        [ 5.6015e-02,  4.4721e+21,  1.8042e+28], 
        [ 8.1477e-33,  1.3563e-19,  1.6114e-19], 
        [ 2.8175e+20,  4.5103e+16,  1.4585e-19]])

この結果を見る限り、使用するデータ型で取りうる値全体の範囲でランダムな値を格納するみたいです。
同じ値がちょいちょい出てきているのがなぜか気になりますが、とりあえず、箱だけ作るとイメージしておくことにします。

■ ランダムに初期化

ランダムに初期化された \(5*3\) のマトリクスを作成します。

x = torch.rand(5, 3)
print(x)
# -----出力-----
tensor([[0.5287, 0.4393, 0.9229],
        [0.3146, 0.8343, 0.1759],
        [0.0451, 0.3627, 0.3233],
        [0.2978, 0.5777, 0.9817],
        [0.3242, 0.8804, 0.8938]])

ほほう、確かにランダムな値が入っていますね。ランダムで初期化をする際には、マイナスの値は入らないんですね。

■ ALLゼロ

次に、全部ゼロが入ったlong型の5×3のマトリクスです。
ここでは”dtype”でデータ型も一緒に指定しています。
numpyの場合も、np.zeros()で配列を作れるので、感覚的にわかりやすいですね。

x = torch.zeros(5, 3, dtype=torch.long)
print(x)

5×3の配列が作成されました。

# -----出力-----
tensor([[0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0],
        [0, 0, 0]])

dtypeをfloatに指定すると「0」->「0.」に変わります。
小数点以下がありますよ、ということですね。これはPythonでも同じなので、特に気にすることはありませんが。

# -----出力-----
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])

★ちなみにlong型は4バイトの符号型の整数を表現するためのもので、 \(-2^{31}~+2^{31}-1\) まで表せるものだそうです。データ型もいまいちよくわかっていないので、よく使うものだけでも勉強したいと思います。

■ 値の直接指定

値を直接指定してテンソルを作ることもできます。

x = torch.tensor([5.5, 3])
print(x)

小数と整数が混在していますが、精度の高い方に合わせてデータ型が決まります。

# -----出力-----
tensor([5.5000, 3.0000])

多分ですが、この配列はfloat型です。
試しに、double型を指定してテンソルを作ってみたところ、出力が以下のようになりました。

—–出力—–
tensor([5.5000, 3.0000], dtype=torch.float64)

小数値はfloat型が一般的(デフォルト?)なので、あえて出力に型を書いていないのでしょう。

■ 既存テンソルベース

あとは、既存のテンソルを使って新しいテンソルを作ることもできます。
データ型とかサイズとかを合わせないとダメなケースがたくさんあると思いますので、これは覚えておいた方が良さそうです。

既存のテンソルxのサイズ・型変換、1埋めを行ったテンソルを作ります。

x = x.new_ones(5, 3, dtype=torch.double)
print(x)

サイズ1×2のテンソルxが5×3に変わり、型もdoubleになっています。

# -----出力-----
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], dtype=torch.float64)

全部変わってるじゃん!!って思いました。
型以外にも、後々出てくるdeviceの設定とか、requires_gradの設定とかを改めてやらなくていい分、全く新しいのを作るよりも楽になるのでしょうかね。

上で作ったテンソルと同じサイズで、ランダム値が入ったテンソルを作ります。データタイプも合わせて変えています。

x = torch.randn_like(x, dtype=torch.float)
print(x)

こんな感じです。
dtype=torch.float64の文字が消えていますので、double->floatになったことがわかります。

# -----出力-----
tensor([[ 0.9637, -0.9373, -0.2481],
        [-0.6289, -0.1012, -0.3534],
        [ 0.5662,  0.7798,  1.5038],
        [ 1.0916, -0.3201, -1.7198],
        [ 1.3004,  1.4057, -0.6745]])
■ サイズ確認

テンソルのサイズを調べるためには、「.size()」を使います。

print(x.size())
# -----出力-----
torch.Size([5, 3])

これはPythonと同じですね。出力はタプルなので、行と列をそれぞれ、

row, col = x.size()

として別個で変数に入れることもできます。

というわけで、今日はここまで。

pytorchチュートリアルはじめました

Deep Learningを扱うにあたって私が使うフレームワークはpytorchです。ほかのフレームワークと比べてどんな特徴があるのか、なんかもそのうち調べていきたいと思いますが、今はこれ以外知りませんのでまずは動かせるところまでもっていきます。

pytorchの公式チュートリアルはここにあります。ここの項目を順次勉強していきます。とはいっても、pythonもほぼ素人ですのでコードの意味を読み解きつつ、将来的には自分でちゃんとネットワークを組めるようにしたいです。

PyTorchってなんだ?

ということで、チュートリアルの導入に当たる「WHAT IS PYTORCH?」からはじめていきます。

PyTorchとはPythonベースの科学計算用のパッケージで、以下の2つの特徴があるそうです。

  • GPUのパワーを使うためにnumpyの代わりに使用する
  • ディープラーニングの研究のためのプラットフォームで、最大のフレキシブルさと速度を持つ

CPUではなくGPUで処理を行うために開発されたものだということですね。
ちなみに、GPUとはいわゆるグラフィックボードのこと(私はこれまでゲーム用途でしか知りませんでした……)。行列演算にはCPUよりも適しているそうです。

ディープラーニングは基本、行列演算なので、画像の取り扱いを主眼に作られたGPUはぴったりなんだろうと。

PyTorchはテンソルというデータ形式で処理を行います。
テンソルはnumpyのndarrayに似た作りで、GPU上で処理できるようになっています。

ndarrayというのは、n-dimential arrayのことで、直訳すると「n次元配列」。任意の次元の行列の配列を作れて、ndarrayを使うと効率よく処理を行えるらしいです。
ただし、データ型(intとかfloatとか)は配列内で統一させなくてはいけません。

PyTorchインストール

次回、テンソルを作ってみるとして、とりあえずはPyTorchを使えるように、インストールだけしておきます。

pip install torch, torchvision

ちなみに、私のマシン環境はpython3.5で、anacondaが入っています。
その他のライブラリは動かして怒られたら入れていく方針です。

環境を作るのも大変だったので、環境設定周りもいずれ、まとめておこうと思います。