1. HOME
  2. ブログ
  3. IT技術
  4. Pytorch公式チュートリアルを訳しながら学ぶ【What is Pytorch? ~ Autograd】

Pytorch公式チュートリアルを訳しながら学ぶ【What is Pytorch? ~ Autograd】

Pytorch 公式チュートリアルを訳しながら学んでみる

「Pytorch」は facebook社が開発し、2016年にリリースした、オープンソース機械学習ライブラリです。

操作方法が、「NumPy」と類似していることや、「Define-by-Run」の性質を持っているのが特徴です。

世界的にも注目度が増加しているフレームワークですが、日本語でのリソースが少ないのが現状です。

英語の公式チュートリアルには、使用方法がわかりやすくまとまっているので、今回は翻訳しながらコードの実装と共に使い方を学んでいきたいと思います!

今回は「基礎」と「勾配計算」!

公式チュートリアルは、いくつかのチャプターに分かれています。

今回は、初めに取り掛かるであろう、基礎編「What is Pytorch?」と、勾配計算の「Autograd: Automatic Differentiation」について一緒に学んでいきましょう!

【公式チュートリアルはこちら】
https://pytorch.org/tutorials/

Pytorch のインストール記事はコチラ!

Pytorch 基礎編「What is Pytorch ?」

まず一つに、「Pytorch」は「NumPy」に替わる、GPU の計算パワーを活用できる計算パッケージであるということです。

チュートリアルを進めると分かりますが、NumPy の ndarray(n次元配列)に類似する「Tensor(テンソル)」という形で計算を行い、GPU が得意とする高速な行列計算を存分に活用していきます。

とにかくコードを書きながら見ていきましょう!

Tensors:Tensor型と生成について

Pytorch で生成される Tensor は、NumPy の ndarray に似ていますが、特徴的なのは GPU を使用して高速計算を行うことができる型であるということです。

Tensor型の様々な状態の行列の生成

次のコードは、Tensor型の様々な状態の行列(以下では5x3)の生成を行っています。

#out:
tensor([[1.7876e-35, 0.0000e+00, 0.0000e+00],
    [0.0000e+00, 0.0000e+00, 0.0000e+00],
    [0.0000e+00, 0.0000e+00, 2.8026e-45],
    [0.0000e+00, 1.1210e-44, 0.0000e+00],
    [1.4013e-45, 0.0000e+00, 0.0000e+00]])

#out:
tensor([[0.7213, 0.2548, 0.4610],
    [0.3287, 0.0262, 0.1603],
    [0.8951, 0.5536, 0.3249],
    [0.1505, 0.4647, 0.1205],
    [0.1879, 0.6873, 0.1848]])

ゼロ行列

以下はゼロ行列ですが、チュートリアル上ではデータ型を、long型(64bit整数)としています。

#out:
tensor([[0, 0, 0],
    [0, 0, 0],
    [0, 0, 0],
    [0, 0, 0],
    [0, 0, 0]])
    torch.int64

データから直接 Tensor を作成

データから直接 Tensor を作成できます。

#out:
tensor([5.5000, 3.0000])

または、既存の Tensor に基づいて、新たな Tensor を作成できます。

以下は、 new_ones() にて1で埋められた行列を作成し、 randn_like() で行列xを正規分布に従うランダムな数値に置き換えています。

また、同時にデータ型の上書きも行っています(double→float)。

tensor([[1., 1., 1.],
    [1., 1., 1.],
    [1., 1., 1.],
    [1., 1., 1.],
    [1., 1., 1.]], dtype=torch.float64)

tensor([[ 0.6357, -0.3452, 0.1283],
    [-0.7312, -0.6917, 0.4404],
    [ 0.3671, -0.2578, -1.6911],
    [ 0.9498, -0.4784, 0.0713],
    [ 0.0162, -0.8405, 0.3782]])

サイズの取得は size() で行い、 randn_like() で生成したこの場合は、基の Tensor と同じです。

#out:
torch.Size([5, 3])

Operations:演算などの各種操作

各種操作について、まず加算操作から見ていきましょう。

