このブログ記事では、ディープラーニングにおける転移学習について紹介します。

前回の記事では、畳み込み層を使用しても大規模なデータセットで大規模なモデルをトレーニングする際の難しさに直面しました。 この問題に対処するため、多くの研究者やエンジニアが解決策を考案し、時間とリソースを費やして高性能なモデルを開発してきました。 本記事では、これらの解決策を転移学習(Transfer Learning)を利用して、少ない労力で活用する方法を紹介します。
転移学習
転移学習は、広範で多様なデータセットでトレーニングされた事前学習済みのモデルを利用し、特定のタスクにおいてパフォーマンスを向上させる強力な機械学習技術として登場しました。 転移学習では、事前学習済みモデルの最終層を除去し、新しい分類器層に置き換えることで、初期の畳み込み層で抽出された重要な特徴を保持しつつ、 特定のタスクに合わせてモデルをトレーニングできます。以下では、コンピュータビジョンのタスクにおいて使用される1つのデータセットといくつかの著名な事前学習済みモデルを紹介します。
ImageNet
ImageNetは、1,000万枚以上の手作業で注釈された画像と、2万以上のカテゴリを含む巨大なデータベースです。ImageNetは、画像認識の分野でベンチマークデータセットとして広く利用されており、 現在利用可能な事前学習済みモデルの多くはこのデータセットでトレーニングされています。データセットの規模と多様性により、 ImageNetでトレーニングされたモデルは、あらゆる画像の分類において重要な特徴を学習していると考えられており、これを他の画像認識タスクに活用することができます。
VGG16
利用可能な事前学習済みモデルの1つにVGG16があります。以下に示すように、VGG16はシンプルなアーキテクチャを持っています。

VGG16のアーキテクチャ設計を見てみると、深さが増すにつれて空間的な解像度が徐々に減少し、チャンネル数が増加する傾向が見られます。 このアーキテクチャパターンは受容野(非常に重要!)の概念に基づいており、空間的解像度が小さくなることでカーネルが画像のより大きな部分を捉え、 高次の特徴を抽出できるようになります。
ResNet50
もう1つの著名な事前学習済みモデルとしてResNet50があります。以下にそのアーキテクチャを示します。

