😄

1日100枚増える嫁との写真を自動仕分け!画像認識モデルを自作し超効率化した話。

に公開

はじめに

大量の写真を手動で仕分ける作業を自動化するため、PyTorchとResNet50を使った画像分類モデルを自作し、我が家で実運用を開始しました。本記事では、自作したモデルの結果をお伝えします。

やりたいこと

嫁と私のツーショットの写真と、ソロショットの写真のフォルダを仕分けしたい。
驚きなことに1日で100枚くらい写真が増えるほど。。なので手作業では到底追いつかないということです、しかも1年間分以上ある!(笑)
書いててアホなタイトルだなと思いましたが、本人はとても真面目に作業をしています。

なぜ自作したのか

  • ローカルPCで動かせる軽量モデルだと配布できるし助かる
  • VLMは大きすぎてローカル実行が難しいのと20GB程度のモデルではF1スコアが0.7くらいだった
  • シンプルな二値分類で十分というユースケースのため大きなモデルは不要
  • カスタマイズしやすいし、何よりも自作するのは面白い

感化された記事

https://zenn.dev/toki_mwc/articles/a7a76e05d143d9
→とても面白い!!!もちろんVLMも検討したが。。、我が家は計算リソースが乏しく。。苦笑

実装の概要

  • フレームワーク: PyTorch
  • 学習環境はGoogleColab T4 GPU
  • モデル: ResNet50(ImageNet事前学習済み)
  • 手法: フルファインチューニング(最初は転移学習を試したが精度があと一歩であった)
  • タスク: ソロショット vs ツーショットの二値分類

モデルアーキテクチャ

ResNet50をベースに、ImageNet用の分類層を2クラス分類用に置き換えました。

  • パラメータ数: 約25.6M(すべて訓練可能)
  • モデルサイズ: 約91MB
    →魅力ポイントは、**VLMと比較し非常に小さいモデル(20BのVLMと比較すると約781倍小さい)**なので、CPUでも動かせるという点です。

データセット

人間のアノテーション(笑)でデータセットを作ります。これ以上は無理。。なのでデータ拡張に頼ります。

  • 総画像数: 86枚
    • ソロショット: 42枚
    • ツーショット: 44枚
  • 分割: 訓練データ 68枚(80%)、検証データ 18枚(20%)

試行錯誤の過程

最初のアプローチ: 転移学習

最初は転移学習(Transfer Learning)を試しました。転移学習は、事前学習済みモデルの重みを凍結し、最後の層のみを訓練する手法です。訓練時間が短く、メモリ使用量も少ないというメリットがあります。

# 転移学習版のコード
model = models.resnet50(weights=ResNet50_Weights.IMAGENET1K_V2)

# ベースモデルの重みを凍結(転移学習のため、ベースモデルは訓練しない)
for param in model.parameters():
    param.requires_grad = False

# 最後の全結合層を置き換え
num_features = model.fc.in_features
model.fc = nn.Sequential(
    nn.Linear(num_features, 128),
    nn.ReLU(),
    nn.Dropout(0.7),
    nn.Linear(128, 2)
)

# 学習率は5e-4を使用
optimizer = optim.Adam(model.parameters(), lr=5e-4)

結果: 転移学習では精度が十分に上がりませんでした。といっても検証データでの精度は約89%程度で、この時点で20B程度のVLMのF1スコアは超えている(笑)&十分なのですが、エンジニア的にもっと上を。。目指したくなりました。

改善策: フルファインチューニングへ

精度向上のため、フルファインチューニングに切り替えました。すべての層のパラメータを訓練可能にすることで、タスクに特化したモデルを構築できます。その反面、転移学習と比較すると、訓練時のメモリ消費量が大きくなります。ただし、LLMのチューニングと比較すれば圧倒的に小さいモデルなので今回のユースケースでは問題になりません。

実装の詳細

1. データ準備

