連載
» 2020年02月06日 05時00分 公開

PyTorch入門:第1回 難しくない! PyTorchでニューラルネットワークの基本

PyTorchの習得は、シンプルなニューラルネットワーク(NN)の、まずは1つだけのニューロンを実装することから始めてみよう。ニューロンのモデル定義から始め、フィードフォワードとバックプロパゲーションといった最低限必要な「核」となる基本機能に絞って解説。自動微分についても簡単に触れる。

[一色政彦,デジタルアドバンテージ]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「PyTorch入門」のインデックス

連載目次

 人気急上昇の「PyTorch」を使いたい。そう考えて、PyTorchの公式チュートリアルを開いて学習を始めてみた、という人は少なくないだろう。しかし、挫折してしまったり、なかなか進まなかったり、という人も少なくないのではないだろうか。

 というのも、その公式チュートリアルの、最初の「テンソル」解説だけは(NumPyライクな直観的なAPIなので)難しくないとしても、次が「Autograd(自動微分)」、その次が「ニューラルネットワーク」……と言いつつも、いきなり「CNN(畳み込みニューラルネットワーク)」による説明になっているからだ。ニューラルネットワークやディープラーニングの経験が浅い初心者にとって、そういった少しハイレベルな視点からの解説は、PyTorch機能そのものを学ぶ際の「理解の妨げ」になりやすいだろう。そもそも公式チュートリアルやAPIドキュメントが英語版しか提供されていない、という障壁もある(ただし最近は、Chromeブラウザーが持つ[日本語に翻訳]機能が優秀なので、機械翻訳すれば十分に読めるが)。

 PyTorchはわざと「入門の壁」を高くして、ある程度の知識がある人だけが入って来られるように「門前払い」しているのではないか。筆書はそんなふうにすら感じている。

PyTorchだって難しくない

 しかしPyTorchはしょせん“ツール”に過ぎない。あくまでツールとして捉えて「PyTorchを活用するのは難しくない」と本連載では主張したい。

図0-1 あい博士「PyTorchだって難しくない!」、マナブくん「ホントに?」 図0-1 あい博士「PyTorchだって難しくない!」、マナブくん「ホントに?」

本連載の目的と方針

 PyTorchは世界的な人気のわりには、日本語の分かりやすいチュートリアルがまだほとんどない。そこで本連載は、日本語によるチュートリアル入門記事として、できるだけシンプルなニューラルネットワークを題材にして、PyTorchでニューラルネットワークを活用するための最重要かつ最低限の基礎知識を最短で説明していく(基本中の基本を本連載の第1回〜第3回の3本にまとめた)。

 読者に想定するゴールとしては、PyTorchの基本的な実装パターンを理解し、初歩的なニューラルネットワーク&ディープラーニングのコードが思い通りに書けるようになること、としたい。

 本連載の読者対象は、「ニューラルネットワークの基本を学んだことがあるレベル」を想定している。具体的には、連載『TensorFlow 2+Keras(tf.keras)入門』のうち、下記の「仕組み理解×初実装」の前編・中編・後編の3本を読んで、内容を理解している状態とする。

 例えば「ニューロン」「活性化関数」「正則化」「勾配」「確率的勾配降下法(SGD)」と聞いて、「その概念が分からない」といった場合には、先に上記3本の記事に目を通してほしい。この3本の記事では、ニューラルネットワークの挙動を図で示しながら、仕組み(とKerasによる実装方法)を分かりやすく説明しているのでお勧めである。

 本連載では、Python(バージョン3.6)と、ディープラーニングのライブラリ「PyTorch」の最新版1.4を利用する。また、開発環境にGoogle Colaboratory(以下、Colab)を用いる。


Google Colabで実行する
GitHubでソースコードを見る

 さっそく説明を始めよう!

