【超入門】PyTorchで始めるディープラーニング開発

はい、承知いたしました。「【超入門】PyTorchで始めるディープラーニング開発」について、約5000語の詳細な説明を含む記事を作成し、直接表示します。


【超入門】PyTorchで始めるディープラーニング開発

はじめに:ディープラーニングの世界へようこそ

人工知能(AI)の進化は目覚ましく、私たちの生活のあらゆる側面に影響を与え始めています。その進化を牽引している技術の一つが、「ディープラーニング(深層学習)」です。画像認識、音声認識、自然言語処理、レコメンデーションシステムなど、多くの最先端AI技術の根幹にディープラーニングがあります。

ディープラーニングは、人間の脳の神経回路網を模倣した「ニューラルネットワーク」を多層に重ねることで、データから複雑な特徴を自動的に学習する技術です。従来の手法では難しかった、大量の非構造化データ(画像、音声、テキストなど)からの高精度なパターン認識を可能にしました。

ディープラーニングを学ぶ上で、強力なフレームワークは不可欠です。現在、広く使われているディープラーニングフレームワークには、TensorFlow、Keras、そしてPyTorchがあります。本記事では、特に研究開発やプロトタイピングにおいて人気が高まっている PyTorch に焦点を当て、「超入門」として、その基本的な使い方から簡単なディープラーニングモデルの実装までを、約5000語の詳細な解説と共にステップバイステップでご紹介します。

なぜPyTorchを選ぶのか?

PyTorchはFacebook(現Meta)が開発を主導しているオープンソースのディープラーニングフレームワークです。Pythonに非常に馴染む設計となっており、直感的で柔軟性が高いことから、特に研究者や開発者の間で急速に普及しました。PyTorchを選ぶ主な理由は以下の通りです。

  1. Pythonフレンドリー: PyTorchはPythonのライブラリとして設計されており、Pythonのコーディングスタイルやイディオムに自然に馴染みます。既存のPythonライブラリ(NumPy, SciPy, scikit-learnなど)との連携も容易です。
  2. 動的な計算グラフ: PyTorchの最大の特徴の一つは「動的な計算グラフ(Dynamic Computation Graph)」を採用している点です。これにより、モデルの構造を柔軟に変更したり、デバッグが容易になったりします。これは、特にRNN(リカレントニューラルネットワーク)のような可変長の入力を扱うモデルや、複雑な研究開発において非常に有利です。対照的に、TensorFlowの古いバージョンでは「静的な計算グラフ」が主流でした(TensorFlow 2.xからは動的グラフが標準になっています)。
  3. 豊富な機能とライブラリ: ニューラルネットワークの構築に必要な様々なレイヤー、損失関数、最適化アルゴリズムなどが torch.nntorch.optim といったモジュールに豊富に用意されています。また、画像処理 (torchvision) や自然言語処理 (torchtext) のための便利なライブラリも提供されています。
  4. 優れたコミュニティとドキュメント: PyTorchは活発なコミュニティを持っており、公式ドキュメントも非常に充実しています。多くの研究論文でPyTorchが使われており、最新の研究成果を実装する際にも参照しやすいです。

もしあなたがPythonにある程度慣れていて、ディープラーニングの基礎をしっかりと学びたい、あるいは最新の研究に触れたいと考えているなら、PyTorchは非常に良い選択肢となるでしょう。

PyTorchのインストール

まずはPyTorchを使えるようにしましょう。インストールは非常に簡単です。Pythonのパッケージマネージャーであるpipを使ってインストールするのが一般的です。

