Zenn
🔰

子供でもわかる!蒸留・転移学習・ファインチューニングの違い

2025/02/09に公開
7

はじめに

アクセンチュア株式会社の桐山です。
最近話題の DeepSeek ですが、OpenAI のモデルを 蒸留 したのでは?と言われています。そこで、蒸留・転移学習・ファインチューニング の違いを改めて整理したいと思います。

今回は、子どもにも分かりやすい説明と、大人向けの説明の両方で整理してみます。(ChatGPT-4o の力を借りています)

蒸留・転移学習・ファインチューニングの違い(子供向けの説明)

1. 蒸留(じょうりゅう)

  • 先生(とっても頭のいいAI)が、生徒(ちょっとだけ頭のいいAI)に、できるだけわかりやすく教えてあげること。
  • 先生はたくさんの本を読んでいろんなことを知っているけど、生徒が全部覚えるのは大変だから、先生は「本当に大事なところ」だけをまとめて教えてくれる。
  • 例えば、お父さんやお母さんが難しいニュースを見た後に、「これはこういうことだよ」と分かりやすく説明してくれるのと同じ。
  • こうすることで、生徒は短い時間で大事な知識を学ぶことができるんだよ。

2. 転移学習(てんいがくしゅう)

  • すでに何かを学んだAIが、その知識を使って、新しいことを学ぶのを助ける方法。
  • 例えば、自転車に乗れるようになった子が、バランスの取り方を知っているから、スケートボードにもすぐ乗れるようになるみたいな感じ。
  • AIも同じで、「すでに勉強したこと」が「別のことを学ぶとき」に役に立つんだ。
  • いちから全部勉強するよりも、前の経験を使うことで、もっと早く、うまくできるようになるんだよ。

3. ファインチューニング

  • すでに学んだことを、「特別な目的」に合わせてちょっとずつ調整すること。
  • 例えば、ピアノを弾ける子が、新しい曲をもっと上手に弾けるように、一生懸命練習するのと似ている。
  • すでに「弾き方」は知っているけど、もっときれいな音を出したり、リズムをぴったり合わせたりするためには、新しい練習が必要だよね?
  • AIも、もともと持っている知識を「より特別な場面」で使えるように、ちょっとずつ調整するんだ。

まとめ表(子供向け)

名前 何をすること? 例え
蒸留 大きなAIが、小さなAIに大事な知識を分かりやすく教える お父さんが難しいニュースの内容を子どもに説明する
転移学習 すでに学んだことを使って、新しいことをすばやく学ぶ 野球をやっていた子が、テニスを始めると上達が早い
ファインチューニング すでに学んだことを、自分の目的に合わせて調整する ピアノをもっと上手に弾くために、細かく練習する

蒸留・転移学習・ファインチューニングの違い(大人向けの説明)

1. 蒸留(Knowledge Distillation)

  • 大規模な教師モデル(Teacher Model)の知識を、小規模な生徒モデル(Student Model)に圧縮して伝える手法。
  • 目的は、計算コストを下げつつ、精度をなるべく維持すること。
  • 蒸留の方法には、教師モデルの出力(ソフトターゲット)を用いる方法や、中間層の特徴量を利用する方法などがある。
  • 例えば、GPTのような大規模言語モデル(LLM)をそのまま使うと計算リソースが膨大にかかるため、蒸留を行い、小型モデルを作成することで、モバイル端末などでの運用が可能になる。
  • これにより、低コストかつ高速な推論を実現できる。
  • 以下、画像認識モデルのResNet18を蒸留する実装イメージ。
蒸留の実装イメージ
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import torchvision.models as models

# 1. データ準備 (CIFAR-10)
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)

testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=1000, shuffle=False)

# 2. Teacherモデル (ResNet18, 事前学習済み)
teacher_model = models.resnet18(pretrained=True)
teacher_model.fc = nn.Linear(512, 10)  # CIFAR-10のクラス数に変更
teacher_model.eval()  # 学習済みモデルとして使用

# 3. Studentモデル (ResNet8, 小型モデル)
class ResNet8(nn.Module):
    def __init__(self, num_classes=10):
        super(ResNet8, self).__init__()
        self.conv1 = nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1)
        self.bn1 = nn.BatchNorm2d(16)
        self.relu = nn.ReLU(inplace=True)
        self.fc = nn.Linear(16 * 32 * 32, num_classes)

    def forward(self, x):
        x = self.relu(self.bn1(self.conv1(x)))
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x

student_model = ResNet8()

# 4. 蒸留のための損失関数 (KLダイバージェンス + 通常のクロスエントロピー)
def distillation_loss(student_logits, teacher_logits, labels, T=3, alpha=0.5):
    """
    student_logits: 学習中の小型モデルの出力
    teacher_logits: 事前学習済みモデルの出力
    labels: 正解ラベル
    T: 温度パラメータ(高温ほどSoft化)
    alpha: KL損失と通常の損失の重み
    """
    soft_targets = nn.functional.log_softmax(student_logits / T, dim=1)
    soft_teacher = nn.functional.softmax(teacher_logits / T, dim=1)
    kl_loss = nn.functional.kl_div(soft_targets, soft_teacher, reduction="batchmean") * (T * T)

    ce_loss = nn.functional.cross_entropy(student_logits, labels)

    return alpha * kl_loss + (1 - alpha) * ce_loss

# 5. 学習ループ
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
teacher_model.to(device)
student_model.to(device)

optimizer = optim.Adam(student_model.parameters(), lr=0.001)