# カスタムデータセットクラス
class PhotoDataset(Dataset):
    def __init__(self, image_paths, labels, transform=None):
        self.image_paths = image_paths
        self.labels = labels
        self.transform = transform

    def __getitem__(self, idx):
        img = Image.open(self.image_paths[idx])
        if img.mode != 'RGB':
            img = img.convert('RGB')
        if self.transform:
            img = self.transform(img)
        return img, self.labels[idx]

2. データ拡張

訓練時の拡張:

  • リサイズ(224×224)
  • ランダム回転(±20度)
  • ランダム水平反転(p=0.5)
  • 色調整(明度、コントラスト、彩度、色相)
  • アフィン変換(平行移動、シアー)
    もちろんですが、人間の写真なので上下反転は使用していません。

3. モデル構築

# ResNet50をベースに使用
model = models.resnet50(weights=ResNet50_Weights.IMAGENET1K_V2)

# フルファインチューニングのため、すべてのパラメータを訓練可能にする
# (重みの凍結は行わない)

# 最後の全結合層を置き換え
num_features = model.fc.in_features
model.fc = nn.Sequential(
    nn.Linear(num_features, 128),
    nn.ReLU(),
    nn.Dropout(0.7),
    nn.Linear(128, 2)  # ソロショット(0)とツーショット(1)
)

4. 訓練設定

# 損失関数とオプティマイザー
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=1e-4)  # フルファインチューニング用の低い学習率

# 学習率スケジューラー
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
    optimizer, mode='min', factor=0.5, patience=5, min_lr=1e-7
)

全層更新するため、転移学習よりも低い学習率で学習を実施しています。

結果

学習曲線


→プライベートでのデータセットのなかでは過去一よい下がり方!

転移学習 vs フルファインチューニングの比較

手法 精度 訓練時間 メモリ使用量 備考
転移学習 約89% 短い 少ない あと一歩物足りない
フルファインチューニング 100% 長い 多い 精度UPを達成

フルファインチューニングに切り替えることで、検証データでの精度が89%から100%に向上しました。

分類レポート(フルファインチューニング版)

              precision    recall  f1-score  

   solo shot       1.00      1.00      1.00        
    two shot       1.00      1.00      1.00         

    accuracy                           1.00        

検証データで精度100%を達成。

👇このような形で推論されます。嫁に怒られてしまうのでモザイクをかけてあります。

モデルサイズ

  • モデルサイズ: 約91MB
  • ローカル実行に十分な軽量さ
    →非常に軽量ですが、非常に高精度なモデルが出来上がりました。

転移学習からフルファインチューニングへの学び

フルファインチューニングのメリット

  1. 高い精度: 全パラメータを最適化できるため、精度向上が期待できる(実際に89%→100%に向上)
  2. タスク特化: データに合わせて全体を調整可能
  3. 学習手法として簡単か?: 転移学習やファインチューニングよりもタスク特化させる場合は経験的に簡単な気がします

今後の改善案

  1. データ拡張の強化: より多様な拡張を追加
  2. アンサンブル: 複数モデルの組み合わせ
  3. 分類ラベルの追加:嫁、私、ツーショットの3分類モデルを作る

まとめ

PyTorchとResNet50を使った画像分類モデルの実装を通じて、転移学習とフルファインチューニングの違いを実体験しました。

試行錯誤の結果:

  1. 最初は転移学習を試したが、精度が89%程度で個人的に物足りなかった(※転移学習ではこれ以上性能が上がる気がしなかった)
  2. フルファインチューニングに切り替えることで、精度向上!

得られた知見:

  • データセットの特性や要求精度に応じて、転移学習とフルファインチューニングを使い分けることが重要かもしれない
  • 最初から完璧を目指すのではなく、段階的に改善していくアプローチが有効と感じる
  • VLMのような大規模モデルが不要な場合、シンプルなCNNでも十分に実用的であることも再認識させられた(次はViTでのチューニングを行ってみたい)

ローカルで動かせる軽量モデルは、プライバシーやコストの面でもメリットがあります。今回の経験から、CNN系のモデルも十分有効であることが分かりました。

そのほか

最近時様々なAI活用のご相談を受けています。
もしご興味ある方は、go.yusng@gmail.comへご連絡ください!

参考資料

Discussion