PyTorchとは?

 PyTorch(パイトーチ)とは、Facebookが開発しているオープンソースの機械学習(特にディープラーニング)のライブラリである。その代表的な特徴を3つだけ挙げるとすれば、以下の通りだ。

  1. 人気急上昇中: 参考記事「PyTorch vs. TensorFlow、ディープラーニングフレームワークはどっちを使うべきか問題
  2. Pythonic: Pythonのイディオムをうまく活用した自然なコーディングが可能
  3. 柔軟性や拡張性に優れる: 「動的」に計算グラフ(=ライブラリ内部で計算処理に使うデータフローなどのグラフ)を構築できる

 3番目が特に重要で、例えばモデルのフィードフォワード時にif条件やforループなどの制御フローを書くなどして動的に計算グラフを変更するといったことが可能だ。とりわけNLP(Natural Language Processing:自然言語処理)の分野では、研究者はさまざまな長さの文を訓練する必要があるので、「動的な計算グラフ」機能が必要不可欠である(実際に筆者が「PyTorchがデファクトスタンダードになっている」と初めて聞いたのは、NLPの分野だった)。

 「動的な計算グラフ」はもちろんPyTorch以外のライブラリにも搭載されており、他のライブラリでは“Define-by-Run”(実行しながら定義する)やEager Execution(即時実行)などとも呼ばれている。例えばTensorFlow 2.0以降にはEager Executionが搭載されたが、「動的な計算グラフ」の細かな使い勝手はPyTorchに一日の長がある(と筆者は感じている)。

本連載の第3回までで説明する大まかな流れ

 本連載では、PyTorchの基礎を一気呵成(かせい)に習得していく。具体的には、次の流れで説明する。

  (1)ニューロンのモデル定義
  (2)フィードフォワード(順伝播)
  (3)バックプロパゲーション(逆伝播)と自動微分(Autograd)
  (4)PyTorchの基礎: テンソルとデータ型
  (5)データセットとデータローダー(DataLoader)
  (6)ディープニューラルネットのモデル定義
  (7)学習/最適化(オプティマイザ)
  (8)評価/精度検証

 (1)〜(3)が1セットで、1つのニューロンだけのネットワークを題材に、PyTorchの核となる部分を説明する。これが第1回(本稿)。

 (4)は、より本格的にPyTorchによるディープラーニングの手順を学ぶに当たり、PyTorchの基礎部分(テンソルとデータ型)をチートシート形式でざっと確認していただく。これが第2回。

 (5)〜(8)が再び1セットで、4層のディープニューラルネットワークを題材に、PyTorchによる基本的な実装と一連の流れを解説する。これが第3回である。

 第3回までのタイトルは下記のようになっている。

 全部読み通すとかなりのボリュームになっているが、頑張って最後まで付いてきてほしい。

(1)ニューロンのモデル定義

 それでは、「ニューロン」の入力と出力を行うコードをPyTorchで記述してみよう。

PyTorch 1.4のインストール

 本連載では、PyTorchのバージョン1.4以上を必須とする(Colabにインストール済みのPyTorchバージョンが分からない場合は、import torch; print('PyTorch', torch.__version__)を実行すれば、バージョンを確認できる)。

 本連載が利用を前提とするColabにデフォルトでインストール済みのバージョンは、(2020年2月4日執筆時点で)PyTorch 1.4.0だった。バージョンが1.4以上ではない場合は、ライブラリ「torch」を最新版にアップグレードして使う必要がある。

 そのためには、リスト1-0に示すいずれかのコードを実行して、アップグレード(もしくはインストール)する。実行後に、[RESTART RUNTIME]ボタンが表示されるので、クリックしてランタイムを再起動してほしい。

#!pip install torch        # ライブラリ「PyTorch」をインストール
#!pip install torchvision  # 画像/ビデオ処理のPyTorch用追加パッケージもインストール

# 最新バージョンにアップグレードする場合
!pip install --upgrade torch torchvision

# バージョンを明示してアップグレードする場合
#!pip install --upgrade torch===1.4.0 torchvision===0.5.0

# 最新バージョンをインストールする場合
#!pip install torch torchvision

# バージョンを明示してインストールする場合
#!pip install torch===1.4.0 torchvision===0.5.0

リスト1-0 [オプション]ライブラリ「PyTorch」最新バージョンのインストール

 リスト1-0では、「torchvision」パッケージもアップグレード(もしくはインストール)しているが、本連載では使っていない。しかし同時にインストールしておかないと、パッケージ関係が不整合になるため、ここでインストールしておく必要がある。