出力

いずれも加算処理を行っており、出力は同じです。

#out:
tensor([[-0.4647, 1.3950, -0.4282],
    [ 1.8951, 0.7929, 1.8287],
    [ 0.6629, 0.9020, -1.1435],
    [ 1.2521, 0.6579, 1.3717],
    [ 1.1686, -1.1220, 1.6196]])

引数として渡すこともできる

出力 Tensor を、引数として渡すこともできます。

#out:
tensor([[-0.4647, 1.3950, -0.4282],
    [ 1.8951, 0.7929, 1.8287],
    [ 0.6629, 0.9020, -1.1435],
    [ 1.2521, 0.6579, 1.3717],
    [ 1.1686, -1.1220, 1.6196]])

add_() で変数自体に加算し更新

また、 add_() で変数自体に加算し、更新することができます。

このような、Tensor をその場(in-place)で変更する操作は、全て「 _ 」で表され、他にも x.copy_(y) や  x.t_() などは x 自体が変更されます。

#out:
tensor([[-0.4647, 1.3950, -0.4282],
    [ 1.8951, 0.7929, 1.8287],
    [ 0.6629, 0.9020, -1.1435],
    [ 1.2521, 0.6579, 1.3717],
    [ 1.1686, -1.1220, 1.6196]])

インデックスによる操作を行うことができる

Tensor行列は、NumPy のようなインデックスによる操作を同様に行うことができます。

#out:
tensor([[-0.4510, 0.2800, 0.1055],
    [-0.3795, -1.1968, 0.2691],
    [ 0.9133, -1.2371, 0.9872],
    [ 1.8379, 1.4533, 0.4116],
    [-0.5310, 1.2762, -0.5901]])
tensor([ 0.2800, -1.1968, -1.2371, 1.4533, 1.2762])

resize や reshape を行いたい場合

resize や reshape を行いたい場合は torch.view() を用います。

#out:
#変更後のサイズ
torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])

#view(16)に変更後
tensor([-0.0941, -1.7758, -0.2859, -2.2382, 0.0138, 2.3766, 0.7874, 0.8522,
    0.9718, -1.2113, 0.3079, 1.7919, 0.3283, 1.4063, 1.4154, 1.5530])

#view(-1, 8)に変更後
tensor([[-0.0941, -1.7758, -0.2859, -2.2382, 0.0138, 2.3766, 0.7874, 0.8522],
    [ 0.9718, -1.2113, 0.3079, 1.7919, 0.3283, 1.4063, 1.4154, 1.5530]])

.item() を使用することで 数値として取得

一つの要素のみの Tensor は .item() を使用することで、数値として取得することができます。

#out:
tensor([-1.9968])
-1.996762990951538

その他 Tensorオペレーション

他にも多くの Tensorオペレーションが用意されており、以下で参照できるのでチェックしてみてください。

【参考サイト】
https://pytorch.org/docs/stable/torch.html

NumPy Bridge:NumPyとTorch Tensorの双方向変換

Torch Tensor と NumPy Array の相互変換を、比較的容易に行うことができます。

これは基礎となるメモリ位置を共有しており、一方を変更すると、もう一方も変更されるためです。

Torch Tensor から NumPy Array への変換

まず、Torch Tensor から NumPy Array への変換を見ていきましょう。

#out: a
tensor([1., 1., 1., 1., 1.])
#b
[1. 1. 1. 1. 1.]

以下のように、Tensor が変更されると Numpy Array も変更されます。

NumPy Array が変更されても同様です。

#out:
tensor([2., 2., 2., 2., 2.])
[2. 2. 2. 2. 2.]

NumPy Array から Torch Tensor への変換

NumPy Array から Torch Tensor への変換も同様です。

#out:
[2. 2. 2. 2. 2.]
tensor([2., 2., 2., 2., 2.], dtype=torch.float64)

CharTensor(符号付き8bit整数)を除く、CPU 上の全ての Tensor は、このように NumPy への変換、逆変換がサポートされています。

この後に出てきますが、Tensor は機械学習に適した特徴(GPUが使用可能、勾配情報の保持など)を有しています。