epochs = 5
for epoch in range(epochs):
    student_model.train()
    running_loss = 0.0
    for inputs, labels in trainloader:
        inputs, labels = inputs.to(device), labels.to(device)

        with torch.no_grad():
            teacher_outputs = teacher_model(inputs)

        student_outputs = student_model(inputs)
        loss = distillation_loss(student_outputs, teacher_outputs, labels)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        running_loss += loss.item()

    print(f"Epoch {epoch+1}/{epochs}, Loss: {running_loss/len(trainloader)}")

print("蒸留学習完了!")

2. 転移学習(Transfer Learning)

  • あるタスクを学習済みのモデルを、新しいタスクに適用する手法。
  • 特にディープラーニングの分野では、事前学習済みのモデル(Pretrained Model)の特徴を活かし、新たなデータセットに適応させる際に広く利用される。
  • 例えば、ImageNetで学習したCNNモデルを医療画像解析や農作物の分類タスクに応用するケースが挙げられる。
  • 一般的に、大規模データセットで学習済みのモデルは、低レベルの特徴(エッジや色などの基本的なパターン)をうまく捉えているため、新しいタスクでも効果的に機能する。
  • これにより、少量のデータでも高い精度を実現でき、計算資源や学習時間の削減が可能となる。
  • 以下、画像認識モデルのResNet18を転移学習する実装イメージ。
転移学習の実装イメージ
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import torchvision.models as models

# 1. データ準備 (CIFAR-10)
transform = transforms.Compose([
    transforms.Resize((224, 224)),  # ResNetの入力サイズに合わせる
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)

testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=1000, shuffle=False)

# 2. 事前学習済みResNet18をロード
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

model = models.resnet18(pretrained=True)  # 事前学習済みモデル
num_features = model.fc.in_features  # もともとの出力層の入力次元数
model.fc = nn.Linear(num_features, 10)  # CIFAR-10に合わせて出力層を変更
model = model.to(device)

# 3. 転移学習(Feature Extraction) → 畳み込み層を凍結
for param in model.parameters():
    param.requires_grad = False  # すべてのパラメータを凍結

# ただし、最終層のみ学習させる
for param in model.fc.parameters():
    param.requires_grad = True

# 4. 学習設定
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.fc.parameters(), lr=0.001)

# 5. 学習ループ
epochs = 5
for epoch in range(epochs):
    model.train()
    running_loss = 0.0
    for inputs, labels in trainloader:
        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()

    print(f"Epoch {epoch+1}/{epochs}, Loss: {running_loss/len(trainloader)}")

print("転移学習完了!")

3. ファインチューニング(Fine-Tuning)

  • 転移学習の一種で、事前学習済みのモデルを特定のデータセットや用途に適応させるプロセス。
  • 転移学習では、事前学習済みモデルの一部を固定して使用することもあるが、ファインチューニングではモデル全体または特定の層のパラメータを更新し、より最適化を行う。
  • 例えば、BERTのような事前学習済みの自然言語処理モデルを、特定の用途(法律文書、医療文献、カスタマーサポートなど)に適応させる際に、追加の学習を行う。
  • ファインチューニングを適切に行うことで、特定領域での性能向上が可能となるが、過学習を防ぐための適切な正則化手法(ドロップアウト、Early Stoppingなど)が重要となる。
  • 以下、画像認識モデルのResNet18をファインチューニングする実装イメージ。
ファインチューニングの実装イメージ
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import torchvision.models as models

# 1. データ準備 (CIFAR-10)
transform = transforms.Compose([
    transforms.Resize((224, 224)),  # ResNetの入力サイズに合わせる
    transforms.RandomHorizontalFlip(),  # 左右反転
    transforms.RandomRotation(10),  # 10度回転
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)

testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=1000, shuffle=False)

# 2. 事前学習済みResNet18をロード
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

model = models.resnet18(pretrained=True)  # 事前学習済みモデル
num_features = model.fc.in_features  # もともとの出力層の入力次元数
model.fc = nn.Linear(num_features, 10)  # CIFAR-10に合わせて出力層を変更
model = model.to(device)

# 3. Fine-Tuning: 畳み込み層の一部を学習可能にする
for name, param in model.named_parameters():
    if "layer4" in name or "fc" in name:  # ResNetの最後のブロック + 出力層のみ学習
        param.requires_grad = True
    else:
        param.requires_grad = False  # それ以外は凍結

# 4. 学習設定
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=0.001)

# 5. 学習ループ
epochs = 5
for epoch in range(epochs):
    model.train()
    running_loss = 0.0
    for inputs, labels in trainloader:
        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()

    print(f"Epoch {epoch+1}/{epochs}, Loss: {running_loss/len(trainloader)}")

print("ファインチューニング完了!")

まとめ表(大人向け)

名前 定義 目的・特徴
蒸留 大規模モデルの知識を小規模モデルに圧縮 計算コスト削減・推論速度向上 GPTの蒸留版を作成し、モバイル端末向けに最適化
転移学習 既存の学習済みモデルを新タスクに適用 少量データで高精度な学習を実現 ImageNetのCNNを医療画像解析に転用
ファインチューニング 転移学習モデルを特定用途に最適化 モデルの一部または全体のパラメータを調整 BERTを法律文書解析用にカスタマイズ

おわりに

今回は、蒸留・転移学習・ファインチューニングの違いを、子ども向けと大人向けの両方の視点から整理してみました。

AIの学習手法にはさまざまなアプローチがあり、それぞれ目的や適用範囲が異なります。蒸留はモデルの軽量化、転移学習は既存知識の再利用、ファインチューニングは特定用途への最適化と、それぞれの特性を理解して適切に活用することが重要と思います。

DeepSeekに関する議論をきっかけに、今後もAI技術の進化を追いながら、実践的な活用方法を探っていきたいと考えます。

7
Accenture Japan (有志)

Discussion

ログインするとコメントできます