PyTorch 1.0 Tutorials : テキスト : NLP 深層学習 (1) PyTorch へのイントロダクション

PyTorch 1.0 Tutorials : テキスト : NLP 深層学習 (1) PyTorch へのイントロダクション (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 12/11/2018 (1.0.0.dev20181207)

* 本ページは、PyTorch 1.0 Tutorials : Text : Deep Learning for NLP with Pytorch : INTRODUCTION TO PYTORCH を
翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

テキスト : NLP 深層学習 (1) PyTorch へのイントロダクション

 

Torch の tensor ライブラリへのイントロダクション

総ての深層学習は tensor 上の計算です、これは 2 次元以上でインデックス可能な行列の一般化です。これが正確に何を意味するか後で深く見ます。最初に、tensor で何ができるかを見てみましょう。

# Author: Robert Guthrie

import torch
import torch.autograd as autograd
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

torch.manual_seed(1)

 

Tensor を作成する

Tensor は torch.Tensor() 関数で Python リストから作成できます。

# torch.tensor(data) creates a torch.Tensor object with the given data.
V_data = [1., 2., 3.]
V = torch.tensor(V_data)
print(V)

# Creates a matrix
M_data = [[1., 2., 3.], [4., 5., 6]]
M = torch.tensor(M_data)
print(M)

# Create a 3D tensor of size 2x2x2.
T_data = [[[1., 2.], [3., 4.]],
          [[5., 6.], [7., 8.]]]
T = torch.tensor(T_data)
print(T)
tensor([1., 2., 3.])
tensor([[1., 2., 3.],
        [4., 5., 6.]])
tensor([[[1., 2.],
         [3., 4.]],

        [[5., 6.],
         [7., 8.]]])

 
いずれにせよ 3D tensor とは何でしょう?それについてこのように考えてください。もし貴方がベクトルを持つ場合、ベクトルへのインデキシングは貴方にスカラーを与えます。もし貴方が行列と持つ場合、行列へのインデキシングは貴方にベクトルを与えます。もし貴方が 3D tensor を持つ場合には、従って tensor へのインデキシングは貴方に行列を与えます!

用語のノート: このチュートリアルで “tensor” と言うとき、それは任意の torch.Tensor オブジェクトに言及しています。 行列とベクトルは torch.Tensor の特別なケースで、そこではそれらの次元はそれぞれ 1 と 2 です。3D tensor について語るときは、明示的に用語 “3D tensor” を使用します。

# Index into V and get a scalar (0 dimensional tensor)
print(V[0])
# Get a Python number from it
print(V[0].item())

# Index into M and get a vector
print(M[0])

# Index into T and get a matrix
print(T[0])
tensor(1.)
1.0
tensor([1., 2., 3.])
tensor([[1., 2.],
        [3., 4.]])

 
他のデータ型の tensor もまた作成できます。見て取れるように、デフォルトは Float です。整数型の tensor を作成するためには、torch.LongTensor() を試してください。更なるデータ型についてはドキュメントをチェックしてください、しかし Float と Long が最も一般的です。

torch.randn() でランダム・データと供給された次元を持つ tensor を作成できます。

x = torch.randn((3, 4, 5))
print(x)
tensor([[[-1.5256, -0.7502, -0.6540, -1.6095, -0.1002],
         [-0.6092, -0.9798, -1.6091, -0.7121,  0.3037],
         [-0.7773, -0.2515, -0.2223,  1.6871,  0.2284],
         [ 0.4676, -0.6970, -1.1608,  0.6995,  0.1991]],

        [[ 0.8657,  0.2444, -0.6629,  0.8073,  1.1017],
         [-0.1759, -2.2456, -1.4465,  0.0612, -0.6177],
         [-0.7981, -0.1316,  1.8793, -0.0721,  0.1578],
         [-0.7735,  0.1991,  0.0457,  0.1530, -0.4757]],

        [[-0.1110,  0.2927, -0.1578, -0.0288,  0.4533],
         [ 1.1422,  0.2486, -1.7754, -0.0255, -1.0233],
         [-0.5962, -1.0055,  0.4285,  1.4761, -1.7869],
         [ 1.6103, -0.7040, -0.1853, -0.9962, -0.8313]]])

 

Tensor で演算

貴方が予想するような方法で tensor 上で演算できます。

x = torch.tensor([1., 2., 3.])
y = torch.tensor([4., 5., 6.])
z = x + y
print(z)
tensor([5., 7., 9.])

貴方に利用可能な大量の演算の完全なリストについては ドキュメント を見てください。それらは単なる数学演算の範囲を越えて拡大しています。

後で利用する一つの有用な演算は concatenation です。

# By default, it concatenates along the first axis (concatenates rows)
x_1 = torch.randn(2, 5)
y_1 = torch.randn(3, 5)
z_1 = torch.cat([x_1, y_1])
print(z_1)

# Concatenate columns:
x_2 = torch.randn(2, 3)
y_2 = torch.randn(2, 5)
# second arg specifies which axis to concat along
z_2 = torch.cat([x_2, y_2], 1)
print(z_2)

# If your tensors are not compatible, torch will complain.  Uncomment to see the error
# torch.cat([x_1, x_2])
tensor([[-0.8029,  0.2366,  0.2857,  0.6898, -0.6331],
        [ 0.8795, -0.6842,  0.4533,  0.2912, -0.8317],
        [-0.5525,  0.6355, -0.3968, -0.6571, -1.6428],
        [ 0.9803, -0.0421, -0.8206,  0.3133, -1.1352],
        [ 0.3773, -0.2824, -2.5667, -1.4303,  0.5009]])
tensor([[ 0.5438, -0.4057,  1.1341, -0.1473,  0.6272,  1.0935,  0.0939,  1.2381],
        [-1.1115,  0.3501, -0.7703, -1.3459,  0.5119, -0.6933, -0.1668, -0.9999]])

 

Tensor を reshape する

tensor を reshape するためには .view() メソッドを使用します。このメソッドは大いに利用されます、何故ならば多くのニューラルネットワーク・コンポーネントはそれらの入力に何らかの shape を持つことを想定するからです。貴方のデータをコンポーネントに渡す前にしばしば reshape する必要があります。

x = torch.randn(2, 3, 4)
print(x)
print(x.view(2, 12))  # Reshape to 2 rows, 12 columns
# Same as above.  If one of the dimensions is -1, its size can be inferred
print(x.view(2, -1))
tensor([[[ 0.4175, -0.2127, -0.8400, -0.4200],
         [-0.6240, -0.9773,  0.8748,  0.9873],
         [-0.0594, -2.4919,  0.2423,  0.2883]],

        [[-0.1095,  0.3126,  1.5038,  0.5038],
         [ 0.6223, -0.4481, -0.2856,  0.3880],
         [-1.1435, -0.6512, -0.1032,  0.6937]]])
tensor([[ 0.4175, -0.2127, -0.8400, -0.4200, -0.6240, -0.9773,  0.8748,  0.9873,
         -0.0594, -2.4919,  0.2423,  0.2883],
        [-0.1095,  0.3126,  1.5038,  0.5038,  0.6223, -0.4481, -0.2856,  0.3880,
         -1.1435, -0.6512, -0.1032,  0.6937]])
tensor([[ 0.4175, -0.2127, -0.8400, -0.4200, -0.6240, -0.9773,  0.8748,  0.9873,
         -0.0594, -2.4919,  0.2423,  0.2883],
        [-0.1095,  0.3126,  1.5038,  0.5038,  0.6223, -0.4481, -0.2856,  0.3880,
         -1.1435, -0.6512, -0.1032,  0.6937]])

 

計算グラフと自動微分

計算グラフの概念は効率的な深層学習プログラミングに本質的です、何故ならばそれは逆伝播勾配を貴方自身で書かなくても良いことを可能にします。計算グラフは貴方のデータが貴方に出力を与えるためにどのように結合されるかの単なる仕様です。グラフはどのようなパラメータがどの演算に伴うかを全体的に指定しますので、それは導関数を計算するために十分な情報を含みます。これはおそらく曖昧に聞こえるでしょうから、基礎的なフラグ requires_grad を使用して何が起きているかを見てみましょう。

最初に、プログラマー視点から考えます。上で作成している torch.Tensor オブジェクトには何がストアされているのでしょう?明らかにデータと shape、そして多分少しの他のもの。しかし2つの tensor を一緒に加算したとき、出力 tensor を得ました。この出力 tensor が知っている総てはそのデータと shape です。それはそれが2つの他の tensor の総和であったことは知りません。(それはファイルから読み込まれたかもしれません、それはある他の演算の結果かもしれません, etc.)

requires_grad=True である場合、Tensor オブジェクトはそれがどのように作成されたかを追跡します。実地にそれを見てみましょう。

# Tensor factory methods have a ``requires_grad`` flag
x = torch.tensor([1., 2., 3], requires_grad=True)

# With requires_grad=True, you can still do all the operations you previously
# could
y = torch.tensor([4., 5., 6], requires_grad=True)
z = x + y
print(z)

# BUT z knows something extra.
print(z.grad_fn)
tensor([5., 7., 9.], grad_fn=<AddBackward0>)
<AddBackward0 object at 0x7f4882aff4e0>

従って Tensor は何がそれらを作成したかを知ります。z はそれがファイルから読み込まれたのではなく、それが乗算や指数やその他の何かの結果ではないことを知っています。そして z.grad_fn を追い続ければ、x と y に気が付くでしょう。

しかし勾配を計算するのにどのように役立つのでしょう?

# Lets sum up all the entries in z
s = z.sum()
print(s)
print(s.grad_fn)
tensor(21., grad_fn=<SumBackward0>)
<SumBackward0 object at 0x7f4882affba8>

そこで今、x の最初の成分に関するこの sum の導関数は何でしょう?数学では、次を望みます

\[
\frac{\partial s}{\partial x_0}
\]

そうですね、s はそれが tensor z の総計として作成されたことを知っています。z はそれが sum x + y であったことを知っています。従って

\[
s = \overbrace{x_0 + y_0}^\text{$z_0$} + \overbrace{x_1 + y_1}^\text{$z_1$} + \overbrace{x_2 + y_2}^\text{$z_2$}
\]

そして s は私達が望む導関数が 1 であることを決定するための十分な情報を含みます!

もちろんこれはその導関数を実際にどのように計算するかの挑戦について取り繕っています。ここでのポイントは s がそれを計算することを可能にするような十分な情報を運んでいることです。現実に、Pytorch の開発者は sum() と + 演算をそれらの勾配をどのように計算して逆伝播アルゴリズムを実行するかを知っているようにプログラムします。そのアルゴリズムの深い議論はこのチュートリアルのスコープの範囲を超えます。

PyTorch に勾配を計算させて、私達が正しかったことを見てみましょう : (もし貴方がこのブロックを複数回実行する場合、勾配は値を増加することに注意してください。それは PyTorch が勾配を .grad プロパティに蓄積するからです、何故ならば多くのモデルのためにこれは非常に便利だからです。)

# calling .backward() on any variable will run backprop, starting from it.
s.backward()
print(x.grad)
tensor([1., 1., 1.])

下のブロックで何が起きているのかを理解することは深層学習で成功するプログラマとなるために重要です。

x = torch.randn(2, 2)
y = torch.randn(2, 2)
# By default, user created Tensors have ``requires_grad=False``
print(x.requires_grad, y.requires_grad)
z = x + y
# So you can't backprop through z
print(z.grad_fn)

# ``.requires_grad_( ... )`` changes an existing Tensor's ``requires_grad``
# flag in-place. The input flag defaults to ``True`` if not given.
x = x.requires_grad_()
y = y.requires_grad_()
# z contains enough information to compute gradients, as we saw above
z = x + y
print(z.grad_fn)
# If any input to an operation has ``requires_grad=True``, so will the output
print(z.requires_grad)

# Now z has the computation history that relates itself to x and y
# Can we just take its values, and **detach** it from its history?
new_z = z.detach()

# ... does new_z have information to backprop to x and y?
# NO!
print(new_z.grad_fn)
# And how could it? ``z.detach()`` returns a tensor that shares the same storage
# as ``z``, but with the computation history forgotten. It doesn't know anything
# about how it was computed.
# In essence, we have broken the Tensor away from its past history
False False
None
<AddBackward0 object at 0x7f4882ab7278>
True
None

with torch.no_grad() でコードブロックをラップすることにより、autograd に .requires_grad=True を持つ Tensor 上で履歴を追跡することを停止することもできます :

print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
    print((x ** 2).requires_grad)
True
True
False

 
以上