NumPy の ndarray型では、それらの特徴は使えないものの、状況に応じて NumPyモジュールを使い分けることが出来ます。

CUDA Tensors:デバイス間移動とGPU処理

さて、Pytorch Tensorでは .to メソッドを使用して、任意のデバイスに Tensor を移動することができ、CUDA を利用してGPU上で処理を行うことができます。

device の宣言は torch.device() を使用します。

Tensor の生成時に、引数に任意の device を渡すことで、直接 GPU 上に Tensor を生成できます。

または、すでに存在する Tensor を GPU 上に転送することができます。

#out:
tensor([2.3518], device='cuda:0')
tensor([2.3518], dtype=torch.float64)

出力結果から、z は 'cuda:0' というGPU上で計算されていることがわかります。

また、 .to() で "cpu" を指定することで、GPU から CPU への転送ができます。

このように、Pytorch の Tensor型は、デバイス間の転送を行い、機械学習に必要な GPU での処理を行うことができるのが特徴の一つです。

Pytorch 勾配計算「Autograd:Automatic Differentiation」

Pytorch の、ニューラルネットワークにおける中核を担うのが、自動微分(Automatic Differentiation)を行う autograd パッケージです。

自動微分とは、プログラムによって定義された任意の関数について、その導関数をアルゴリズムによって求める処理です。

この autograd パッケージは、Define-by-Run という性質を持っており、Pytorch が支持される一つの特徴といえます。

Define-by-Run とは、入力データがニューラルネットワークを流れる際に、ニューラルネットの計算構造を表す計算グラフも、同時に動的に構築する手法のことです。

Pytorch では、autograd パッケージが Tensor オブジェクトの計算を全て、追跡記憶することで、Define-by-run を可能にしています。

Tensor:Tensorオブジェクト生成から順伝播まで

Tensorオブジェクトの生成時に、 requires_grad を  True に設定することで、全ての操作の追跡が開始されます。

その Tensorオブジェクトが、ネットワークに流れていく際に各地点で行った計算が、計算グラフとして構築・記憶されていきます。

計算終了後、 backward() を呼び出すことで、構築された計算グラフに基づいて全ての勾配が自動的に計算され、 grad 属性に蓄積されます。

コード

実際に、簡単なコードを書いていきましょう。

#out:
tensor([[1., 1.],
    [1., 1.]], requires_grad=True)

計算

簡単な計算を行ってみます。

#out:
tensor([[3., 3.],
    [3., 3.]], grad_fn=<AddBackward0>)

計算結果

計算結果の y は、 grad_fn 属性を持っています。

#out:
<AddBackward0 object at 0x7fa5af3edb38>

さらに計算

さらに計算を行います。