ニューロンのモデル設計と活性化関数

 PyTorchの利用環境が整ったとして話を進めよう。ここでは、2つの入力を受け付けて、それを使った計算結果を1つの出力として生成するニューロンを、モデル化(=モデル設計)する。

 モデル設計のために、ライブラリ「PyTorch」のメインパッケージであるtorchをインポートし、その中で定義されているnnパッケージ(=NN:ニューラルネットワーク機能)を使って、torch.nn.Moduleクラスを継承した「独自の派生クラス」(今回の名前は「NeuralNetwork」)を作成する。なお、torch.nn.Moduleを「モジュール」と記載すると、Pythonの「モジュール」と紛らわしいので、本連載では「torch.nn.Module」で表記を統一する。

 ちなみにPyTorchによるモデルの作成方法/書き方は幾つかあり、代表的なものを挙げるなら以下の3つである。

  • torch.nn.Moduleクラスのサブクラス化: これから説明する。典型的な書き方なので、最もお勧めできる
  • torch.nn.Sequentialクラス: 確かにKerasのようにシンプルに書けるが、PyTorchの良さがなくなる
  • 低水準APIを使ってフルスクラッチ実装: 高水準APIのtorch.nn.Moduleを使う方が、効率がよい

 モデル設計のコードは、リスト1-1のようになる。ちなみにPyTorchでは、インデントのタブ文字数は4となっているので、本連載ではそれに従うこととする。ここでは何らかの座標を入力すると(2つの入力値)、それが青色なのかオレンジ色なのか(1つの出力値)を推測するものとする(青:1.0〜オレンジ:-1.0の範囲)。

import torch       # ライブラリ「PyTorch」のtorchパッケージをインポート
import torch.nn as nn  # 「ニューラルネットワーク」モジュールの別名定義

# 定数(モデル定義時に必要となるもの)
INPUT_FEATURES = 2  # 入力(特徴)の数: 2
OUTPUT_NEURONS = 1  # ニューロンの数: 1

# 変数(モデル定義時に必要となるもの)
activation = torch.nn.Tanh()  # 活性化関数: tanh関数

# 「torch.nn.Moduleクラスのサブクラス化」によるモデルの定義
class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        # 層(layer:レイヤー)を定義
        self.layer1 = nn.Linear(  # Linearは「全結合層」を指す
            INPUT_FEATURES,       # データ(特徴)の入力ユニット数
            OUTPUT_NEURONS)       # 出力結果への出力ユニット数

    def forward(self, input):
        # フィードフォワードを定義
        output = activation(self.layer1(input))  # 活性化関数は変数として定義
        # 「出力=活性化関数(第n層(入力))」の形式で記述する。
        # 層(layer)を重ねる場合は、同様の記述を続ければよい(第3回)。
        # 「出力(output)」は次の層(layer)への「入力(input)」に使う。
        # 慣例では入力も出力も「x」と同じ変数名で記述する(よって以下では「x」と書く)
        return output

# モデル(NeuralNetworkクラス)のインスタンス化
model = NeuralNetwork()
model   # モデルの内容を出力

リスト1-1 ニューロンのモデル設計と活性化関数

 コード内にできるだけ多くの説明コメントを入れたので、本文ではポイントのみを示すこととしよう。

 まずclass NeuralNetwork(nn.Module):が、「torch.nn.Moduleクラスのサブクラス化」の部分である。クラス内には、

  • __init__関数: レイヤー(層)を定義する
  • forward関数: フィードフォワード(=活性化関数で変換しながらデータを流す処理)を実装する

の2つのメソッドが実装されている。NeuralNetworkクラスをインスタンス化すればモデルの作成は完了だ。

 レイヤー(層)を定義しているtorch.nn.Linearクラスは、「入力データに対して線形変換を行うこと」を意味し、一般的には全結合層Fully Connected Layer)と呼ばれており、ライブラリによっては「Dense層」(Kerasなど)や「Affine層」(Neural Network Consoleなど)とも呼ばれている。Linearクラスのコンストラクター(厳密には__init__関数)の第1引数と第2引数には、入力ユニット数と出力ユニット数を指定すればよい。

 活性化関数は、activation変数に定義しているが、今回はtanh関数(図1-1)を使用する。

