はい、承知いたしました。【初心者必見】PyTorchでディープラーニングを始めよう、というテーマで、約5000語の詳細な記事を作成します。
【初心者必見】PyTorchでディープラーニングを始めよう
ディープラーニングは、近年目覚ましい発展を遂げ、画像認識、自然言語処理、音声認識、推薦システムなど、様々な分野で革新的な成果をもたらしています。この強力な技術を学びたいと考えている初心者の方にとって、どのフレームワークを選べば良いのか、どうやって学習を進めれば良いのか、迷うことも多いでしょう。
この記事では、ディープラーニングフレームワークの中でも、特に研究者やエンジニアから支持されている PyTorch(パイトーチ) に焦点を当て、その基本的な使い方から、簡単なニューラルネットワークの構築、そして実際の学習プロセスまでを、初心者向けに分かりやすく解説します。この記事を読めば、PyTorchを使ったディープラーニングの第一歩を踏み出すことができるでしょう。
1. はじめに:ディープラーニングとPyTorch
1.1 ディープラーニングとは何か?
ディープラーニング(深層学習)は、機械学習の一分野であり、人間の脳の神経回路網を模倣した「ニューラルネットワーク」を多層(深く)にしたモデルを使った学習方法です。従来の機械学習手法では、データから特徴量を手動で抽出する必要がありましたが、ディープラーニングはデータ(画像や音声など)から自動的に階層的な特徴量を学習することができます。
例えば、画像認識タスクの場合、浅い層では線の方向や色の塊といった単純な特徴を学習し、深い層に進むにつれて、それらを組み合わせて目、鼻、口といったより複雑な特徴や、最終的には「猫」「犬」といった対象そのものを認識するための特徴を学習します。
この「自動的な特徴学習」能力が、ディープラーニングが多くの問題で高い性能を発揮できる理由です。
1.2 なぜPyTorchを選ぶのか?
ディープラーニングフレームワークには、PyTorchの他にTensorFlow、Keras、Chainerなど、様々な選択肢があります。その中でもPyTorchは、特に以下のような理由で初心者から上級者まで幅広く使われています。
- Pythonとの親和性: PyTorchはPythonで書かれており、Pythonの豊富なライブラリ(NumPy, SciPy, scikit-learnなど)とスムーズに連携できます。コードが直感的で、Pythonの標準的な書き方に近いため、Python経験者にとっては習得しやすいでしょう。
- Define by Run(動的計算グラフ): PyTorchは、コードの実行時に計算グラフを構築します。これにより、プログラムの制御フロー(条件分岐やループ)を計算グラフに組み込みやすく、デバッグが容易で、複雑なモデル構造やリカレントニューラルネットワーク(RNN)のようなモデルを柔軟に扱うことができます。これは、Define by Compile(静的計算グラフ)を基本としていた古いTensorFlowとの大きな違いでしたが、最近のTensorFlow(TensorFlow 2.x)も動的計算グラフに対応しており、この差は縮まっています。しかし、PyTorchの動的な性質は、特に研究開発フェーズでの柔軟性の高さとして依然評価されています。
- 豊富な機能と活発なコミュニティ: ニューラルネットワークの構築に必要な様々な層や関数、学習用のツールが豊富に用意されています。また、Facebook (Meta) が中心となって開発を進めており、コミュニティが活発で、情報や最新の研究成果に基づいたモデルが共有されやすい環境があります。
- 使いやすいAPI: 直感的で分かりやすいAPI(Application Programming Interface)が設計されています。
これらの特徴から、PyTorchは特に研究開発やプロトタイピングに適していると言われますが、その使いやすさから初心者にも非常におすすめできるフレームワークです。
1.3 この記事で学ぶこと
この記事では、PyTorchを使ったディープラーニングの基礎を学ぶために、以下の内容を順を追って解説します。
- PyTorchのインストールと基本的な準備
- PyTorchの根幹である「Tensor」の操作
- 自動微分機能「Autograd」の仕組みと使い方
- ニューラルネットワークモデルの構築方法
- データセットの準備と管理
- 損失関数とオプティマイザ
- 実際の学習ループの実装
- 簡単な実践例(画像分類)
この記事を読み終える頃には、PyTorchを使って簡単なニューラルネットワークを構築し、データを学習させ、評価することができるようになっているはずです。
2. PyTorchの準備
ディープラーニングを始めるには、まずPyTorchを使える環境を構築する必要があります。
2.1 必要な環境
- Python: PyTorchはPythonで動作します。Python 3.6以上が推奨されます。
- 開発環境: コードを書くためのテキストエディタやIDE(統合開発環境)が必要です。VS Code, PyCharm, Jupyter Notebook/Labなどがよく使われます。初心者の方は、コードの実行結果をすぐに確認できるJupyter Notebook/Labから始めるのがおすすめです。
- パッケージ管理ツール: Pythonのライブラリ管理には、
pip
やconda
が便利です。特にconda
は、仮想環境の管理も容易に行えるため推奨されます。AnacondaやMinicondaをインストールするとconda
が使えるようになります。 - GPU (任意): ディープラーニングの計算は大量の並列計算を含むため、GPUを利用できると学習速度が飛躍的に向上します。NVIDIA製のGPUとCUDAという技術が必要です。必須ではありませんが、本格的に取り組む場合はGPU環境の準備を検討しましょう。この記事では、GPUがなくても学習できるよう、まずはCPUでの実行を前提に解説しますが、GPUを利用するための設定方法も紹介します。
2.2 PyTorchのインストール方法
PyTorchのインストール方法は、使用するOS、Pythonのバージョン、GPUを利用するかどうかによって異なります。公式ウェブサイトのインストール手順(https://pytorch.org/get-started/locally/)を参考に進めるのが最も確実です。ここでは一般的な方法をいくつか紹介します。
a) Condaを使用する場合 (推奨)
AnacondaまたはMinicondaをインストール済みの場合は、condaコマンドを使って簡単にインストールできます。GPUを使う場合は、お使いのNVIDIAドライバーに対応したCUDAツールキットのバージョンを指定する必要があります。
“`bash
GPUを使用する場合 (CUDA 11.8 の例)
必要に応じて cuda-toolkit=11.8 の部分を適切なバージョンに書き換えてください
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia
CPUのみを使用する場合
conda install pytorch torchvision torchaudio cpuonly -c pytorch
“`
-c pytorch -c nvidia
は、パッケージの取得元チャンネルを指定しています。
b) Pipを使用する場合
pipコマンドでもインストールできます。こちらもGPU版とCPU版があります。
“`bash
GPUを使用する場合 (CUDA 11.8 の例)
必要に応じて –index-url … の部分を適切なバージョンに書き換えてください
pip install torch torchvision torchaudio –index-url https://download.pytorch.org/whl/cu118
CPUのみを使用する場合
pip install torch torchvision torchaudio –index-url https://download.pytorch.org/whl/cpu
“`
ご自身の環境に合わせて、適切なコマンドを選択してください。仮想環境を作成してからインストールすることをおすすめします。
例:conda create -n myenv python=3.9
-> conda activate myenv
-> conda install ...
2.3 インストール確認とGPUの確認
インストールが完了したら、PythonインタプリタやJupyter Notebookで以下のコードを実行して、正しくインストールされているか、GPUが利用可能かを確認しましょう。
“`python
import torch
PyTorchのバージョンを確認
print(f”PyTorch version: {torch.version}”)
CUDA (GPU) が利用可能か確認
if torch.cuda.is_available():
print(“CUDA is available. Using GPU.”)
print(f”Number of GPUs available: {torch.cuda.device_count()}”)
print(f”GPU Name: {torch.cuda.get_device_name(0)}”) # 最初のGPUの名前を表示
else:
print(“CUDA is not available. Using CPU.”)
利用するデバイスを設定
device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”)
print(f”Using device: {device}”)
“`
出力でPyTorchのバージョンが表示され、GPUが利用可能であればその情報も表示されるはずです。Using device: cuda
と表示されれば、GPUを使って計算できる状態です。
3. PyTorchの基本要素:TensorとAutograd
PyTorchを使ったプログラミングは、主に以下の2つの核となる要素に基づいています。
- Tensor: 多次元配列を扱うためのクラス。NumPyの配列と似ていますが、GPUでの計算や自動微分に対応しています。
- Autograd: Tensorを使った計算の勾配を自動的に計算する機能。ディープラーニングにおける逆伝播(バックプロパゲーション)に不可欠です。
3.1 Tensorとは
Tensorは、PyTorchにおけるデータの基本単位です。スカラー(0次元)、ベクトル(1次元)、行列(2次元)など、任意の次元を持つことができます。ディープラーニングモデルへの入力データ、モデルのパラメータ(重みやバイアス)、計算結果などは全てTensorとして表現されます。
a) Tensorの生成
様々な方法でTensorを生成できます。
“`python
import torch
未初期化のTensor
x = torch.empty(5, 3) # 形状が5×3のTensorを生成。値は不定。
print(x)
全て0のTensor
x = torch.zeros(5, 3, dtype=torch.long) # 形状5×3、データ型longで全て0
print(x)
全て1のTensor
x = torch.ones(5, 3, dtype=torch.float) # 形状5×3、データ型floatで全て1
print(x)
ランダムな値のTensor
x = torch.rand(5, 3) # 形状5×3で[0, 1)の一様乱数
print(x)
PythonリストやNumPy配列からTensorを生成
import numpy as np
data = [[1, 2], [3, 4]]
x_data = torch.tensor(data) # Pythonリストから
print(x_data)
np_array = np.array(data)
x_np = torch.from_numpy(np_array) # NumPy配列から
print(x_np)
“`
b) Tensorの属性
Tensorは、その形状(shape)、データ型(dtype)、格納されているデバイス(device)などの属性を持っています。
python
x = torch.rand(5, 3)
print(f"Shape of x: {x.shape}")
print(f"Data type of x: {x.dtype}")
print(f"Device of x: {x.device}") # デフォルトはCPU
c) Tensorの操作
Tensorは様々な算術演算や操作をサポートしています。NumPyと似た感覚で扱えます。
-
算術演算
“`python
x = torch.ones(2, 2)
y = torch.ones(2, 2) * 2print(f”x + y: {x + y}”)
print(f”x – y: {x – y}”)
print(f”x * y: {x * y}”) # 要素ごとの掛け算
print(f”x / y: {x / y}”) # 要素ごとの割り算Alternative syntax
print(f”x.add(y): {x.add(y)}”)
print(f”x.sub(y): {x.sub(y)}”)
print(f”x.mul(y): {x.mul(y)}”)
print(f”x.div(y): {x.div(y)}”)行列の掛け算
matrix1 = torch.rand(3, 2)
matrix2 = torch.rand(2, 3)
matrix_mul = matrix1 @ matrix2 # または torch.matmul(matrix1, matrix2)
print(f”Matrix multiplication:\n{matrix_mul}”)
“` -
インデックスとスライス
NumPyと同様に、Pythonのインデックス記法を使ってTensorの要素にアクセスできます。
“`python
x = torch.rand(4, 4)
print(f”Original tensor:\n{x}”)最初の行
print(f”First row: {x[0]}”)
最後の列
print(f”Last column: {x[:, -1]}”)
0行目と1行目、1列目と2列目
print(f”Sub-tensor:\n{x[0:2, 1:3]}”)
単一要素 (スカラーTensorになる)
print(f”Single element: {x[1, 2]}”)
“` -
形状変更
Tensorの形状を変更できます。要素数は変更できません。
“`python
x = torch.rand(4, 4)
y = x.view(16) # 1次元のTensorに平坦化
z = x.view(2, 8) # 形状を2×8に変更
a = x.view(-1, 8) # -1を指定すると、その次元のサイズを自動的に計算
print(f”Original shape: {x.shape}”)
print(f”Flattened shape: {y.shape}”)
print(f”Reshaped shape: {z.shape}”)
print(f”Reshaped shape with -1: {a.shape}”)unsqueeze: 次元を追加
x = torch.tensor([1, 2, 3]) # 形状: [3]
y = x.unsqueeze(0) # 形状: [1, 3]
z = x.unsqueeze(1) # 形状: [3, 1]
print(f”Original shape: {x.shape}”)
print(f”Unsqueeze(0) shape: {y.shape}”)
print(f”Unsqueeze(1) shape: {z.shape}”)squeeze: サイズが1の次元を削除
x = torch.tensor([[1], [2], [3]]) # 形状: [3, 1]
y = x.squeeze() # 形状: [3]
print(f”Original shape: {x.shape}”)
print(f”Squeezed shape: {y.shape}”)
“` -
連結と分割
複数のTensorを結合したり、1つのTensorを分割したりできます。
“`python
x = torch.ones(2, 2)
y = torch.zeros(2, 2)連結 (次元を指定)
z_cat_dim0 = torch.cat([x, y], dim=0) # 縦方向に連結 (形状: 4×2)
z_cat_dim1 = torch.cat([x, y], dim=1) # 横方向に連結 (形状: 2×4)
print(f”Concatenated dim 0:\n{z_cat_dim0}”)
print(f”Concatenated dim 1:\n{z_cat_dim1}”)スタック (新しい次元を追加して連結)
z_stack = torch.stack([x, y], dim=0) # 形状: 2x2x2
print(f”Stacked dim 0:\n{z_stack}”)分割
x = torch.rand(4, 2)
split_tensors = torch.split(x, 2, dim=0) # dim=0方向にサイズ2で分割
print(f”Split tensors (list of tensors): {split_tensors}”)
“`
d) TensorとNumPyの変換
TensorはNumPy配列との相互変換が容易です。CPU上のTensorである必要があります。
“`python
a = torch.ones(5)
b = a.numpy() # TensorからNumPyへ
print(f”Tensor: {a}”)
print(f”NumPy array: {b}”)
a.add_(1) # Tensorの値を変更
print(f”Tensor after add_: {a}”)
print(f”NumPy array after add_: {b}”) # NumPy側も変更される (同じメモリを共有)
np_array = np.ones(5)
t = torch.from_numpy(np_array) # NumPyからTensorへ
print(f”NumPy array: {np_array}”)
print(f”Tensor: {t}”)
np.add(np_array, 1, out=np_array) # NumPyの値を変更
print(f”NumPy array after add: {np_array}”)
print(f”Tensor after NumPy add: {t}”) # Tensor側も変更される
“`
NumPyとPyTorch Tensorの間でメモリを共有していることに注意が必要です。
e) TensorのGPU利用
TensorをGPUに移動させると、GPUで高速に計算できます。to()
メソッドを使います。
“`python
前述のデバイス設定を使用
device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”)
print(f”Using device: {device}”)
CPU上のTensorを作成
x = torch.rand(5, 3)
print(f”Tensor on CPU: {x.device}”)
TensorをGPUに移動 (GPUが利用可能な場合)
if torch.cuda.is_available():
x_gpu = x.to(device)
print(f”Tensor on GPU: {x_gpu.device}”)
# GPU上のTensor同士で計算
y_gpu = torch.ones(5, 3, device=device) # 生成時にデバイスを指定
z_gpu = x_gpu + y_gpu
print(f"Result on GPU: {z_gpu.device}")
# 結果をCPUに戻す
z_cpu = z_gpu.to("cpu")
print(f"Result on CPU: {z_cpu.device}")
注意:NumPyに変換するにはCPU上のTensorである必要がある
numpy_array = z_gpu.numpy() # エラーになる
numpy_array = z_cpu.numpy() # OK
“`
GPUで計算を行いたい場合は、計算に関わる全てのTensorが同じGPUデバイス上にある必要があります。また、NumPy配列や標準的なPythonオブジェクトとの間でデータをやり取りする際は、一度CPUに戻す必要があります。
3.2 Autograd (自動微分)
ディープラーニングの学習では、モデルのパラメータに関する損失関数の勾配を計算し、その勾配を使ってパラメータを更新することでモデルを最適化します(勾配降下法)。この勾配計算を自動で行ってくれるのがPyTorchのAutograd機能です。
Autogradは、Tensorに対する全ての操作を記録し、入力から出力までの計算グラフを動的に構築します。そして、.backward()
メソッドを呼び出すことで、この計算グラフを逆方向に辿り、各パラメータの勾配を効率的に計算します。
a) 勾配計算の有効化
デフォルトでは、多くのTensorは勾配計算の対象になりません。勾配を計算したいTensor(主にモデルのパラメータ)に対しては、生成時または後から requires_grad=True
を設定する必要があります。
“`python
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True) # 生成時に有効化
print(x.requires_grad)
y = torch.tensor([4.0, 5.0, 6.0])
print(y.requires_grad) # デフォルトはFalse
後から有効化
y.requires_grad_(True) # アンダーバー(_)が付いているメソッドはインプレース操作
print(y.requires_grad)
勾配計算が不要なTensor (例: 入力データ、正解ラベル)
requires_grad=False (デフォルト)
data = torch.randn(64, 784)
labels = torch.randint(0, 10, (64,))
print(data.requires_grad)
print(labels.requires_grad)
“`
b) 計算グラフと順伝播
requires_grad=True
のTensorに対する操作は全て記録され、計算グラフが構築されます。
python
x = torch.tensor([1.0, 2.0], requires_grad=True)
y = x + 2 # y = [3.0, 4.0]
z = y * y * 3 # z = [27.0, 48.0]
out = z.mean() # out = (27.0 + 48.0) / 2 = 37.5
この計算に対応する計算グラフは以下のようになります(簡略化)。
x
-> + 2
-> y
-> * y
-> y^2
-> * 3
-> z
-> mean()
-> out
out
はスカラー値であり、requires_grad=True
のTensorから派生しているため、out.requires_grad
は True
になります。
c) 逆伝播と勾配計算
計算グラフの終端にあるスカラー値のTensorに対して .backward()
メソッドを呼び出すと、そのTensor(この例では out
)に関する、計算グラフ上の全ての requires_grad=True
のTensorの勾配が計算され、それぞれの .grad
属性に格納されます。
“`python
上記の計算に続けて実行
out.backward() # outに関するxの勾配を計算
xの勾配を表示 (dout/dx)
out = mean(3 * (x + 2)^2)
dout/dz = 1/2 * [1, 1]
dz/dy = 3 * 2 * y = 6y
dy/dx = 1
dout/dx = (dout/dz) * (dz/dy) * (dy/dx) = (1/2) * (6y) * 1 = 3y
y = x + 2 なので、dout/dx = 3 * (x + 2)
x = [1.0, 2.0] なので、y = [3.0, 4.0]
期待される勾配: [33.0, 34.0] = [9.0, 12.0]
print(f”Gradient of x: {x.grad}”)
実際に実行すると、Tensor([9., 12.]) が得られる
“`
.backward()
はスカラー値に対してのみ呼び出すことができます。もし非スカラー値のTensorに対して勾配計算を行いたい場合は、そのTensorと同じ形状の勾配テンソル(勾配の重み)を .backward()
に渡す必要がありますが、通常は損失関数のようなスカラー値に対して呼び出します。
d) 勾配のクリア
重要な点として、Autogradは勾配を累積します。つまり、複数回の .backward()
呼び出しで計算された勾配は、 .grad
属性に加算されていきます。ディープラーニングの学習では、通常、各ミニバッチの処理ごとに新しい勾配を計算し、パラメータを更新するため、次のバッチの計算を開始する前に前回の勾配をゼロにリセットする必要があります。
これはオプティマイザの .zero_grad()
メソッドで行います(後述)。手動で行う場合は、対象Tensorの .grad.zero_()
を呼び出します。
“`python
別の計算を行う場合
x = torch.tensor([1.0, 2.0], requires_grad=True)
y = x * x # y = [1.0, 4.0]
z = y.mean() # z = (1.0 + 4.0) / 2 = 2.5
z.backward()
print(f”Gradient of x (second calculation): {x.grad}”)
xのgradはリセットされていないので、前回の勾配に加算されてしまう!
これを避けるには、新しい計算の前に勾配をクリアする必要がある。
“`
e) 勾配計算の無効化
モデルの評価時や推論時など、パラメータを更新する必要がない場面では、勾配計算は不要であり、むしろ計算リソースの無駄になります。このような場合は、勾配計算を無効化できます。
“`python
x = torch.tensor([1.0, 2.0, 3.0], requires_grad=True)
with torch.no_grad() ブロック内では勾配計算が無効になる
with torch.no_grad():
y = x * 2
print(f”y: {y}”)
print(f”y.requires_grad: {y.requires_grad}”) # False になっている
デコレータとして使用することも可能
@torch.no_grad()
def dont_calculate_gradient(x):
return x + 10
z = dont_calculate_gradient(x)
print(f”z: {z}”)
print(f”z.requires_grad: {z.requires_grad}”) # False になっている
“`
モデルの評価時には with torch.no_grad():
ブロックを使うのが一般的です。
4. ニューラルネットワークの構築
PyTorchでは、ニューラルネットワークモデルを構築するために torch.nn
モジュールを提供しています。このモジュールには、様々な種類の層(Linear、Conv2dなど)や活性化関数、損失関数などが含まれています。
モデルは通常、nn.Module
クラスを継承して定義します。
4.1 nn.Moduleを継承したモデル定義
nn.Module
を継承するクラスを作成し、以下の2つのメソッドを実装するのが一般的な方法です。
__init__(self, ...)
: コンストラクタ。ここで、モデルで使用する各層や他のnn.Module
インスタンスを定義します。定義した層は、クラスの属性として保持します。forward(self, x)
: 順伝播の処理を記述します。入力テンソルx
を受け取り、定義した層を順番に適用していく計算処理を記述し、最終的な出力テンソルを返します。
例:簡単な線形モデル
入力1つ、出力1つの線形モデル $y = wx + b$ を考えます。
“`python
import torch
import torch.nn as nn
class SimpleLinearModel(nn.Module):
def init(self):
super(SimpleLinearModel, self).init() # 親クラスのコンストラクタを呼び出し
# nn.Linearは全結合層(線形変換)を定義
# 入力サイズ1、出力サイズ1
self.linear = nn.Linear(1, 1)
def forward(self, x):
# 入力xに対して定義したlinear層を適用する
return self.linear(x)
モデルのインスタンスを作成
model = SimpleLinearModel()
print(model) # モデルの構造が表示される
ダミーの入力データを作成
x = torch.tensor([[2.0]]) # 入力はTensorの形状に合わせる(通常は[バッチサイズ, 特徴量数])
モデルを実行(順伝播)
y = model(x) # forwardメソッドが呼び出される
print(f”Input x: {x}”)
print(f”Output y: {y}”)
モデルのパラメータ(重みwとバイアスb)を確認
for name, param in model.named_parameters():
print(f”Parameter Name: {name}, Parameter Value: {param.data}”)
print(f”Requires grad: {param.requires_grad}”) # パラメータはデフォルトでrequires_grad=True
“`
nn.Linear
は、入力テンソルに重み行列を掛け合わせ、バイアスベクトルを加える操作を行います。重みとバイアスは nn.Linear
が自動的に管理し、学習可能なパラメータとして登録されます (requires_grad=True
になります)。
例:簡単な多層パーセプトロン (MLP)
入力サイズ784(例:手書き数字画像のピクセル数)、隠れ層1つ(サイズ128)、出力層(サイズ10:クラス数)の多層パーセプトロンを定義します。隠れ層には活性化関数(ReLU)を使用します。
“`python
import torch.nn as nn
import torch.nn.functional as F # 活性化関数などは nn.functional にもよく含まれる
class SimpleMLP(nn.Module):
def init(self, input_size, hidden_size, num_classes):
super(SimpleMLP, self).init()
# 1つ目の全結合層: 入力 -> 隠れ層
self.fc1 = nn.Linear(input_size, hidden_size)
# 2つ目の全結合層: 隠れ層 -> 出力層
self.fc2 = nn.Linear(hidden_size, num_classes)
def forward(self, x):
# 入力xを平坦化(画像データを1次元ベクトルに変換する場合など)
# x = x.view(x.size(0), -1) # バッチサイズを維持して残りを平坦化
# 1つ目の層を通して活性化関数(ReLU)を適用
hidden = F.relu(self.fc1(x)) # nn.ReLU() というクラスを使う方法もある
# 2つ目の層を通して最終出力を得る
output = self.fc2(hidden)
return output
モデルのインスタンスを作成
input_size = 784
hidden_size = 128
num_classes = 10
model = SimpleMLP(input_size, hidden_size, num_classes)
print(model)
ダミーの入力データ (バッチサイズ64, 入力サイズ784)
x = torch.randn(64, input_size)
順伝播を実行
output = model(x)
print(f”Input shape: {x.shape}”)
print(f”Output shape: {output.shape}”) # [バッチサイズ, クラス数] となる
“`
F.relu()
は、要素ごとのReLU(Rectified Linear Unit)関数を適用します。活性化関数はニューラルネットワークに非線形性を導入するために不可欠です。
4.2 nn.Sequential
いくつかの層を順番に適用するだけの単純なモデルであれば、nn.Sequential
を使うとより簡潔に記述できます。
“`python
import torch.nn as nn
上記のSimpleMLPと同じ構成のモデルをnn.Sequentialで定義
model_seq = nn.Sequential(
nn.Linear(784, 128),
nn.ReLU(), # または nn.Sigmoid(), nn.Tanh() など
nn.Linear(128, 10)
)
print(model_seq)
ダミー入力で実行
x = torch.randn(64, 784)
output_seq = model_seq(x)
print(f”Output shape (Sequential): {output_seq.shape}”)
“`
nn.Sequential
は、定義した層をリストの順番に実行するコンテナです。SimpleMLPのように層の間に分岐や結合がない場合に便利です。
4.3 GPUでのモデル利用
モデルをGPUで計算させたい場合は、モデル全体をGPUデバイスに移動させます。
“`python
前述のデバイス設定を使用
device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”)
print(f”Using device: {device}”)
モデルのインスタンスを作成し、デバイスに移動
model = SimpleMLP(784, 128, 10).to(device)
print(f”Model on device: {next(model.parameters()).device}”) # パラメータのデバイスを確認
入力データも同じデバイスに移動させる必要がある
x = torch.randn(64, 784).to(device)
GPU上で順伝播を実行
output = model(x)
print(f”Input on device: {x.device}”)
print(f”Output on device: {output.device}”)
注意:モデルがGPUにある場合、入力データもGPUになければエラーになります。
x_cpu = torch.randn(64, 784)
output = model(x_cpu) # エラーになる
“`
model.to(device)
を実行すると、モデル内の全てのパラメータとバッファが指定したデバイスに移動します。入力データも同様に .to(device)
してからモデルに入力する必要があります。
5. 学習プロセス
ニューラルネットワークの学習は、以下のステップを繰り返すプロセスです。
- データの準備: 学習データと検証データを準備し、モデルに入力できる形式(Tensor)に変換します。ミニバッチに分割して効率的に処理できるようにします。
- 順伝播 (Forward Pass): 入力データをモデルに入力し、予測値を出力します。
- 損失計算: モデルの予測値と正解ラベルを比較し、どれだけ予測が間違っているか(損失)を計算します。
- 逆伝播 (Backward Pass): 損失関数が出力するスカラー値をもとに、Autogradを使ってモデルの各パラメータに関する勾配を計算します。
- パラメータ更新: 計算された勾配を使って、オプティマイザがモデルのパラメータ(重みやバイアス)をわずかに調整し、損失が減少する方向にモデルを改善します。
- 勾配のクリア: 次のミニバッチの処理のために、計算済みの勾配をゼロにリセットします。
このプロセスを、学習データセット全体を複数回(エポック数)繰り返します。
5.1 データセットとデータローダー
大量のデータを効率的に学習に使うために、PyTorchでは Dataset
と DataLoader
という抽象化を提供しています。
Dataset
: データのサンプルとそのラベルを保持し、個々のサンプルにアクセスするためのインターフェースを定義します。独自のデータセットを作成する場合は、torch.utils.data.Dataset
を継承し、__len__
(データセットのサイズを返す) と__getitem__
(指定されたインデックスのサンプルとラベルを返す) メソッドを実装します。DataLoader
:Dataset
からデータを取得し、ミニバッチにまとめて、シャッフルや並列処理(マルチプロセス)を行って効率的に供給するイテレータです。
PyTorchには、画像認識でよく使われるMNIST, CIFAR10などの有名なデータセットを簡単にダウンロード・利用できる torchvision.datasets
モジュールが含まれています。
“`python
import torch
from torch.utils.data import Dataset, DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
データをダウンロードし、Datasetを作成
training_data = datasets.FashionMNIST(
root=”data”, # データセットを保存するディレクトリ
train=True, # 訓練用データセット
download=True, # なければダウンロード
transform=ToTensor() # データをTensorに変換
)
test_data = datasets.FashionMNIST(
root=”data”,
train=False, # テスト用データセット
download=True,
transform=ToTensor()
)
データセットのサイズを確認
print(f”Number of training samples: {len(training_data)}”)
print(f”Number of test samples: {len(test_data)}”)
サンプルの形状とラベルを確認
img, label = training_data[0]
print(f”Image shape: {img.shape}”) # [チャンネル, 高さ, 幅] (FashionMNISTはグレースケールなのでチャンネルは1)
print(f”Label: {label}”)
DataLoaderを作成
batch_size = 64 # 1ミニバッチあたりのサンプル数
train_dataloader = DataLoader(training_data, batch_size=batch_size, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=batch_size, shuffle=False) # テストデータはシャッフル不要
DataLoaderの使い方(イテレート)
最初のバッチを取得
train_features, train_labels = next(iter(train_dataloader))
print(f”Feature batch shape: {train_features.shape}”) # [batch_size, チャンネル, 高さ, 幅]
print(f”Labels batch shape: {train_labels.shape}”) # [batch_size]
可視化 (オプション)
img = train_features[0].squeeze() # チャンネル次元を削除
label = train_labels[0]
plt.imshow(img, cmap=”gray”)
plt.title(f”Label: {label}”)
plt.show()
“`
DataLoader
は、Dataset
から指定された batch_size
でデータをまとめて取得し、必要に応じて shuffle
します。num_workers
引数を設定すると、データのロードを複数のプロセスで並列化して高速化できます(Windowsでは注意が必要な場合があります)。
5.2 損失関数 (Loss Function)
損失関数は、モデルの予測値と正解ラベルの間の誤差を数値化します。この誤差を最小化するように学習が進められます。使用する損失関数は、解きたい問題の種類によって異なります。
PyTorchの torch.nn
モジュールには、一般的な損失関数が多数用意されています。
- 回帰問題:
nn.MSELoss
: 平均二乗誤差 (Mean Squared Error)。
- 分類問題:
nn.CrossEntropyLoss
: 交差エントロピー損失。多クラス分類でよく使われます。モデルの出力がクラスごとのロジット(正規化されていないスコア)である場合に使用します。Softmax活性化関数を内部に含んでいます。nn.BCELoss
: 二値交差エントロピー損失 (Binary Cross Entropy Loss)。二値分類で使用します。モデルの出力がSigmoid活性化関数を通した0-1の確率である場合に使用します。nn.BCEWithLogitsLoss
: SigmoidとBCELossを組み合わせたもの。モデルの出力がSigmoidを適用する前のロジットである場合に使用します。数値的に安定しています。
損失関数の定義と使用例
“`python
import torch.nn as nn
例: 多クラス分類問題 (CrossEntropyLoss)
モデルの出力はクラスごとのロジット (形状: [バッチサイズ, クラス数])
正解ラベルはクラスのインデックス (形状: [バッチサイズ])
ダミーのモデル出力 (ロジット)
output = torch.randn(10, 5) # バッチサイズ10, クラス数5
print(f”Model output (logits) shape: {output.shape}”)
ダミーの正解ラベル (クラスのインデックス)
labels = torch.randint(0, 5, (10,)) # バッチサイズ10, 各要素は0から4のランダムな整数
print(f”True labels shape: {labels.shape}”)
損失関数を定義
criterion = nn.CrossEntropyLoss()
損失を計算
loss = criterion(output, labels)
print(f”Calculated loss: {loss.item()}”) # .item() でスカラー値を取得
例: 回帰問題 (MSELoss)
モデルの出力は予測値 (形状: [バッチサイズ, 出力サイズ])
正解ラベルは目標値 (形状: [バッチサイズ, 出力サイズ])
ダミーのモデル出力 (予測値)
prediction = torch.randn(10, 1) # バッチサイズ10, 出力サイズ1
ダミーの正解ラベル (目標値)
target = torch.randn(10, 1)
損失関数を定義
criterion_mse = nn.MSELoss()
損失を計算
loss_mse = criterion_mse(prediction, target)
print(f”Calculated MSE loss: {loss_mse.item()}”)
“`
損失関数は、学習可能なパラメータを持ちません。したがって、モデルのパラメータを損失関数の .to(device)
で同じデバイスに移動させる必要はありません。ただし、損失関数の入力となるモデルの出力と正解ラベルは、必ず同じデバイス上にある必要があります。
5.3 オプティマイザ (Optimizer)
オプティマイザは、計算された勾配を使ってモデルのパラメータを更新する役割を担います。様々な最適化アルゴリズムがあります。
PyTorchの torch.optim
モジュールには、一般的なオプティマイザが多数用意されています。
optim.SGD
: 確率的勾配降下法 (Stochastic Gradient Descent)。最も基本的なオプティマイザ。momentum
やweight_decay
(L2正則化) などの引数があります。optim.Adam
: Adam (Adaptive Moment Estimation)。SGDよりも一般的に収束が速く、初期設定でも比較的良い性能を出しやすいです。多くのタスクでデフォルトのオプティマイザとして使われます。optim.Adagrad
,optim.RMSprop
など。
オプティマイザを定義する際は、最適化対象となるモデルのパラメータ (model.parameters()
) と学習率 (lr
) を指定します。
オプティマイザの定義と使用例
“`python
import torch.optim as optim
例: 前述のSimpleMLPモデルを使用
model = SimpleMLP(…) # モデルは定義済みとする
オプティマイザを定義
モデルの全パラメータと学習率0.001を指定
optimizer = optim.Adam(model.parameters(), lr=0.001)
または SGD を使用する場合
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
“`
オプティマイザは、学習ループ内で以下の2つの主要なメソッドを使います。
optimizer.zero_grad()
: モデルの全パラメータの勾配をゼロにリセットします。これは各ミニバッチの処理の前に必ず行う必要があります。optimizer.step()
: 計算された勾配(.backward()
で.grad
属性に格納された値)を使って、パラメータを更新します。
5.4 学習ループの実装
いよいよ、これまでに学んだ要素を組み合わせて、実際の学習ループを実装します。学習ループは、通常、複数のエポック(データセット全体を何回繰り返すか)から構成され、各エポック内ではミニバッチごとに以下の処理を行います。
基本的な学習ループの構造
“`python
前提: model, criterion, optimizer, train_dataloader, test_dataloader, device が定義済み
num_epochs = 10 # データセット全体を繰り返す回数
for epoch in range(num_epochs):
# —– 訓練フェーズ —–
model.train() # モデルを訓練モードに設定 (DropoutやBatchNormなどが訓練時と評価時で挙動が変わるため)
running_loss = 0.0 # エポックごとの累積損失
for batch_idx, (inputs, labels) in enumerate(train_dataloader):
# データをデバイスに移動
inputs, labels = inputs.to(device), labels.to(device)
# 1. 勾配をゼロにリセット
optimizer.zero_grad()
# 2. 順伝播: 入力データをモデルに通し、予測値を得る
outputs = model(inputs)
# 3. 損失計算: 予測値と正解ラベルを使って損失を計算
loss = criterion(outputs, labels)
# 4. 逆伝播: 損失に基づいてモデルのパラメータの勾配を計算
loss.backward()
# 5. パラメータ更新: 計算された勾配を使ってパラメータを更新
optimizer.step()
# 損失を蓄積
running_loss += loss.item() * inputs.size(0) # バッチサイズを掛けて合計損失に加算
# 進捗表示 (任意)
if (batch_idx + 1) % 100 == 0: # 100バッチごとに表示
print(f"Epoch [{epoch+1}/{num_epochs}], Step [{batch_idx+1}/{len(train_dataloader)}], Loss: {loss.item():.4f}")
# エポックごとの平均損失を計算
epoch_loss = running_loss / len(training_data) # len(training_data) は Dataset のサイズ
print(f"Epoch [{epoch+1}/{num_epochs}] finished, Average Training Loss: {epoch_loss:.4f}")
# ----- 評価フェーズ (任意だが推奨) -----
model.eval() # モデルを評価モードに設定 (訓練モードとは挙動が変わる可能性がある)
# 評価中は勾配計算を無効化することでメモリ使用量と計算時間を節約
with torch.no_grad():
correct = 0
total = 0
test_loss = 0.0
for inputs, labels in test_dataloader:
# データをデバイスに移動
inputs, labels = inputs.to(device), labels.to(device)
# 順伝播 (勾配計算はしない)
outputs = model(inputs)
# 損失を計算 (評価時も損失を確認するため)
test_loss += criterion(outputs, labels).item() * inputs.size(0)
# 予測クラスを取得 (分類問題の場合)
_, predicted = torch.max(outputs.data, 1) # 各サンプルの最も確率が高いクラスのインデックスを取得
# 正解数のカウント
total += labels.size(0)
correct += (predicted == labels).sum().item() # (予測 == 正解) の要素数を合計
# エポックごとの平均テスト損失と精度を計算
average_test_loss = test_loss / len(test_data)
accuracy = 100 * correct / total
print(f"Epoch [{epoch+1}/{num_epochs}] finished, Average Test Loss: {average_test_loss:.4f}, Test Accuracy: {accuracy:.2f}%")
print(“Finished Training!”)
“`
この学習ループは、ディープラーニングモデル学習の基本的なテンプレートとなります。
model.train()
とmodel.eval()
の切り替えは重要です。Dropout層は訓練時のみ有効になり、BatchNorm層は訓練時と評価時で統計情報の計算方法が変わるため、これらのメソッドを適切に呼び出す必要があります。- 評価フェーズでは
with torch.no_grad():
を使用して、計算グラフの構築と勾配計算を無効化します。これにより、メモリ使用量を削減し、計算を高速化できます。 - 訓練中の損失は、
.item()
を使ってTensorからPythonのスカラー値に変換し、累積することが多いです。損失のTensor自体を直接累積すると、計算グラフが保持され続けてしまい、メモリを圧迫する可能性があるためです。
6. 実践例:FashionMNIST画像分類
前述の基礎知識を使って、実際にFashionMNISTデータセットを用いた画像分類モデルを構築・学習・評価してみましょう。FashionMNISTは、衣料品などの白黒画像(28×28ピクセル)と、それに対応する10クラスのラベルからなるデータセットで、MNISTよりも少し難しい問題です。
今回は、簡単な畳み込みニューラルネットワーク(CNN)を使ってみます。CNNは画像認識で非常に効果的です。
“`python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
0. 環境設定
device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”)
print(f”Using device: {device}”)
1. データセットとデータローダーの準備
print(“Loading data…”)
training_data = datasets.FashionMNIST(
root=”data”,
train=True,
download=True,
transform=ToTensor() # ToTensor() は画像を [0, 1] の範囲の float Tensor に変換し、形状を [C, H, W] に変更します
)
test_data = datasets.FashionMNIST(
root=”data”,
train=False,
download=True,
transform=ToTensor()
)
batch_size = 128 # バッチサイズを少し大きくする
train_dataloader = DataLoader(training_data, batch_size=batch_size, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=batch_size, shuffle=False)
print(f”Number of training batches: {len(train_dataloader)}”)
print(f”Number of test batches: {len(test_dataloader)}”)
2. モデルの定義 (簡単なCNN)
class FashionCNN(nn.Module):
def init(self):
super(FashionCNN, self).init()
self.conv_layers = nn.Sequential(
# 畳み込み層1: 入力チャンネル1 (グレースケール), 出力チャンネル32, カーネルサイズ3×3
nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1),
nn.ReLU(),
# プーリング層1: 最大プーリング 2×2
nn.MaxPool2d(kernel_size=2, stride=2),
# 畳み込み層2: 入力チャンネル32, 出力チャンネル64, カーネルサイズ3x3
nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1),
nn.ReLU(),
# プーリング層2: 最大プーリング 2x2
nn.MaxPool2d(kernel_size=2, stride=2)
)
# 最終的な全結合層への入力サイズを計算する必要がある
# 28x28画像 -> MaxPool(2) -> 14x14 -> MaxPool(2) -> 7x7
# 最後のConv層の出力チャンネルが64なので、平坦化後は 64 * 7 * 7 = 3136
self.fc_layers = nn.Sequential(
# 隠れ層
nn.Linear(in_features=64 * 7 * 7, out_features=128),
nn.ReLU(),
# 出力層 (クラス数10)
nn.Linear(in_features=128, out_features=10)
)
def forward(self, x):
# 畳み込み層とプーリング層を通過
x = self.conv_layers(x)
# 全結合層へ入力するために平坦化
x = x.view(x.size(0), -1) # バッチサイズを維持して残りを平坦化
# 全結合層を通過
x = self.fc_layers(x)
return x
model = FashionCNN().to(device) # モデルをデバイスに移動
print(“\nModel Architecture:”)
print(model)
3. 損失関数とオプティマイザの定義
criterion = nn.CrossEntropyLoss() # 多クラス分類なのでCrossEntropyLoss
optimizer = optim.Adam(model.parameters(), lr=0.001) # Adamオプティマイザ
4. 学習ループの実装
num_epochs = 5 # エポック数を少なめに設定 (実行時間を短縮)
print(“\nStarting training…”)
for epoch in range(num_epochs):
# — 訓練 —
model.train()
running_loss = 0.0
for batch_idx, (inputs, labels) in enumerate(train_dataloader):
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item() * inputs.size(0)
if (batch_idx + 1) % 100 == 0:
print(f"Epoch [{epoch+1}/{num_epochs}], Step [{batch_idx+1}/{len(train_dataloader)}], Train Loss: {loss.item():.4f}")
epoch_train_loss = running_loss / len(training_data)
print(f"Epoch [{epoch+1}/{num_epochs}] Training Loss: {epoch_train_loss:.4f}")
# --- 評価 ---
model.eval()
with torch.no_grad():
correct = 0
total = 0
test_loss = 0.0
for inputs, labels in test_dataloader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
test_loss += criterion(outputs, labels).item() * inputs.size(0)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
average_test_loss = test_loss / len(test_data)
accuracy = 100 * correct / total
print(f"Epoch [{epoch+1}/{num_epochs}] Test Loss: {average_test_loss:.4f}, Test Accuracy: {accuracy:.2f}%")
print(“Training finished!”)
5. 推論 (オプション)
訓練済みのモデルを使って新しい画像を分類
model.eval() # 評価モードに設定
with torch.no_grad():
# テストデータから最初の画像を1つ取得
sample_image, sample_label = test_data[0]
# モデルへの入力は通常バッチ形式なので、unsqueeze(0)でバッチ次元を追加
sample_input = sample_image.unsqueeze(0).to(device)
# モデルで予測
output = model(sample_input)
# 出力はクラスごとのロジットなので、Softmaxを適用して確率に変換 (必須ではないが分かりやすい)
probabilities = torch.softmax(output, dim=1)
# 最も確率が高いクラスを取得
_, predicted_class_index = torch.max(probabilities, 1)
predicted_class_index = predicted_class_index.item() # TensorからPython intへ
# FashionMNISTのクラス名を定義
class_names = [
'T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat',
'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot'
]
predicted_class_name = class_names[predicted_class_index]
true_class_name = class_names[sample_label]
print(f"\nSample image prediction:")
print(f"True Label: {true_class_name}")
print(f"Predicted Label: {predicted_class_name}")
print(f"Prediction Probabilities: {probabilities}")
# 画像を表示
img = sample_image.squeeze() # チャンネル次元を削除
plt.imshow(img, cmap="gray")
plt.title(f"True: {true_class_name}, Predicted: {predicted_class_name}")
plt.axis('off')
plt.show()
“`
このコードを実行すると、FashionMNISTデータセットのダウンロードから始まり、簡単なCNNモデルの構築、指定したエポック数だけ訓練と評価が行われ、最後に最初のテスト画像を使って推論を行う例が表示されます。
初めて実行する場合は、データセットのダウンロードに時間がかかることがあります。また、GPU環境であれば訓練は比較的速く終わりますが、CPUのみの場合は時間がかかる可能性があります(数分〜数十分)。
コード内のコメントを参考に、各ステップが何を行っているのか理解を深めてください。特に、nn.Conv2d
や nn.MaxPool2d
のパラメータ(in_channels
, out_channels
, kernel_size
, stride
, padding
など)がどのように画像の特徴量を抽出・圧縮していくのか、そして view
メソッドを使って多次元の畳み込み出力を1次元のベクトルに平坦化して全結合層に渡す部分などを確認しましょう。
7. さらに進むために
この記事で、PyTorchの基本的な要素(Tensor, Autograd, nn.Module)と、ディープラーニングの学習プロセス(データ準備、モデル定義、損失関数、オプティマイザ、学習ループ)を学びました。これらはPyTorchを使ったディープラーニングの基礎の基礎です。
さらにPyTorchとディープラーニングを深く学ぶためには、以下のトピックを探求することをおすすめします。
- 様々なネットワーク構造:
- CNN (Convolutional Neural Network): 画像認識で非常に強力です。畳み込み層、プーリング層、ドロップアウト層などの詳細や、代表的なアーキテクチャ(VGG, ResNet, Inceptionなど)を学びましょう。
- RNN (Recurrent Neural Network): 時系列データや自然言語処理に適しています。LSTM (Long Short-Term Memory) や GRU (Gated Recurrent Unit) といった派生形がよく使われます。
- Transformer: 自然言語処理を中心に、近年多くの分野で高い性能を発揮している新しいアーキテクチャです。Attentionメカニズムが核となります。
- データ処理と拡張:
torchvision.transforms
: 画像データの正規化、サイズ変更、切り抜き、反転といった様々な変換処理を提供します。- データ拡張 (Data Augmentation): 学習データを水増しし、モデルの汎化性能を高めるテクニックです。
- 高度な学習テクニック:
- 学習率スケジューリング (Learning Rate Scheduling): 学習の進行に合わせて学習率を調整するテクニック。
- 正則化 (Regularization): 過学習を防ぐためのテクニック(L1/L2正則化、Dropoutなど)。
- バッチ正規化 (Batch Normalization) や層正規化 (Layer Normalization): 学習の安定化と高速化に役立ちます。
- モデルの保存とロード: 訓練済みのモデルのパラメータを保存し、後で再利用したり、学習を途中から再開したりする方法を学びましょう (
torch.save
,torch.load
)。 - 事前学習済みモデルと転移学習: ImageNetのような大規模データセットで訓練されたモデルの重みを初期値として利用し、独自のデータセットでファインチューニング(転移学習)を行うことで、少ないデータでも高い性能を得られることがあります。
torchvision.models
には多くの有名な事前学習済みモデルが用意されています。 - 可視化ツール: TensorBoard (
torch.utils.tensorboard.SummaryWriter
) などを利用して、訓練中の損失や精度、勾配の分布などをグラフ化することで、学習プロセスを監視し、デバッグや改善に役立てることができます。 - クラウド環境の利用: Google Colaboratory や Kaggle Kernels のような無料のGPU環境を利用すると、手元のPCに強力なGPUがなくてもディープラーニングの実験や学習を行うことができます。
これらのトピックを学ぶことで、より複雑な問題を解けるようになり、ディープラーニングの応用範囲を広げることができます。
8. まとめ
この記事では、PyTorchを使ったディープラーニングの入門として、以下の内容を学びました。
- PyTorchのインストールと環境設定
- データの基本単位であるTensorの操作
- 自動微分機能Autogradの仕組みと使い方
nn.Module
を使ったニューラルネットワークモデルの定義方法Dataset
とDataLoader
を使ったデータの準備とミニバッチ処理- 損失関数とオプティマイザの選択と使用
- 訓練モードと評価モード、そして学習ループの実装
- 簡単なCNNモデルによるFashionMNIST画像分類の実践
PyTorchは、その柔軟性とPythonとの親和性から、ディープラーニングの研究開発において非常に強力なツールです。この記事で学んだ基礎を土台として、ぜひ様々なデータセットやモデルに挑戦してみてください。
ディープラーニングの世界は奥深く、学ぶことは尽きません。しかし、一歩ずつ着実に進んでいけば、必ずその面白さと強力さを実感できるはずです。この記事が、あなたのディープラーニング学習の素晴らしいスタートとなることを願っています。
Happy Coding with PyTorch!