#out:
tensor([[27., 27.],
    [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)

各演算によって、 grad_fn 属性が AddBackward0MeanBackward0 など異なっているのがわかります。

requires_grad_() を用いてみる

また、以下のように requires_grad_() を用いると、既に存在するTensorオブジェクトの requires_grad を、その場で変更できます(デフォルトでは False)。

#out:
False
True
<SumBackward0 object at 0x7ff50a1a5c18>

Gradients - 逆伝播と勾配の計算 -

それでは上記で計算した、変数 out について、 out.backward()誤差逆伝播法(Backpropagation)を実行します。

勾配\(\frac{dout}{dx}\)の出力には、 x.grad で取得できます。

結果

実際に計算をしても確認できますが、 autograd によって、今回の場合「4.5」の勾配が得られました。

#out:
tensor([[4.5000, 4.5000],
    [4.5000, 4.5000]])

チュートリアルでは、この autograd について、数学的な観点から解説されていますね。

簡単にいえば、ベクトル値関数 \(\vec{y}=f(\vec{x})\) が与えられた場合、\(\vec{y}\) の \(\vec{x}\) に対する勾配ベクトルを並べたものが、ヤコビ行列(Jacobian matrix : \( J \))であるということです。

$$J=\left(\begin{array}{ccccc}\frac{\partial y_1}{\partial x_1}&\cdots&\frac{\partial y_1}{\partial x_n}\\\vdots&\ddots&\vdots\\\frac{\partial y_m}{\partial x_1}&\cdots&\frac{\partial y_m}{\partial x_n}\end{array}\right)$$

さらに他の関数が与えられた場合

さらに他の関数が与えられた場合、連鎖律に従い、ベクトルとヤコビ行列の行列積を計算することで、勾配を求めることができます

例えば、スカラー関数 \( l=g(\vec{y}) \) の勾配が、ベクトル \( \nu=\left(\begin{array}{rrrrr}\frac{\partial l}{\partial y_1}\cdots\frac{\partial l}{\partial y_m}\end{array}\right)^{\mathrm{T}} \) である時、\(\vec{x}\) に対する \( l \) の勾配は、以下のように \( J^{\mathrm{T}}\cdot\nu \) の行列式で表すことができます。

$$J^{\mathrm{T}}\cdot\nu=\left(\begin{array}{ccccc}\frac{\partial y_1}{\partial x_1}&\cdots&\frac{\partial y_m}{\partial x_1}\\\vdots&\ddots&\vdots\\\frac{\partial y_1}{\partial x_n}&\cdots&\frac{\partial y_m}{\partial x_n}\end{array}\right) \left(\begin{array}{c}\frac{\partial l}{\partial y_1}\\\vdots\\\frac{\partial l}{\partial y_m}\end{array}\right)=\left(\begin{array}{c}\frac{\partial l}{\partial x_1}\\\vdots\\\frac{\partial l}{\partial x_n}\end{array}\right)$$

autograd の活用例

他にも、autograd の活用例をみていきましょう。

#out:
tensor([ 319.9040, -732.8150, 1116.2251], grad_fn=<MulBackward0>)

乱数により生成された、3つの値を持つ Tensor x を生成し、これに2をかけ続けた結果が、最終的な y です。

ノルムが1000未満の間、2をかけ続けています。

この勾配も同様に、 backward() で計算することができます。

ですが、今回は Tensor が3つの要素を持っているので、 backward() に同じ形状の Tensor である、勾配引数を指定する必要があります(以下の gradients 変数)。

勾配引数は重みパラメータに対応している

また、この勾配引数は重みパラメータに対応しており、渡した勾配引数で重み付けされ、逆伝播を行っていきます。

#out:
tensor([1.0240e+02, 1.0240e+03, 1.0240e-01])

勾配のトラッキングを止める方法 1

では次に、勾配のトラッキングを止める方法について紹介します。

torch.no_grad() のコードブロック内で、勾配の記憶を止めることができます。

#out:
True
True
False

勾配のトラッキングを止める方法 2

もしくは、既存の requires_grad=True な Tensor に対して、 detach() を使用して、新しく Tensor を生成する方法です。

これは、勾配の追跡から切り離した同じ内容の Tensor を生成することができます。

#out:
True
False
tensor(True)

このようにして、勾配のトラッキングのON/OFFを操作することが可能です。

さいごに

Pytorch の公式チュートリアルをなぞりながら、Pytorch で扱うTensor型オブジェクトについてや、操作方法、機械学習させるための自動微分の方法について紹介しました!

自動微分については、少ないコードで実現できますが、プラスαでどのような処理なのかについても簡単に触れました。

これらを理解することで、本格的なモデルの構築にスムーズに入れると思います!

こちらの記事もオススメ!

書いた人はこんな人

広告メディア事業部
広告メディア事業部
「好きを仕事にするエンジニア集団」の(株)ライトコードです!

ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。

システム開発依頼・お見積もり大歓迎!

また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」「WEBディレクター」を積極採用中です!
インターンや新卒採用も行っております。

以下よりご応募をお待ちしております!
https://rightcode.co.jp/recruit

関連記事

採用情報

\ あの有名サービスに参画!? /

バックエンドエンジニア

\ クリエイティブの最前線 /

フロントエンドエンジニア

\ 世界を変える…! /

Androidエンジニア

\ みんなが使うアプリを創る /

iOSエンジニア