ResNet50は、残差接続(非常に重要!)を導入しており、これにより初期入力を忘れてしまう問題を軽減します。 特定の層の出力を入力に加えることで、モデルが特徴を記憶するのではなく、入力の調整に集中することを助け、 パフォーマンスを向上させます。また、多くの層を積み重ねても学習の難しさに直面しないようにします。 これは、勾配ブースティングを使った残差学習と似ています。
他にも、MobileNetV2のようなモデルがあり、これは比較的少ないパラメータ数を特徴とし、計算リソースが限られたモバイルアプリケーションに特に適しています。 したがって、自分のニーズに合った事前学習済みモデルを見つけるために、さらに調査を行うことをお勧めします。
ファインチューニング
通常、事前学習済みモデルは、学習済みの特徴抽出を保持するために、トレーニング時に学習不可能に設定されます。しかし、 最後の数層を学習可能にすることで、特定のタスクに合わせて特徴抽出を調整することができます。 この手法をファインチューニングと呼び、ファインチューニングを行うことでより良いパフォーマンスが期待できます。 しかし、ファインチューニングは学習可能な重みの数を増やし、トレーニングがより難しくなります(特徴抽出の調整にはより多くの時間とデータが必要です)が、 単純なタスクではわずかな性能向上しか見られないこともあります。
データ拡張
高性能で堅牢な機械学習モデルのトレーニングにおける最大のボトルネックの1つは、高品質なデータの不足です。 転移学習はこの問題を解決しますが、ファインチューニングを行わない限り、実用に足るパフォーマンスは期待できません。 しかし、ファインチューニングにはより多くのデータが必要です。この問題はデータ拡張によってある程度緩和されます。 データ拡張とは、既存のデータを回転、反転、再着色、ノイズ付加などで新しいデータに生成する手法です。データ拡張は、 モデルをより堅牢にし、ノイズが入りやすい実生活の入力にも対応できるようにするのに役立ちます。
コードの実装
これまでに、転移学習、ファインチューニング、データ拡張の概念について理解してきました。 次に、これらをTensorFlowとPyTorchで実装してみましょう。例として、ImageNetで事前学習されたResNet50を使用して、 猫と犬の分類器をトレーニングします。
ステップ1 & 2. データ探索と前処理
この例では、TensorFlow Coreの記事 Transfer learning and fine-tuning で提供されているデータセットを使用します。このデータセットには、猫と犬の画像が2000枚のトレーニングデータと1000枚のテストデータが含まれています。 ファイル構造は次のように整理されています:
cats_and_dogs_filtered/
├── train/
│ ├── cats/
│ └── dogs/
├── validation/
│ ├── cats/
│ └── dogs/
└── vectorize.py
上記のディレクトリから、TensorFlowとPyTorchでデータセットを作成できます。
_URL = 'https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip'
path_to_zip = tf.keras.utils.get_file('cats_and_dogs.zip', origin=_URL, extract=True)
PATH = os.path.join(os.path.dirname(path_to_zip), 'cats_and_dogs_filtered')
train_dir = os.path.join(PATH, 'train')
validation_dir = os.path.join(PATH, 'validation')
BATCH_SIZE = 32
IMG_SIZE = (224, 224)
# TensorFlow
train_dataset = tf.keras.utils.image_dataset_from_directory(train_dir,
shuffle=True,
batch_size=BATCH_SIZE,
image_size=IMG_SIZE)
validation_dataset = tf.keras.utils.image_dataset_from_directory(validation_dir,
shuffle=True,
batch_size=BATCH_SIZE,
image_size=IMG_SIZE)
val_batches = tf.data.experimental.cardinality(validation_dataset)
test_dataset = validation_dataset.take(val_batches // 5)
validation_dataset = validation_dataset.skip(val_batches // 5)
AUTOTUNE = tf.data.AUTOTUNE # Data Prefeching
train_dataset = train_dataset.prefetch(buffer_size=AUTOTUNE)
validation_dataset = validation_dataset.prefetch(buffer_size=AUTOTUNE)
test_dataset = test_dataset.prefetch(buffer_size=AUTOTUNE)
# PyTorch
train_dataset = datasets.ImageFolder(root=train_dir)
validation_dataset = datasets.ImageFolder(root=validation_dir)
validation_dataset, test_dataset = torch.utils.data.random_split(dataset, [0.8, 0.2])
これまでの例とは異なり、今回はトレーニング用にJPG画像を含むディレクトリを使用します。画像が2000枚しかないため、 過学習を防ぐためにデータ拡張を行います。(データ拡張は、PyTorchによるResNet50のものとほぼ同じです。)
# TensorFlow
data_augmentation = tf.keras.Sequential([
tf.keras.layers.RandomFlip('horizontal'),
tf.keras.layers.RandomRotation(0.2),
])
# PyTorch
train_transform = transforms.Compose([
transforms.Resize(size=IMG_SIZE),
transforms.RandomHorizontalFlip(),
transforms.RandomRotation((-72, 72)),
## Uncomment the below transformations when plotting the images
transforms.PILToTensor(),
transforms.v2.ToDtype(torch.float32, scale=True), # to float32 in [0, 1]
transforms.v2.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), # typically from ImageNet
])
test_transform = transforms.Compose([
transforms.Resize(size=IMG_SIZE),
transforms.PILToTensor(),
transforms.v2.ToDtype(torch.float32, scale=True), # to float32 in [0, 1]
transforms.v2.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), # typically from ImageNet
])
train_dataset = datasets.ImageFolder(root=train_dir, transform=train_trainsform)
validation_dataset = datasets.ImageFolder(root=validation_dir, transform=test_trainsform)
validation_dataset, test_dataset = torch.utils.data.random_split(validation_dataset, [0.8, 0.2])
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=BATCH_SIZE, shuffle=True)
val_loader = torch.utils.data.DataLoader(dataset=validation_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, shuffle=True)
TensorFlowでは、RandomFlipやRandomRotationなどのレイヤーは、データ拡張のために設定されており、 トレーニング可能な重みを持っていません。これらのレイヤーは、検証データセットやテストデータセットでは使用されません。 PyTorchも同様の機能を持っていますが、データ拡張はTensorFlowとは異なり、データセットを作成する際に実行されます。 PyTorchでは、リサイズや正規化などの前処理もすべてのデータセットに対して行う必要があります。 (他のライブラリを使用して、任意のタイミングでデータ拡張を行うことも可能です。)以下のコードを使用して、 両方のデータ拡張の結果を確認できます。
# TensorFlow
for image, _ in train_dataset.take(1):
plt.figure(figsize=(10, 4))
for i in range(9):
ax = plt.subplot(2, 5, i + 1)
augmented_image = data_augmentation(tf.expand_dims(image[i], 0))
plt.imshow(augmented_image[0] / 255)
plt.axis('off')
# PyTorch
plt.figure(figsize=(10, 4))
for i in range(9):
ax = plt.subplot(2, 5, i + 1)
plt.imshow(train_dataset[i][0])
plt.axis('off')
以下は、PyTorchの実装におけるデータ拡張の結果を示しており、データ拡張が期待どおりに適用されていることを確認できます。したがって、次にステップ3に進みます。

ステップ 3. モデル
以下は、TensorFlowおよびPyTorchで転移学習とファインチューニングを実装した例です。
もし実際に試している場合、finetune=False
を設定することを強くお勧めします。このタスクは非常にシンプルで、
猫や犬の画像はImageNetに含まれている可能性が高いため、特徴抽出のファインチューニングはほとんど価値がなく、
トレーニングを複雑にするだけです。上記の例では、技術的なデモンストレーションとしてファインチューニングが含まれていますが、
タスクが複雑で、事前学習済みモデルが見たことのない画像を使う場合にのみ役立ちます。
ステップ4: モデル評価に関しては、特に詳しく説明することがないため、本記事では省略しました。 実際に評価してみることをお勧めします。(ネタバレ: 上記のモデルは数エポックで急速に学習し、テスト画像をほぼ100%の精度で分類できるようになります。)
結論
他にも多くの事前学習済みモデルが利用可能なので、PyTorchの Models and pre-trained weights や、TensorFlowのModule: tf.keras.applications を確認して、他の事前学習済みモデルの使い方や利用可能なモデルのリストを参照することをお勧めします。
また、転移学習、ファインチューニング、データ拡張の概念は、他の機械学習タスクやデータにも適用され、 今日の機械学習においても非常に重要です。大規模言語モデル(LLM)は、特定のニーズに合わせて汎用的な言語モデルを調整するためにファインチューニングがよく行われる、 代表的な例です。大規模言語モデルの話も出てきたところで、次の記事から、自然言語処理について掘り下げていきたいと思います。
リソース
- TensorFlow. n.d. Transfer learning and fine-tuning. TensorFlow Core.