• トップ
  • ブログ一覧
  • 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ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...

    広告メディア事業部

    広告メディア事業部

    おすすめ記事