図1-1 tanh関数(参考比較対象:シグモイド関数) 図1-1 tanh関数(参考比較対象:シグモイド関数)

 PyTorchでは、活性化関数としてtorch.nnパッケージ内に以下のものが用意されており、すぐに利用できる(カッコ書きがあるものは特に有名なもの)。

  • ELU
  • Hardshrink
  • Hardtanh
  • LeakyReLU
  • LogSigmoid
  • MultiheadAttention
  • PReLU
  • ReLU(有名)
  • ReLU6
  • RReLU
  • SELU
  • CELU
  • GELU
  • Sigmoid(シグモイド)
  • Softplus(ソフトプラス)
  • Softshrink
  • Softsign(ソフトサイン)
  • Tanh(本連載で使用)
  • Tanhshrink
  • Threshold
  • Softmin
  • Softmax(ソフトマックス)
  • Softmax2d
  • LogSoftmax
  • AdaptiveLogSoftmaxWithLoss

パラメーター(重みとバイアス)の初期値設定

 「パラメーター(=重みとバイアス)を初期化したい」というニーズは高いだろう。例えば「0」や「一様分布のランダム値」で初期化するなどの方法がある。この方法については、第3回で掲載するリスト7-4の冒頭で示す。

 ここでは「自動的な初期化」ではなく「任意の初期値の指定」を行う方法を説明する。初期値の例として、2つの入力(の接続線)にかかる重みに「0.6」と「-0.2」を、バイアスには「0.8」を設定することにしよう(リスト1-2)。

# パラメーター(ニューロンへの入力で必要となるもの)の定義
weight_array = nn.Parameter(
    torch.tensor([[ 0.6,
                   -0.2]]))  # 重み
bias_array = nn.Parameter(
    torch.tensor([  0.8 ]))  # バイアス

# 重みとバイアスの初期値設定
model.layer1.weight = weight_array
model.layer1.bias = bias_array

# torch.nn.Module全体の状態を辞書形式で取得
params = model.state_dict()
#params = list(model.parameters()) # このように取得することも可能
params
# 出力例:
# OrderedDict([('layer1.weight', tensor([[ 0.6000, -0.2000]])),
#              ('layer1.bias', tensor([0.8000]))])

リスト1-2 パラメーター(重みとバイアス)の初期値設定

 リスト1-2もポイントを示しておこう。

 モデルのパラメーターはtorch.nn.Parameterオブジェクトとして定義する必要がある。そのtorch.nn.Parameterクラスのコンストラクターには、torch.Tensorオブジェクト(以下、テンソル)を指定する(テンソルの使い方の詳細は、第2回で説明する)。そのtorch.Tensorクラスのコンストラクターには、Pythonの多次元リストを指定できる。このクラスを活用して、重みとバイアスをweight_arraybias_arrayという変数に定義している。

 定義した重みやバイアスをモデルに適用するには、

  • <モデル名>.<レイヤー名>.weightプロパティ: 重みを指定可能
  • <モデル名>.<レイヤー名>.biasプロパティ: バイアスを指定可能

を使用すればよい。

 重みやバイアスといったパラメーターの情報を取得して表示したい場合には、<モデル名>.state_dict()メソッドが便利だ。このメソッドを使えば、パラメーターといったtorch.nn.Module全体の状態が取得できる。

 ちなみに情報取得ではなく、最適化のために使う「実際のパラメーターのオブジェクト」を取得するには、<モデル名>.parameters()メソッドを呼び出せばよい。この方法については、第3回に掲載するリスト7-1で示す。

(2)フィードフォワード(順伝播)

 以上でモデルの設計は完了だ。実際にサンプルデータを入力してフィードフォワードを実行し、その出力結果を確認してみよう。

フィードフォワードの実行と結果確認

 フィードフォワード、つまりモデルによる推論/予測は、modelオブジェクトを関数のように呼び出すだけである(厳密には__call__メソッドの呼び出し)。この関数の引数には、(テンソルの)入力値を渡せばよい。戻り値として、出力結果(つまり予測値)がテンソル値で返される。

 それを実際に行っているのがリスト2-1である。

X_data = torch.tensor([[1.0, 2.0]])  # 入力する座標データ(1.0、2.0)
print(X_data)
# tensor([[1., 2.]]) ……などと表示される

y_pred = model(X_data)  # このモデルに、データを入力して、出力を得る(=予測:predict)
print(y_pred)
# tensor([[0.7616]], grad_fn=<TanhBackward>) ……などと表示される

リスト2-1 フィードフォワードの実行と結果確認

 リスト2-1のポイントを示す。

Copyright© Digital Advantage Corp. All Rights Reserved.

編集部からのお知らせ

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。