• トップ
  • ブログ一覧
  • Pytorch公式チュートリアルを訳しながら学ぶ【What is Pytorch? ~ Autograd】
  • Pytorch公式チュートリアルを訳しながら学ぶ【What is Pytorch? ~ Autograd】

    メディアチームメディアチーム
    2020.06.11

    IT技術

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

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

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

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

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

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

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

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

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

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

    featureImg2020.01.20PyTorchの特徴とインストール方法PyTorchとはPyTorch(パイトーチ)とは、Pythonの機械学習ライブラリの一つで、現在最もアツいフレームワ...

    Pytorch 基礎編「What is Pytorch ?」

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

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

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

    Tensors:Tensor型と生成について

    1from __future__ import print_function
    2import torch

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

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

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

    1#メモリが初期化されていない行列
    2x = torch.empty(5, 3)
    3print(x)

    #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]])

    1#ランダムに初期化された行列
    2x = torch.rand(5, 3)
    3print(x)

    #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整数)としています。

    1#ゼロ行列
    2x = torch.zeros(5, 3, dtype=torch.long)
    3print(x)
    4print(x.dtype)

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

    データから直接 Tensor を作成

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

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

    #out:
    tensor([5.5000, 3.0000])

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

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

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

    1#1の行列を生成
    2x = x.new_ones(5, 3, dtype=torch.double)
    3print(x)
    4#
    5x = torch.randn_like(x, dtype=torch.float)
    6#result has the same size
    7print(x)

    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 と同じです。

    1print(x.size())

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

    Operations:演算などの各種操作

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

    1# +記号を用いた場合
    2y = torch.rand(5, 3)
    3print(x + y)
    4
    5#add()を用いた場合
    6print(torch.add(x, y))

    出力

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

    #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 を、引数として渡すこともできます。

    1result = torch.empty(5, 3)
    2torch.add(x, y, out=result)
    3print(result)

    #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 自体が変更されます。

    1#yにxを加えて、更新
    2y.add_(x)
    3print(y)

    #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 のようなインデックスによる操作を同様に行うことができます。

    1# [ :, 1 ]で参照する行と列を指定
    2x = torch.randn(5, 3)
    3print(x)
    4print(x[:, 1])

    #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() を用います。

    1x = torch.randn(4, 4)
    2y = x.view(16) #16個の要素の1次元配列に変更
    3z = x.view(-1, 8) #-1のサイズは他の次元によって予測されます。
    4print(x.size(), y.size(), z.size()) #各Tensorのサイズを出力
    5print(y)
    6print(z)

    #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() を使用することで、数値として取得することができます。

    1x = torch.randn(1)
    2print(x)
    3print(x.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 への変換を見ていきましょう。

    1# aのTensorを.numpy()により変換
    2a = torch.ones(5)
    3b = a.numpy()
    4print(a)
    5print(b)

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

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

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

    1a.add_(1) # aに1を加える
    2print(a)
    3print(b)

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

    NumPy Array から Torch Tensor への変換

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

    1import numpy as np
    2a = np.ones(5)
    3b = torch.from_numpy(a)
    4np.add(a, 1, out=a)
    5print(a)
    6print(b)

    #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 上に転送することができます。

    1#CUDAが使用可能の場合のみ実行します
    2if torch.cuda.is_available():
    3    device = torch.device("cuda")          #デバイスとしてcudaを宣言
    4    y = torch.ones_like(x, device=device)  #直接GPU上にTensorを作成
    5    x = x.to(device)                       #'.to()'でxをGPU上に転送します。または".to("cuda")"でもOK
    6    z = x + y
    7    print(z)
    8    print(z.to("cpu", torch.double))       #".to" で同時にデータ型も変更することができます

    #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_gradTrue に設定することで、全ての操作の追跡が開始されます。

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

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

    コード

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

    1x = torch.ones(2, 2, requires_grad=True)
    2print(x)

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

    計算

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

    1y = x + 2
    2print(y)

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

    計算結果

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

    1print(y.grad_fn)

    #out:
    <AddBackward0 object at 0x7fa5af3edb38>

    さらに計算

    さらに計算を行います。

    1z = y * y * 3
    2out = z.mean()
    3print(z, out)

    #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)。

    1a = torch.randn(2, 2)
    2a = ((a * 3) / (a - 1))
    3print(a.requires_grad)
    4a.requires_grad_(True)
    5print(a.requires_grad)
    6b = (a * a).sum()
    7print(b.grad_fn)

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

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

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

    1out.backward()

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

    結果

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

    1print(x.grad)
    2
    3#out:
    4tensor([[4.5000, 4.5000],
    5        [4.5000, 4.5000]])

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

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

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

    J=(y1x1amp;amp;y1xnamp;amp;ymx1amp;amp;ymxn)J=\left(\begin{array}{ccccc}\frac{\partial y_1}{\partial x_1}&amp;\cdots&amp;\frac{\partial y_1}{\partial x_n}\\\vdots&amp;\ddots&amp;\vdots\\\frac{\partial y_m}{\partial x_1}&amp;\cdots&amp;\frac{\partial y_m}{\partial x_n}\end{array}\right)

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

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

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

    JTν=(y1x1amp;amp;ymx1amp;amp;y1xnamp;amp;ymxn)(ly1lym)=(lx1lxn)J^{\mathrm{T}}\cdot\nu=\left(\begin{array}{ccccc}\frac{\partial y_1}{\partial x_1}&amp;\cdots&amp;\frac{\partial y_m}{\partial x_1}\\\vdots&amp;\ddots&amp;\vdots\\\frac{\partial y_1}{\partial x_n}&amp;\cdots&amp;\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 の活用例をみていきましょう。

    1x = torch.randn(3, requires_grad=True)
    2y = x * 2
    3while y.data.norm() < 1000:
    4    y = y * 2
    5print(y)

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

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

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

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

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

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

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

    1gradients = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
    2y.backward(gradients)
    3print(x.grad)

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

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

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

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

    1print(x.requires_grad)
    2print((x ** 2).requires_grad)
    3with torch.no_grad():
    4    print((x ** 2).requires_grad)

    #out:
    True
    True
    False

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

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

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

    1print(x.requires_grad)
    2y = x.detach()
    3print(y.requires_grad)
    4print(x.eq(y).all())

    #out:
    True
    False
    tensor(True)

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

    さいごに

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

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

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

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

    featureImg2020.07.28機械学習 特集知識編人工知能・機械学習でよく使われるワード徹底まとめ!機械学習の元祖「パーセプトロン」とは?【人工知能】ニューラルネ...

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...

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

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

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

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

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

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

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

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

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

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

    featureImg2020.01.20PyTorchの特徴とインストール方法PyTorchとはPyTorch(パイトーチ)とは、Pythonの機械学習ライブラリの一つで、現在最もアツいフレームワ...

    Pytorch 基礎編「What is Pytorch ?」

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

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

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

    Tensors:Tensor型と生成について

    1from __future__ import print_function
    2import torch

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

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

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

    1#メモリが初期化されていない行列
    2x = torch.empty(5, 3)
    3print(x)

    #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]])

    1#ランダムに初期化された行列
    2x = torch.rand(5, 3)
    3print(x)

    #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整数)としています。

    1#ゼロ行列
    2x = torch.zeros(5, 3, dtype=torch.long)
    3print(x)
    4print(x.dtype)

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

    データから直接 Tensor を作成

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

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

    #out:
    tensor([5.5000, 3.0000])

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

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

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

    1#1の行列を生成
    2x = x.new_ones(5, 3, dtype=torch.double)
    3print(x)
    4#
    5x = torch.randn_like(x, dtype=torch.float)
    6#result has the same size
    7print(x)

    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 と同じです。

    1print(x.size())

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

    Operations:演算などの各種操作

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

    1# +記号を用いた場合
    2y = torch.rand(5, 3)
    3print(x + y)
    4
    5#add()を用いた場合
    6print(torch.add(x, y))

    出力

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

    #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 を、引数として渡すこともできます。

    1result = torch.empty(5, 3)
    2torch.add(x, y, out=result)
    3print(result)

    #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 自体が変更されます。

    1#yにxを加えて、更新
    2y.add_(x)
    3print(y)

    #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 のようなインデックスによる操作を同様に行うことができます。

    1# [ :, 1 ]で参照する行と列を指定
    2x = torch.randn(5, 3)
    3print(x)
    4print(x[:, 1])

    #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() を用います。

    1x = torch.randn(4, 4)
    2y = x.view(16) #16個の要素の1次元配列に変更
    3z = x.view(-1, 8) #-1のサイズは他の次元によって予測されます。
    4print(x.size(), y.size(), z.size()) #各Tensorのサイズを出力
    5print(y)
    6print(z)

    #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() を使用することで、数値として取得することができます。

    1x = torch.randn(1)
    2print(x)
    3print(x.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 への変換を見ていきましょう。

    1# aのTensorを.numpy()により変換
    2a = torch.ones(5)
    3b = a.numpy()
    4print(a)
    5print(b)

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

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

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

    1a.add_(1) # aに1を加える
    2print(a)
    3print(b)

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

    NumPy Array から Torch Tensor への変換

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

    1import numpy as np
    2a = np.ones(5)
    3b = torch.from_numpy(a)
    4np.add(a, 1, out=a)
    5print(a)
    6print(b)

    #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 上に転送することができます。

    1#CUDAが使用可能の場合のみ実行します
    2if torch.cuda.is_available():
    3    device = torch.device("cuda")          #デバイスとしてcudaを宣言
    4    y = torch.ones_like(x, device=device)  #直接GPU上にTensorを作成
    5    x = x.to(device)                       #'.to()'でxをGPU上に転送します。または".to("cuda")"でもOK
    6    z = x + y
    7    print(z)
    8    print(z.to("cpu", torch.double))       #".to" で同時にデータ型も変更することができます

    #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_gradTrue に設定することで、全ての操作の追跡が開始されます。

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

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

    コード

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

    1x = torch.ones(2, 2, requires_grad=True)
    2print(x)

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

    計算

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

    1y = x + 2
    2print(y)

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

    計算結果

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

    1print(y.grad_fn)

    #out:
    <AddBackward0 object at 0x7fa5af3edb38>

    さらに計算

    さらに計算を行います。

    1z = y * y * 3
    2out = z.mean()
    3print(z, out)

    #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)。

    1a = torch.randn(2, 2)
    2a = ((a * 3) / (a - 1))
    3print(a.requires_grad)
    4a.requires_grad_(True)
    5print(a.requires_grad)
    6b = (a * a).sum()
    7print(b.grad_fn)

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

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

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

    1out.backward()

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

    結果

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

    1print(x.grad)
    2
    3#out:
    4tensor([[4.5000, 4.5000],
    5        [4.5000, 4.5000]])

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

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

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

    J=(y1x1amp;amp;y1xnamp;amp;ymx1amp;amp;ymxn)J=\left(\begin{array}{ccccc}\frac{\partial y_1}{\partial x_1}&amp;\cdots&amp;\frac{\partial y_1}{\partial x_n}\\\vdots&amp;\ddots&amp;\vdots\\\frac{\partial y_m}{\partial x_1}&amp;\cdots&amp;\frac{\partial y_m}{\partial x_n}\end{array}\right)

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

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

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

    JTν=(y1x1amp;amp;ymx1amp;amp;y1xnamp;amp;ymxn)(ly1lym)=(lx1lxn)J^{\mathrm{T}}\cdot\nu=\left(\begin{array}{ccccc}\frac{\partial y_1}{\partial x_1}&amp;\cdots&amp;\frac{\partial y_m}{\partial x_1}\\\vdots&amp;\ddots&amp;\vdots\\\frac{\partial y_1}{\partial x_n}&amp;\cdots&amp;\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 の活用例をみていきましょう。

    1x = torch.randn(3, requires_grad=True)
    2y = x * 2
    3while y.data.norm() < 1000:
    4    y = y * 2
    5print(y)

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

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

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

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

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

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

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

    1gradients = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
    2y.backward(gradients)
    3print(x.grad)

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

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

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

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

    1print(x.requires_grad)
    2print((x ** 2).requires_grad)
    3with torch.no_grad():
    4    print((x ** 2).requires_grad)

    #out:
    True
    True
    False

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

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

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

    1print(x.requires_grad)
    2y = x.detach()
    3print(y.requires_grad)
    4print(x.eq(y).all())

    #out:
    True
    False
    tensor(True)

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

    さいごに

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

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

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

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

    featureImg2020.07.28機械学習 特集知識編人工知能・機械学習でよく使われるワード徹底まとめ!機械学習の元祖「パーセプトロン」とは?【人工知能】ニューラルネ...

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    メディアチーム
    メディアチーム
    Show more...

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background