公式サイト(https://pytorch.org/get-started/locally/)にアクセスし、お使いの環境(OS、パッケージマネージャー、Pythonのバージョン、CUDAのバージョン)を選択すると、適切なインストールコマンドが表示されます。

例えば、Linux環境でpipを使い、CUDA 11.8がインストールされている場合のコマンドは以下のようになります。

bash
pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

CUDAを使用しない場合(CPU版)は、よりシンプルなコマンドになります。

bash
pip3 install torch torchvision torchaudio

Anacondaを使っている場合は、condaコマンドでインストールすることも可能です。

bash
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia

ご自身の環境に合わせて、公式サイトで正確なコマンドを確認して実行してください。インストールが完了したら、Pythonインタプリタを起動して確認してみましょう。

python
import torch
print(torch.__version__)

エラーなくバージョン情報が表示されれば、インストールは成功です。

PyTorchの基本:テンソル(Tensor)

PyTorchにおけるデータ操作の基本となるのが「テンソル(Tensor)」です。テンソルは、簡単に言えば多次元配列のことです。数学的なベクトル(1次元)、行列(2次元)を一般化した概念で、ディープラーニングでは入力データ(画像、音声など)、モデルのパラメータ、計算の途中結果などをすべてテンソルとして扱います。

PyTorchのテンソルはNumPyのndarrayと非常によく似ており、NumPyを使っている方ならすぐに慣れるでしょう。大きな違いは、テンソルはGPU上での高速計算に対応しており、また自動微分(Autograd)の機能を持っている点です。

テンソルの作成

様々な方法でテンソルを作成できます。

“`python
import torch

データのリストやNumPy配列から作成

data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)
print(f”リストからのテンソル:\n{x_data}”)

import numpy as np
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
print(f”NumPy配列からのテンソル:\n{x_np}”)

他のテンソルから作成(明示的にデータ型を上書き可能)

x_ones = torch.ones_like(x_data) # x_dataと同じ形状、同じデータ型で、要素が1のテンソルを作成
print(f”Ones Tensor:\n{x_ones}”)

x_rand = torch.rand_like(x_data, dtype=torch.float) # x_dataと同じ形状で、要素がランダムな浮動小数点数のテンソルを作成
print(f”Random Tensor:\n{x_rand}”)

形状を指定して作成

shape = (2, 3,)
ones_tensor = torch.ones(shape) # 要素が1のテンソル
zeros_tensor = torch.zeros(shape) # 要素が0のテンソル
rand_tensor = torch.rand(shape) # 要素がランダムなテンソル

print(f”Ones Tensor:\n{ones_tensor}”)
print(f”Zeros Tensor:\n{zeros_tensor}”)
print(f”Random Tensor:\n{rand_tensor}”)
“`

出力例:
リストからのテンソル:
tensor([[1, 2],
[3, 4]])
NumPy配列からのテンソル:
tensor([[1, 2],
[3, 4]])
Ones Tensor:
tensor([[1, 1],
[1, 1]])
Random Tensor:
tensor([[0.1234, 0.5678],
[0.9012, 0.3456]]) # ランダムな値なので毎回変わります
Ones Tensor:
tensor([[1., 1., 1.],
[1., 1., 1.]])
Zeros Tensor:
tensor([[0., 0., 0.],
[0., 0., 0.]])
Random Tensor:
tensor([[0.1234, 0.5678, 0.9012],
[0.3456, 0.7890, 0.1234]])

テンソルの属性

テンソルの形状、データ型、そして格納されているデバイス(CPUまたはGPU)は重要な属性です。

python
tensor = torch.ones(4, 4)
print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")

出力例:
Shape of tensor: torch.Size([4, 4])
Datatype of tensor: torch.float32
Device tensor is stored on: cpu

デフォルトではCPU上に作成されます。GPUが利用可能な環境であれば、device属性を使ってGPU上にテンソルを作成したり、既存のテンソルをGPUに移動したりできます。これについては後述します。

テンソル演算

テンソルに対する様々な演算(四則演算、行列積、スライス、インデックス参照など)は、NumPyの操作と非常によく似ています。

“`python
tensor = torch.ones(4, 4)

四則演算

print(f”First row: {tensor[0]}”)
print(f”First column: {tensor[:, 0]}”)
print(f”Last column: {tensor[…, -1]}”)

tensor[:,1] = 0
print(f”Modified tensor:\n{tensor}”)

結合 (Concatenation)

t1 = torch.cat([tensor, tensor, tensor], dim=0) # 行方向に結合
print(f”Concatenated (dim=0):\n{t1}”)

t2 = torch.cat([tensor, tensor, tensor], dim=1) # 列方向に結合
print(f”Concatenated (dim=1):\n{t2}”)

行列乗算 (@ または torch.matmul)

tensor = torch.ones(4, 4)
y1 = tensor @ tensor.T # Tは転置
y2 = tensor.matmul(tensor.T)
y3 = torch.rand(2, 3) @ torch.rand(3, 4) # 形状が合うようにランダムなテンソルで例
print(f”Matrix multiplication y1:\n{y1}”)
print(f”Matrix multiplication y2:\n{y2}”)
print(f”Matrix multiplication y3:\n{y3}”)

要素ごとの積 (*)

z1 = tensor * tensor
z2 = tensor.mul(tensor)
print(f”Element-wise multiplication z1:\n{z1}”)
print(f”Element-wise multiplication z2:\n{z2}”)

単一要素の取得 (.item())

agg = tensor.sum()
agg_item = agg.item() # テンソルからPythonの数値を取得
print(f”Sum: {agg}”)
print(f”Sum as item: {agg_item}, type: {type(agg_item)}”)

形状の変更 (view/reshape)

viewは可能な限り元のメモリを共有、reshapeは常に新しいメモリ領域にコピー

tensor = torch.arange(9).reshape(3, 3) # 0から8までの要素を持つ3×3テンソル
print(f”Original tensor:\n{tensor}”)
reshaped_tensor = tensor.view(9) # 1次元に
print(f”Reshaped tensor:\n{reshaped_tensor}”)
reshaped_tensor = tensor.view(1, 9) # 1行9列に
print(f”Reshaped tensor:\n{reshaped_tensor}”)
reshaped_tensor = tensor.view(-1, 3) # -1を指定すると自動計算
print(f”Reshaped tensor:\n{reshaped_tensor}”)

squeeze/unsqueeze: 形状に次元1を挿入/削除

tensor = torch.zeros(1, 3, 1, 4)
print(f”Original shape: {tensor.shape}”)
squeezed_tensor = tensor.squeeze() # 次元1を削除
print(f”Squeezed shape: {squeezed_tensor.shape}”)
unsqueezed_tensor = squeezed_tensor.unsqueeze(0) # 0番目の位置に次元1を挿入
print(f”Unsqueezed shape: {unsqueezed_tensor.shape}”)
“`

出力例:
First row: tensor([1., 1., 1., 1.])
First column: tensor([1., 1., 1., 1.])
Last column: tensor([1., 1., 1., 1.])
Modified tensor:
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
Concatenated (dim=0):
tensor([[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.],
[1., 0., 1., 1.]])
Concatenated (dim=1):
tensor([[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.],
[1., 0., 1., 1., 1., 0., 1., 1., 1., 0., 1., 1.]])
Matrix multiplication y1:
tensor([[4., 4., 4., 4.],
[4., 4., 4., 4.],
[4., 4., 4., 4.],
[4., 4., 4., 4.]])
Matrix multiplication y2:
tensor([[4., 4., 4., 4.],
[4., 4., 4., 4.],
[4., 4., 4., 4.],
[4., 4., 4., 4.]])
Matrix multiplication y3:
tensor([[...], [...]]) # ランダムな値
Element-wise multiplication z1:
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
Element-wise multiplication z2:
tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
Sum: tensor(16.)
Sum as item: 16.0, type: <class 'float'>
Original tensor:
tensor([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
Reshaped tensor:
tensor([0, 1, 2, 3, 4, 5, 6, 7, 8])
Reshaped tensor:
tensor([[0, 1, 2, 3, 4, 5, 6, 7, 8]])
Reshaped tensor:
tensor([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
Original shape: torch.Size([1, 3, 1, 4])
Squeezed shape: torch.Size([3, 4])
Unsqueezed shape: torch.Size([1, 3, 4])

NumPy配列との変換も容易です。CPU上のテンソルとNumPy配列は同じメモリ領域を共有する場合があり、一方を変更するともう一方にも反映されます(GPU上のテンソルの場合はコピーが発生します)。

“`python
t = torch.ones(5)
print(f”t: {t}”)
n = t.numpy()
print(f”n: {n}”)

テンソルを変更するとNumPy配列も変更される

t.add_(1) # インプレース演算 (末尾に_が付くメソッド)
print(f”t: {t}”)
print(f”n: {n}”) # NumPy配列も変更されている!

NumPy配列を変更するとテンソルも変更される

n = np.ones(5)
t = torch.from_numpy(n)
np.add(n, 1, out=n) # NumPyでインプレース演算
print(f”n: {n}”)
print(f”t: {t}”) # テンソルも変更されている!
“`

出力例:
t: tensor([1., 1., 1., 1., 1.])
n: [1. 1. 1. 1. 1.]
t: tensor([2., 2., 2., 2., 2.])
n: [2. 2. 2. 2. 2.]
n: [2. 2. 2. 2. 2.]
t: tensor([2., 2., 2., 2., 2.], dtype=torch.float64) # NumPyのfloat64から変換されたためdtypeが変わる

このNumPyとの親和性の高さも、PyTorchが人気を集める理由の一つです。

自動微分(Autograd):ディープラーニングの心臓部

ディープラーニングモデルの学習は、損失関数を最小化するためにモデルのパラメータを少しずつ調整していくプロセスです。この調整には、損失関数に対する各パラメータの勾配(微分)を計算する必要があります。PyTorchのautogradモジュールは、この勾配計算を自動的に行ってくれる強力な機能です。

autogradは、テンソルに対して行われたすべての演算を記録し、指定された出力テンソルから入力テンソルまでの勾配を「逆伝播(Backward Propagation)」によって効率的に計算します。

勾配計算を有効にするには、テンソルを作成する際に requires_grad=True を指定します。これは通常、学習対象となるモデルのパラメータ(重みやバイアス)に対して行われます。入力データに対しては通常は False です。

“`python
import torch

requires_grad=True でテンソルを作成すると、勾配計算の対象になる

x = torch.ones(5, requires_grad=True)
y = torch.zeros(3, requires_grad=False) # デフォルトはFalse
z = x + y[0] # y[0]はrequires_grad=Falseなので、zもFalse

print(f”x.requires_grad: {x.requires_grad}”)
print(f”y.requires_grad: {y.requires_grad}”)
print(f”z.requires_grad: {z.requires_grad}”)

requires_gradがTrueのテンソルを含む演算結果はrequires_grad=Trueになる

a = torch.ones(2, requires_grad=True)
b = torch.ones(2, requires_grad=True) * 2
c = a + b
print(f”a.requires_grad: {a.requires_grad}”)
print(f”b.requires_grad: {b.requires_grad}”)
print(f”c.requires_grad: {c.requires_grad}”) # Trueになる

勾配計算(.backward())

逆伝播を開始するには、計算グラフの「根っこ」となるスカラー値に対して .backward() を呼び出します。

テンソルがスカラーでない場合、例えば損失が複数の要素を持つ場合などは、

損失テンソルと同じ形状の勾配(gradient)を .backward() に渡す必要があります。

通常、損失はミニバッチ全体の平均や合計を取ってスカラーになるため、引数は不要なことが多いです。

x = torch.ones(2, requires_grad=True)
y = x + 2
z = y * y * 3
out = z.mean() # 平均を取ることでスカラーにする

print(f”x: {x}”) # tensor([1., 1.], requires_grad=True)
print(f”y: {y}”) # tensor([3., 3.], grad_fn=)
print(f”z: {z}”) # tensor([27., 27.], grad_fn=)
print(f”out: {out}”) # tensor(27., grad_fn=)

out.backward() # 逆伝播を実行!

xの勾配は x.grad に格納される

print(f”x.grad: {x.grad}”)

計算:

out = (z1 + z2) / 2, z1 = 3y1^2, z2 = 3y2^2, y1=x1+2, y2=x2+2

dout/dx1 = d(out)/dz1 * dz1/dy1 * dy1/dx1

d(out)/dz1 = d((z1+z2)/2)/dz1 = 1/2

dz1/dy1 = d(3y1^2)/dy1 = 6y1

dy1/dx1 = d(x1+2)/dx1 = 1

なので dout/dx1 = (1/2) * (6*y1) * 1 = 3 * y1

x = [1, 1] なので y = [3, 3]

したがって dout/dx1 = 3 * 3 = 9, dout/dx2 = 3 * 3 = 9

x.grad は [9., 9.] になるはず

“`

出力例:
x.requires_grad: True
y.requires_grad: False
z.requires_grad: False # y[0]がFalseなので、zもFalse
a.requires_grad: True
b.requires_grad: True
c.requires_grad: True
x: tensor([1., 1.], requires_grad=True)
y: tensor([3., 3.], grad_fn=<AddBackward0>)
z: tensor([27., 27.], grad_fn=<MulBackward0>)
out: tensor(27., grad_fn=<MeanBackward1>)
x.grad: tensor([9., 9.])

.backward() を呼び出すと、計算グラフ上の requires_grad=True となっているリーフノード(計算結果ではなく、直接作成されたテンソル)の .grad 属性に、そのテンソルに対する出力テンソル(.backward() を呼び出したテンソル)の勾配が累積されます。

重要な注意点: .backward() を複数回呼び出す場合は、毎回勾配を明示的にゼロクリアする必要があります。これは、デフォルトでは勾配が累積されてしまうためです。通常、オプティマイザの zero_grad() メソッドを使って行います。

また、勾配計算を無効にしたい場合は、torch.no_grad() コンテキストマネージャーを使用します。これは、モデルの推論時など、学習を行わない場合にメモリ使用量や計算速度を抑えるために便利です。

“`python
x = torch.ones(5, requires_grad=True)
print(f”x.requires_grad: {x.requires_grad}”)

with torch.no_grad():
y = x + 2
print(f”y.requires_grad inside no_grad: {y.requires_grad}”)

y = x + 2
print(f”y.requires_grad outside no_grad: {y.requires_grad}”)
“`

出力例:
x.requires_grad: True
y.requires_grad inside no_grad: False
y.requires_grad outside no_grad: True

このように、autogradはPyTorchでディープラーニングモデルを学習させる上で非常に重要な役割を果たします。

ディープラーニングの基本構成要素

PyTorchを使ってディープラーニングモデルを構築し、学習させるためには、いくつかの基本的な構成要素を理解する必要があります。

  1. モデル (Model): 入力データを受け取り、予測を出力するニューラルネットワークそのものです。PyTorchでは torch.nn.Module クラスを継承して定義するのが一般的です。
  2. 損失関数 (Loss Function): モデルの予測値と正解値との間の誤差を定量化する関数です。学習の目的は、この損失関数の値を最小化することです。タスクに応じて適切な損失関数を選択します(例: 回帰ならMSE、分類ならCrossEntropyLoss)。
  3. 最適化手法 (Optimizer): 損失関数の値を最小化するために、モデルのパラメータ(重みやバイアス)をどのように更新するかを決定するアルゴリズムです。勾配降下法(Gradient Descent)をベースとした様々な手法があります(例: SGD, Adam, RMSprop)。

これらの要素を組み合わせて、学習ループ(Training Loop)を実行することで、モデルをデータに適合させていきます。

PyTorchでモデルを構築する (torch.nn)

PyTorchでは、ニューラルネットワークの各層や活性化関数などのビルディングブロックが torch.nn モジュールに豊富に用意されています。モデルは通常、nn.Module を継承したクラスとして定義します。

nn.Module を継承したクラスを定義する際には、以下の2つのメソッドを実装するのが基本です。

  • __init__(self, ...): モデルで使用する各層やその他のモジュールをここで定義・初期化します。
  • forward(self, x): 入力 x を受け取り、モデルを通じて計算を行い、出力を返すメソッドです。ニューラルネットワークの順伝播の処理を記述します。

簡単な線形回帰モデルを例に見てみましょう。入力1つ、出力1つの線形モデル $y = wx + b$ を考えます。

“`python
import torch
from torch import nn

nn.Module を継承してモデルクラスを定義

class LinearRegressionModel(nn.Module):
def init(self):
super().init()
# 線形変換を行う層 nn.Linear を定義
# 入力サイズ1、出力サイズ1
self.linear = nn.Linear(1, 1)

def forward(self, x):
    # 順伝播の処理
    return self.linear(x)

モデルのインスタンスを作成

model = LinearRegressionModel()

print(model) # モデルの構造が表示される
“`

出力例:
LinearRegressionModel(
(linear): Linear(in_features=1, out_features=1, bias=True)
)

nn.Linear(in_features, out_features) は、入力テンソルに重み行列をかけてバイアスベクトルを加える線形変換 $y = xW^T + b$ を行います(torch.nn.Linear のドキュメントでは $y = xA^T + b$ と書かれていますが、ここでは $W^T$ を $W$ として $y = xW + b$ として説明することも多いです。どちらの表現でも本質は同じです)。in_features は入力テンソルの最後の次元のサイズ、out_features は出力テンソルの最後の次元のサイズです。

このモデルのパラメータ(重みとバイアス)は model.parameters() メソッドで取得できます。これらのパラメータが requires_grad=True となっており、学習中に勾配が計算され、更新されます。

python
for name, param in model.named_parameters():
print(f"Layer: {name} | Size: {param.size()} | Values: {param[:2]}")

出力例:
Layer: linear.weight | Size: torch.Size([1, 1]) | Values: tensor([[-0.1234]], requires_grad=True) # 初期値はランダム
Layer: linear.bias | Size: torch.Size([1]) | Values: tensor([0.5678], requires_grad=True) # 初期値はランダム

nn.Module クラスには、様々なレイヤーが用意されています。いくつか代表的なものを挙げます。

  • nn.Linear: 全結合層(線形変換)
  • nn.Conv1d, nn.Conv2d, nn.Conv3d: 畳み込み層(1D, 2D, 3D)
  • nn.ReLU, nn.Sigmoid, nn.Tanh: 活性化関数
  • nn.MaxPool1d, nn.MaxPool2d: 最大プーリング層
  • nn.Dropout: ドロップアウト層(正則化)
  • nn.LSTM, nn.GRU: リカレントニューラルネットワーク関連
  • nn.Transformer: Transformerモデルの構成要素

これらの層を __init__ で組み合わせて、forward メソッドでデータが流れる順序を定義することで、複雑なニューラルネットワークを構築できます。

損失関数 (torch.nn)

損失関数も torch.nn モジュールに含まれています。損失関数も nn.Module のサブクラスとして実装されており、インスタンスを作成して使用します。

“`python

回帰問題でよく使われる平均二乗誤差 (Mean Squared Error)

loss_fn_mse = nn.MSELoss()

分類問題でよく使われるクロスエントロピー誤差 (Cross Entropy Loss)

入力はモデルの出力(ロジット)、ターゲットはクラスのインデックスまたは確率分布

loss_fn_ce = nn.CrossEntropyLoss()
“`

損失関数のインスタンスは、モデルの出力と正解ラベル(またはターゲット)を引数として受け取り、損失値(スカラーテンソル)を返します。

“`python

例:回帰問題

output = torch.tensor([1.5], requires_grad=True)
target = torch.tensor([1.0])
loss = loss_fn_mse(output, target)
print(f”MSE Loss: {loss}”) # (1.5 – 1.0)^2 = 0.25

例:分類問題 (バッチサイズ=1, クラス数=3)

output = torch.tensor([[0.1, 0.5, 0.4]], requires_grad=True) # モデルの出力(ロジット)
target = torch.tensor([1]) # 正解クラスのインデックス (クラス0, 1, 2の中からクラス1が正解)
loss = loss_fn_ce(output, target)
print(f”Cross Entropy Loss: {loss}”) # -log(softmax(output)[target]) に相当
“`

出力例:
MSE Loss: tensor(0.2500, grad_fn=<MseLossBackward0>)
Cross Entropy Loss: tensor(1.0439, grad_fn=<NllLossBackward0>) # 値は出力テンソルによって変わります

損失値はスカラーテンソルであり、requires_grad=True となっています。この損失値に対して .backward() を呼び出すことで、モデルのパラメータに対する勾配が計算されます。

最適化手法 (torch.optim)

損失関数の勾配を使って、モデルのパラメータを更新するのが最適化手法です。PyTorchでは torch.optim モジュールに様々な最適化アルゴリズムが用意されています。

最適化アルゴリズムのインスタンスを作成する際には、どのパラメータを最適化するか(通常はモデルの parameters() メソッドで取得)、そして学習率(Learning Rate)などのハイパーパラメータを指定します。

“`python

線形回帰モデルのパラメータをSGDオプティマイザで最適化

optimizer_sgd = torch.optim.SGD(model.parameters(), lr=0.01)

分類モデルのパラメータをAdamオプティマイザで最適化

optimizer_adam = torch.optim.Adam(model.parameters(), lr=0.001)

“`

オプティマイザは、学習ループの中で以下の3つのステップで使用します。

  1. optimizer.zero_grad(): 前回の逆伝播で計算された勾配をクリアします。勾配はデフォルトで累積されるため、新しいミニバッチで学習を行う前に必ず実行する必要があります。
  2. loss.backward(): 損失関数に対して逆伝播を実行し、各パラメータの勾配を計算します。
  3. optimizer.step(): 計算された勾配を使って、各パラメータを更新します。

これらのステップは、勾配降下法の更新ルール $param = param – learning_rate \times gradient$ を実装しています。

簡単な実装例:線形回帰

ここまでの知識を使って、最もシンプルなディープラーニングモデルの一つである線形回帰モデルを学習させてみましょう。目的は、人工的に生成したデータ $y = 2x + 1$ にノイズを加えたデータセットから、元の直線の方程式(係数2と切片1)を学習することです。

“`python
import torch
from torch import nn
import matplotlib.pyplot as plt

1. データ生成

torch.manual_seed(42) # 再現性のためのシード設定
X = torch.randn(100, 1) * 10 # -10から10の範囲でランダムな100個のx値を生成
y = 2 * X + 1 + torch.randn(100, 1) # y = 2x + 1 にノイズを加える

データをテンソルとして確認

print(f”X shape: {X.shape}”)
print(f”y shape: {y.shape}”)

2. モデル定義 (先ほど定義したものと同じ)

class LinearRegressionModel(nn.Module):
def init(self):
super().init()
self.linear = nn.Linear(1, 1)

def forward(self, x):
    return self.linear(x)

model = LinearRegressionModel()
print(model)

モデルの初期パラメータを確認

print(“Initial parameters:”)
for name, param in model.named_parameters():
print(f” {name}: {param.data.squeeze()}”)

3. 損失関数と最適化手法の定義

loss_fn = nn.MSELoss() # 平均二乗誤差
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 確率的勾配降下法, 学習率0.01

4. 学習ループ

num_epochs = 100 # 学習エポック数
losses = [] # 損失値を記録するため

print(“\nStarting training…”)
for epoch in range(num_epochs):
# 順伝播: モデルに入力Xを通して予測y_predを計算
y_pred = model(X)

# 損失計算: 予測y_predと正解yの間の損失を計算
loss = loss_fn(y_pred, y)
losses.append(loss.item()) # .item() でテンソルからPythonの数値に変換

# 逆伝播: 損失に対する各パラメータの勾配を計算
# 注意: 新しいエポックの前に必ず勾配をゼロクリアする
optimizer.zero_grad()
loss.backward()

# パラメータ更新: 計算された勾配を使ってパラメータを更新
optimizer.step()

# エポックごとに損失を表示
if (epoch + 1) % 10 == 0:
    print(f"Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}")

print(“Training finished.”)

5. 学習後のモデルパラメータを確認

print(“\nLearned parameters:”)
for name, param in model.named_parameters():
print(f” {name}: {param.data.squeeze()}”)

6. 結果の可視化 (Optional)

predicted = model(X).detach().numpy() # 推論結果を取得し、detach().numpy()でNumPy配列に変換 (勾配計算から切り離す)
X_np = X.numpy()
y_np = y.numpy()

plt.figure(figsize=(8, 6))
plt.plot(losses)
plt.title(“Training Loss over Epochs”)
plt.xlabel(“Epoch”)
plt.ylabel(“MSE Loss”)
plt.grid(True)
plt.show()

plt.figure(figsize=(8, 6))
plt.scatter(X_np, y_np, label=”Original Data”, alpha=0.6)
plt.plot(X_np, predicted, color=’red’, label=”Linear Regression Fit”)
plt.title(“Linear Regression Result”)
plt.xlabel(“X”)
plt.ylabel(“y”)
plt.legend()
plt.grid(True)
plt.show()
“`

このコードを実行すると、損失が徐々に減少し、最終的にモデルが学習したパラメータ(重みとバイアス)が、データ生成に使った真の値(2と1)に近い値になっていることが確認できます。また、散布図には学習後の直線がきれいにフィットしている様子が描画されます。

このシンプルな例を通して、以下のPyTorchによるディープラーニング開発の基本的な流れを掴めたはずです。

  1. データを準備する(ここでは人工的に生成)
  2. nn.Module を継承してモデルを定義する
  3. 損失関数 (nn.*Loss) を定義する
  4. 最適化手法 (torch.optim.*) を定義し、モデルのパラメータを渡す
  5. 学習ループの中で、以下のステップを繰り返す:
    a. 順伝播で予測値を計算
    b. 損失を計算
    c. オプティマイザの zero_grad() で勾配をクリア
    d. 損失に対して backward() で勾配を計算
    e. オプティマイザの step() でパラメータを更新
  6. 学習後のモデルを使って予測を行う

データセットとDataLoader (torch.utils.data)

実際のディープラーニング開発では、大量のデータを使います。これらのデータを効率的に扱いため、PyTorchには torch.utils.data モジュールが用意されています。このモジュールには、データセットを表す Dataset クラスと、ミニバッチ処理やデータシャッフルなどを担う DataLoader クラスが含まれています。

  • Dataset: データセット全体を表現する抽象クラスです。これを継承してカスタムデータセットを作成する場合、最低限 __len__(self) (データセットのサイズを返す)と __getitem__(self, idx) (指定されたインデックス idx のサンプルを返す)の2つのメソッドを実装する必要があります。PyTorchは多くの標準的なデータセット (torchvision.datasets のMNIST, CIFAR10など) を Dataset の形式で提供しています。
  • DataLoader: Dataset からデータを読み込み、ミニバッチにまとめて提供するイテレータです。バッチサイズ、データのシャッフル、並列処理(num_workers)などを設定できます。学習時には、このDataLoaderを使ってデータを少しずつ(ミニバッチ単位で)モデルに入力します。

ミニバッチ学習を行う主な理由は以下の通りです。

  • メモリ効率: 大量のデータを一度にメモリに載せる必要がなくなります。
  • 計算効率: 特にGPUを使う場合、ミニバッチ単位で並列計算を行うことで高速化できます。
  • 安定性: 全データで勾配を計算する(バッチ学習)よりも、ミニバッチごとに勾配を計算する(ミニバッチ学習)方が、更新方向にある程度のランダム性が加わり、局所最適解に陥りにくくなる場合があります。

先ほどの線形回帰の例では、全データ(100個)を一度にモデルに入力しましたが、これをミニバッチ処理に書き換えてみましょう。

“`python
import torch
from torch import nn
from torch.utils.data import Dataset, DataLoader

1. データ生成 (再掲)

torch.manual_seed(42)
X = torch.randn(100, 1) * 10
y = 2 * X + 1 + torch.randn(100, 1)

2. カスタムDatasetクラスの定義

class CustomRegressionDataset(Dataset):
def init(self, X, y):
self.X = X
self.y = y

def __len__(self):
    return len(self.X) # データセットのサイズを返す

def __getitem__(self, idx):
    # 指定されたインデックスの(入力, 正解)ペアを返す
    return self.X[idx], self.y[idx]

Datasetインスタンスを作成

dataset = CustomRegressionDataset(X, y)

3. DataLoaderの作成

batch_size = 10 # ミニバッチサイズ
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True) # shuffle=Trueでエポックごとにデータをシャッフル

DataLoaderの使い方(イテレータとしてforループで回せる)

print(“DataLoader batches:”)
for batch_X, batch_y in dataloader:
print(f” Batch X shape: {batch_X.shape}, Batch y shape: {batch_y.shape}”)
break # 最初のバッチだけ表示

4. モデル、損失関数、最適化手法の定義 (先ほどと同じ)

class LinearRegressionModel(nn.Module):
def init(self):
super().init()
self.linear = nn.Linear(1, 1)
def forward(self, x):
return self.linear(x)

model = LinearRegressionModel()
loss_fn = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

5. 学習ループ (DataLoaderを使用)

num_epochs = 100
losses = []

print(“\nStarting training with DataLoader…”)
for epoch in range(num_epochs):
epoch_loss = 0
for batch_X, batch_y in dataloader:
# 順伝播
y_pred = model(batch_X)

    # 損失計算
    loss = loss_fn(y_pred, batch_y)
    epoch_loss += loss.item()

    # 逆伝播とパラメータ更新
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

avg_epoch_loss = epoch_loss / len(dataloader) # 1エポックの平均損失
losses.append(avg_epoch_loss)

# エポックごとに損失を表示
if (epoch + 1) % 10 == 0:
    print(f"Epoch [{epoch+1}/{num_epochs}], Avg Loss: {avg_epoch_loss:.4f}")

print(“Training finished.”)

学習後のモデルパラメータを確認 (先ほどと同じ)

print(“\nLearned parameters:”)
for name, param in model.named_parameters():
print(f” {name}: {param.data.squeeze()}”)

結果の可視化 (Optional) – 学習後の予測直線のプロットは同じコードで可能

損失のプロットは平均損失を使う

plt.figure(figsize=(8, 6))
plt.plot(losses)
plt.title(“Training Average Loss over Epochs (with DataLoader)”)
plt.xlabel(“Epoch”)
plt.ylabel(“Average MSE Loss”)
plt.grid(True)
plt.show()

予測直線のプロット (再掲)

predicted = model(X).detach().numpy()
X_np = X.numpy()
y_np = y.numpy()
plt.figure(figsize=(8, 6))
plt.scatter(X_np, y_np, label=”Original Data”, alpha=0.6)
plt.plot(X_np, predicted, color=’red’, label=”Linear Regression Fit”)
plt.title(“Linear Regression Result (with DataLoader)”)
plt.xlabel(“X”)
plt.ylabel(“y”)
plt.legend()
plt.grid(True)
plt.show()
“`

このコードでは、人工データから CustomRegressionDataset を作成し、それを DataLoader に渡しています。学習ループでは、dataloader をイテレータとして回すことで、ミニバッチ単位でデータを取り出して学習を行っています。実際のディープラーニング開発では、提供されている torchvisiontorchtext などのデータセットを使うか、独自のデータを Dataset クラスとしてラップして DataLoader で扱うのが標準的な流れとなります。

GPUを活用する

ディープラーニングの計算は、特に大規模なモデルやデータセットを扱う場合、CPUだけでは非常に時間がかかります。NVIDIA製のGPUは、多数のコアを持っており、行列演算のようなディープラーニングで頻繁に行われる計算を非常に高速に実行できます。PyTorchはGPUでの計算を強力にサポートしています。

GPUを使うためには、CUDAというNVIDIAが提供する並列コンピューティングプラットフォームが必要です。PyTorchのGPU対応版をインストールしていれば、以下の手順でGPUを利用できます。

  1. GPUが利用可能か確認: torch.cuda.is_available() で確認できます。
  2. 使用するデバイスを設定: torch.device オブジェクトを作成します。GPUを使いたい場合は 'cuda'、CPUを使いたい場合は 'cpu' と指定します。GPUが複数ある場合は 'cuda:0', 'cuda:1' のようにインデックスを指定します。
  3. モデルとデータをデバイスに移動: 作成したモデルのインスタンスと、学習・推論に使うデータを、.to(device) メソッドを使って指定したデバイスに移動させます。モデルもデータも同じデバイス上に存在している必要があることに注意してください。

先ほどの線形回帰の例にGPU対応を追加してみましょう。

“`python
import torch
from torch import nn
from torch.utils.data import Dataset, DataLoader
import matplotlib.pyplot as plt

1. デバイスの設定

device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”)
print(f”Using device: {device}”)

2. データ生成 (再掲) – データもデバイスに移動させる

torch.manual_seed(42)
X = torch.randn(100, 1) * 10
y = 2 * X + 1 + torch.randn(100, 1)

データをデバイスに移動

X = X.to(device)
y = y.to(device)

3. カスタムDatasetクラス (デバイス移動はDataLoaderの前にデータ自体に対して行うのが一般的)

class CustomRegressionDataset(Dataset):
def init(self, X, y):
self.X = X
self.y = y

def __len__(self):
    return len(self.X)

def __getitem__(self, idx):
    return self.X[idx], self.y[idx]

dataset = CustomRegressionDataset(X, y)
batch_size = 10

DataLoaderはデータ自体がデバイスに移動済みであればそのまま使える

もしくは、DataLoader内でbatch_X, batch_yをdeviceに移動させることも可能

dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)

4. モデル定義 (再掲) – モデルもデバイスに移動させる

class LinearRegressionModel(nn.Module):
def init(self):
super().init()
self.linear = nn.Linear(1, 1)

def forward(self, x):
    return self.linear(x)

model = LinearRegressionModel()
model.to(device) # !!! モデルをデバイスに移動 !!!
print(f”Model is on device: {next(model.parameters()).device}”) # モデルのパラメータがどこにあるか確認

5. 損失関数と最適化手法の定義 (再掲)

loss_fn = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

6. 学習ループ (DataLoaderを使用)

num_epochs = 100
losses = []

print(“\nStarting training with DataLoader and GPU support…”)
for epoch in range(num_epochs):
epoch_loss = 0
for batch_X, batch_y in dataloader:
# DataLoaderから取得したデータは既にdevice上にあるはず
# batch_X = batch_X.to(device) # もしデータセット作成時にデバイス移動していなければここで移動
# batch_y = batch_y.to(device) # もしデータセット作成時にデバイス移動していなければここで移動

    # 順伝播
    y_pred = model(batch_X)

    # 損失計算
    loss = loss_fn(y_pred, batch_y)
    epoch_loss += loss.item()

    # 逆伝播とパラメータ更新
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

avg_epoch_loss = epoch_loss / len(dataloader)
losses.append(avg_epoch_loss)

if (epoch + 1) % 10 == 0:
    print(f"Epoch [{epoch+1}/{num_epochs}], Avg Loss: {avg_epoch_loss:.4f}")

print(“Training finished.”)

7. 学習後のモデルパラメータを確認 (再掲)

print(“\nLearned parameters:”)
for name, param in model.named_parameters():
print(f” {name}: {param.data.squeeze()}”) # .data 属性を使うか .cpu() でCPUに戻してからnumpy()にするのが一般的

8. 結果の可視化 (Optional) – 推論はCPUで行う場合はCPUに戻す

X, y をCPUに戻すか、新たにCPU上でデータを作成して推論

例: 元のCPU上のX, yデータがある場合

predicted = model(X.to(“cpu”)).detach().numpy() # 必要なら入力もCPUに移動

または学習に使ったXをCPUに戻す

X_cpu = X.to(“cpu”)
y_cpu = y.to(“cpu”)
predicted_cpu = model(X_cpu).detach().numpy() # 推論結果もCPU上のテンソルになる

plt.figure(figsize=(8, 6))
plt.plot(losses)
plt.title(“Training Average Loss over Epochs (with DataLoader & GPU)”)
plt.xlabel(“Epoch”)
plt.ylabel(“Average MSE Loss”)
plt.grid(True)
plt.show()

plt.figure(figsize=(8, 6))
plt.scatter(X_cpu.numpy(), y_cpu.numpy(), label=”Original Data”, alpha=0.6)
plt.plot(X_cpu.numpy(), predicted_cpu, color=’red’, label=”Linear Regression Fit”)
plt.title(“Linear Regression Result (with DataLoader & GPU)”)
plt.xlabel(“X”)
plt.ylabel(“y”)
plt.legend()
plt.grid(True)
plt.show()
“`

このコードでは、device 変数を使ってCPUまたはGPUを選択し、モデルとデータをそのデバイスに移動させています。GPUが利用可能な環境であれば、実際にGPUが使われます。線形回帰のような簡単な例ではGPUによる高速化はほとんど体感できませんが、画像認識や自然言語処理のような複雑なモデルでは、GPUの利用が必須となります。

推論結果をNumPy配列に変換したり、matplotlibでプロットしたりする場合は、テンソルがCPU上にある必要があることに注意してください。.to("cpu") メソッドでCPUに移動させてから .numpy() を呼び出します。また、.detach() は、推論時には勾配計算のグラフから切り離すためによく使われます。

モデルの保存と読み込み

学習したモデルは、後で再利用したり、他の人と共有したりするために保存する必要があります。PyTorchでは、モデル全体を保存する方法と、モデルのパラメータ(state_dict)だけを保存する方法があります。一般的には、パラメータ(state_dict)だけを保存する方法が推奨されます。なぜなら、モデルのクラス定義があれば、同じ構造のモデルインスタンスを作成し、そこに保存したパラメータを読み込むことができるため、より柔軟でファイルサイズも小さくなるからです。

パラメータ (state_dict) の保存と読み込み

model.state_dict() は、モデルの各層のパラメータ(重みやバイアスなど)をキーと値のペアで持つOrderedDictです。これを torch.save() でファイルに保存します。

“`python

学習済みのmodelインスタンスがあるとして…

保存するパスを指定

MODEL_PATH = “linear_regression_model.pth” # .pth や .pt という拡張子を使うのが一般的

モデルのstate_dictを保存

torch.save(model.state_dict(), MODEL_PATH)

print(f”Model state_dict saved to {MODEL_PATH}”)
“`

学習済みのパラメータを読み込むには、まず同じ構造のモデルインスタンスを作成し、そこに保存した state_dictmodel.load_state_dict() メソッドでロードします。

“`python

新しい(学習前の)モデルインスタンスを作成

loaded_model = LinearRegressionModel() # 必ず学習時と同じモデルクラスを使う

保存したstate_dictをロード

loaded_state_dict = torch.load(MODEL_PATH)

ロードしたstate_dictをモデルに読み込む

loaded_model.load_state_dict(loaded_state_dict)

print(“Model state_dict loaded successfully.”)

ロードしたモデルで予測してみる(推論モードにすることを忘れずに)

loaded_model.eval() # 推論モードに設定 (DropoutやBatchNormなどの挙動が変わる)

推論モードでは勾配計算を無効にするのが一般的

with torch.no_grad():
# 例: 新しい入力に対する予測
new_x = torch.tensor([[15.0]]).to(device) # 学習に使ったデバイスと同じデバイスに移動
prediction = loaded_model(new_x)
print(f”Prediction for x=15: {prediction.item()}”) # .item()でPython数値に変換

学習に使ったデータ全体で予測し、学習結果と比較することもできる

X_cpu に戻した学習データを使う

with torch.no_grad():
predicted_loaded_cpu = loaded_model(X_cpu.to(device)).to(“cpu”).numpy() # デバイスに注意

predicted_loaded_cpu と predicted_cpu が同じ値になるはず

“`

モデル全体を保存と読み込み (非推奨の場合あり)

torch.save(model, PATH) でモデルの構造とパラメータの両方をまとめて保存することも可能ですが、非推奨とされる場合が多いです。特に、異なるPython環境やPyTorchのバージョン間での互換性の問題が発生しやすいです。特殊なモデル構成でない限りは、state_dict を保存・ロードする方法が一般的です。

さらに学ぶために

この記事では、PyTorchの超入門として、テンソルの操作、自動微分、基本的なモデル構築、学習ループ、DataLoader、GPU活用、モデルの保存・読み込みといった核となる概念と使い方をカバーしました。

ここからディープラーニングの世界をさらに深く探求するためには、以下のステップに進むことをお勧めします。

  1. 画像分類 (CNN): torchvision ライブラリを使って、MNISTやCIFAR10のような標準的な画像データセットに対する畳み込みニューラルネットワーク (CNN) モデルの実装に挑戦してみましょう。CNNは画像認識タスクで非常に強力なモデルです。
  2. 自然言語処理 (RNN, LSTM, Transformer): torchtexttransformers (Hugging Face) ライブラリを使って、テキストデータに対するリカレントニューラルネットワーク (RNN) やLSTM、あるいはTransformerモデルの実装に挑戦してみましょう。
  3. 転移学習: 学習済みの大規模モデルの一部を再利用し、新しいタスクに適用する「転移学習」は非常に強力なテクニックです。torchvision.models には多くの学習済みモデルが用意されており、これらを使った転移学習を試してみましょう。
  4. 他のタスク: 物体検出、セマンティックセグメンテーション、音声認識、強化学習など、PyTorchで実装できる様々なディープラーニングタスクに触れてみましょう。
  5. PyTorchの内部: nn.Module の詳細、カスタムレイヤーの実装、カスタム損失関数、カスタム最適化手法など、PyTorchのより高度な機能について学んでみましょう。
  6. 実践: 実際に自分で興味のあるデータセットを見つけて、PyTorchを使ってディープラーニングモデルを構築し、学習・評価するプロジェクトを行ってみましょう。Kaggleなどのコンペティションに参加するのも良い経験になります。

参考リソース:

  • PyTorch公式ドキュメント: 最も信頼できる情報源です。チュートリアルやAPIリファレンスが充実しています。(https://pytorch.org/docs/stable/index.html
  • PyTorch公式チュートリアル: 様々なタスクや機能に関するステップバイステップのチュートリアルが豊富に用意されています。(https://pytorch.org/tutorials/
  • 書籍: PyTorchに関する入門書や応用書も多数出版されています。ご自身の学習スタイルに合ったものを選んでみましょう。
  • オンラインコース: Coursera, edX, Udemyなどのオンラインプラットフォームには、PyTorchを使ったディープラーニングのコースが多数あります。
  • GitHub: 多くの研究者や開発者がPyTorchで実装したコードをGitHubで公開しています。興味のある論文やプロジェクトの実装コードを読んでみるのも勉強になります。

まとめ

本記事では、PyTorchを使ったディープラーニング開発の基礎を超入門レベルで解説しました。

  • PyTorchがPythonに馴染みやすい動的な計算グラフを持つ強力なフレームワークであること
  • データ操作の基本となるテンソルとその演算について
  • 勾配計算を自動で行うAutogradの仕組みと .backward() の使い方
  • ディープラーニングモデルを構成する要素(モデル、損失関数、最適化手法)
  • torch.nn モジュールを使ったモデルの定義方法
  • 簡単な線形回帰モデルの実装を通して、PyTorchによる学習ループの基本的な流れ
  • torch.utils.data モジュールを使ったデータセットとDataLoaderの活用方法
  • GPUを使った計算高速化の方法
  • 学習済みモデルの保存と読み込み方法

これらはPyTorchを使ったディープラーニング開発のほんの始まりに過ぎませんが、ここまでの内容を理解すれば、PyTorchを使って様々なディープラーニングモデルを実装するための土台はできています。

ディープラーニングは奥深く、常に進化している分野ですが、PyTorchという強力なツールを使えば、そのエキサイティングな世界へ比較的容易に足を踏み入れることができます。この記事が、あなたのPyTorchを使ったディープラーニング開発の第一歩となることを願っています。

焦らず、楽しみながら、コードを書いて動かしてみて、ディープラーニングの世界を探求していきましょう!


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール