以下の内容はhttps://touch-sp.hatenablog.com/entry/2025/07/03/200804より取得しました。


【画像分類】AutoGluon の MultiModalPredictor でキノコの分類をやってみる

はじめに

以前「犬と猫の画像分類」を行いました。
久しぶりに画像分類が必要になったので「キノコの分類」を行ってみました。

データの準備

こちらからデータをダウンロードさせて頂きました。

今回は「Boletus edulis」と「それ以外」の2クラス分類としました。

使用する画像をランダムに選択

すべての画像から2000枚(Boletus edulis 1000枚、それ以外 1000枚)を抽出しました。

import os
import random
import shutil
from pathlib import Path

def get_image_files(folder_path):
    """指定フォルダから画像ファイルのパスを取得"""
    image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.webp'}
    image_files = []
    
    for file in os.listdir(folder_path):
        if Path(file).suffix.lower() in image_extensions:
            image_files.append(os.path.join(folder_path, file))
    
    return image_files

def extract_images_from_folders(base_path, target_folder_name, output_path, num_images=200):
    """
    フォルダから画像をランダム抽出
    
    Args:
        base_path: 全フォルダが入っている親ディレクトリのパス
        target_folder_name: 特定フォルダの名前(指定枚数抽出対象)
        output_path: 抽出した画像の保存先パス
        num_images: 各グループから抽出する画像数(デフォルト200枚)
    """
    
    # 出力フォルダを作成
    os.makedirs(output_path, exist_ok=True)
    target_output = os.path.join(output_path, "targets")
    other_output = os.path.join(output_path, "others")
    os.makedirs(target_output, exist_ok=True)
    os.makedirs(other_output, exist_ok=True)
    
    # 全フォルダのリストを取得
    all_folders = [f for f in os.listdir(base_path) 
                   if os.path.isdir(os.path.join(base_path, f))]
    
    print(f"発見されたフォルダ数: {len(all_folders)}")
    print(f"フォルダ一覧: {all_folders}")
    
    # 特定フォルダの処理
    target_folder_path = os.path.join(base_path, target_folder_name)
    
    if not os.path.exists(target_folder_path):
        print(f"エラー: 特定フォルダ '{target_folder_name}' が見つかりません")
        return
    
    # 特定フォルダから画像を取得
    target_images = get_image_files(target_folder_path)
    print(f"特定フォルダ '{target_folder_name}' の画像数: {len(target_images)}")
    
    # 特定フォルダから指定枚数をランダム抽出
    if len(target_images) >= num_images:
        selected_target_images = random.sample(target_images, num_images)
    else:
        selected_target_images = target_images
        print(f"警告: 特定フォルダの画像数が{num_images}枚未満です ({len(target_images)}枚)")
    
    # 特定フォルダの画像をコピー
    for i, img_path in enumerate(selected_target_images):
        filename = f"target_{i+1:03d}_{os.path.basename(img_path)}"
        shutil.copy2(img_path, os.path.join(target_output, filename))
    
    print(f"特定フォルダから {len(selected_target_images)} 枚の画像を抽出完了")
    
    # その他フォルダの処理
    other_folders = [f for f in all_folders if f != target_folder_name]
    all_other_images = []
    
    for folder in other_folders:
        folder_path = os.path.join(base_path, folder)
        folder_images = get_image_files(folder_path)
        all_other_images.extend(folder_images)
        print(f"フォルダ '{folder}': {len(folder_images)} 枚")
    
    print(f"その他フォルダの総画像数: {len(all_other_images)}")
    
    # その他フォルダから指定枚数をランダム抽出
    if len(all_other_images) >= num_images:
        selected_other_images = random.sample(all_other_images, num_images)
    else:
        selected_other_images = all_other_images
        print(f"警告: その他フォルダの画像数が{num_images}枚未満です ({len(all_other_images)}枚)")
    
    # その他フォルダの画像をコピー
    for i, img_path in enumerate(selected_other_images):
        folder_name = os.path.basename(os.path.dirname(img_path))
        filename = f"other_{i+1:03d}_{folder_name}_{os.path.basename(img_path)}"
        shutil.copy2(img_path, os.path.join(other_output, filename))
    
    print(f"その他フォルダから {len(selected_other_images)} 枚の画像を抽出完了")
    print(f"抽出完了!出力先: {output_path}")

# 使用例
if __name__ == "__main__":
    # 設定を変更してください
    BASE_PATH = "D:/classification/merged_dataset"  # 20個のフォルダがある親ディレクトリ
    TARGET_FOLDER = "Boletus edulis"                # 指定枚数抽出したい特定フォルダ名
    OUTPUT_PATH = "D:/classification/output"        # 抽出した画像の保存先
    NUM_IMAGES = 1000                               # 各グループから抽出する画像数
    
    # 実行
    extract_images_from_folders(BASE_PATH, TARGET_FOLDER, OUTPUT_PATH, NUM_IMAGES)

学習データとテストデータに分割

8 : 2 で分割しました。

import glob
import random
import pandas as pd
import os

targets_files = glob.glob("output/targets/*")
others_files = glob.glob("output/others/*")

targets_train = random.sample(targets_files, 500)
others_train = random.sample(others_files, 500)

targets_test = list(set(targets_files) - set(targets_train))
others_test = list(set(others_files) - set(others_train))

train_dataset_list = []
for image_path in others_train:
    train_dataset_list.append({
        'image': os.path.abspath(image_path),  # 絶対パスに変換
        'label': 0
        })
for image_path in targets_train:
    train_dataset_list.append({
        'image': os.path.abspath(image_path),  # 絶対パスに変換
        'label': 1
        })
train_df = pd.DataFrame(train_dataset_list)
train_df.to_pickle('train_df.pkl')


test_dataset_list = []
for image_path in others_test:
    test_dataset_list.append({
        'image': os.path.abspath(image_path),  # 絶対パスに変換
        'label': 0
        })
for image_path in targets_test:
    test_dataset_list.append({
        'image': os.path.abspath(image_path),  # 絶対パスに変換
        'label': 1
        })
test_df = pd.DataFrame(test_dataset_list)
test_df.to_pickle('test_df.pkl')

このようなPandasデータフレームをpklで保存しています。

                                                  image  label
0     /home/hoge/works/output/others/other_952_Fomit...      0
1     /home/hoge/works/output/others/other_490_Sarco...      0
2     /home/hoge/works/output/others/other_550_Caloc...      0
3     /home/hoge/works/output/others/other_471_Physc...      0
4     /home/hoge/works/output/others/other_336_Fomes...      0
...                                                 ...    ...
1595  /home/hoge/works/output/targets/target_213_699...      1
1596  /home/hoge/works/output/targets/target_615_647...      1
1597  /home/hoge/works/output/targets/target_941_703...      1
1598  /home/hoge/works/output/targets/target_033_652...      1
1599  /home/hoge/works/output/targets/target_995_750...      1

[1600 rows x 2 columns]

学習

「AutoGluon」の名前の通りすべてが「Auto」です。
ハイパーパラメーターをまったく指定しないで実行しました。
ここでいうハイパーパラメーターとは使用するモデル、バッチ数、エポック数、学習率などです。

Windowsで実行する時には「freeze_support()」が必要でした。

import warnings
warnings.filterwarnings('ignore')

import pandas as pd
from autogluon.multimodal import MultiModalPredictor
from multiprocessing import freeze_support

def main():

    train_df = pd.read_pickle("train_df.pkl")

    predictor = MultiModalPredictor(
        problem_type="binary",
        label="label"
    )

    predictor.fit(train_data = train_df)

if __name__ == "__main__":
    freeze_support()  # Windows用
    main()

テストデータを用いた検証

import warnings
warnings.filterwarnings("ignore")

import pandas as pd
from autogluon.multimodal import MultiModalPredictor
from multiprocessing import freeze_support

def main():

    test_df = pd.read_pickle("test_df.pkl")

    predictor = MultiModalPredictor.load("AutogluonModels/ag-20250703_105206")

    score = predictor.evaluate(test_df, metrics=["accuracy"])
    print(score)

if __name__ == "__main__":
    freeze_support()  # Windows用
    main()

出力

Load pretrained checkpoint: D:\autogluon\AutogluonModels\ag-20250703_105206\model.ckpt
💡 Tip: For seamless cloud uploads and versioning, try installing [litmodels](https://pypi.org/project/litmodels/) to enable LitModelCheckpoint, which syncs automatically with the Lightning model registry.
You are using a CUDA device ('NVIDIA GeForce RTX 4090') that has Tensor Cores. To properly utilize them, you should set `torch.set_float32_matmul_precision('medium' | 'high')` which will trade-off precision for performance. For more details, read https://pytorch.org/docs/stable/generated/torch.set_float32_matmul_precision.html#torch.set_float32_matmul_precision
Predicting DataLoader 0: 100%|█████████████████████████████████████████████████████████| 13/13 [00:01<00:00,  8.88it/s]
{'accuracy': 0.965}

詳細な出力のためのスクリプト

import warnings
warnings.filterwarnings("ignore")

import pandas as pd
from autogluon.multimodal import MultiModalPredictor
import os
import yaml

def extract_values_from_yaml(yaml_file_path):
    """
    YAMLファイルから指定された値を抽出する関数
    
    Args:
        yaml_file_path (str): YAMLファイルのパス
        
    Returns:
        dict: 抽出された値の辞書
    """
    try:
        with open(yaml_file_path, 'r', encoding='utf-8') as file:
            data = yaml.safe_load(file)
        
        # 抽出したい値を取得
        extracted_values = {
            'checkpoint_name': data.get('model', {}).get('timm_image', {}).get('checkpoint_name'),
            'optim_type': data.get('optim', {}).get('optim_type'),
            'lr': data.get('optim', {}).get('lr'),
            'batch_size': data.get('env', {}).get('batch_size'),
            'per_gpu_batch_size': data.get('env', {}).get('per_gpu_batch_size')
        }
        
        return extracted_values
    
    except FileNotFoundError:
        print(f"エラー: ファイル '{yaml_file_path}' が見つかりません。")
        return None
    except yaml.YAMLError as e:
        print(f"YAML解析エラー: {e}")
        return None
    except Exception as e:
        print(f"予期しないエラー: {e}")
        return None
    
def main(foldername: str):

    test_df = pd.read_pickle("image_dataset_test.pkl")

    predictor = MultiModalPredictor.load(foldername)
    total_parameters = predictor.total_parameters
    trainable_parameters = predictor.trainable_parameters

    score = predictor.evaluate(test_df, metrics=["accuracy"])

    yaml_path = os.path.join(foldername, "config.yaml")
    result = extract_values_from_yaml(yaml_path)

    print(f"{foldername}: {score}")
    if result:
        print(f"checkpoint_name: {result['checkpoint_name']}")
        print(f"total_parameters: {int(total_parameters/1000000)}MB")
        print(f"trainable_parameters: {int(trainable_parameters/1000000)}MB")
        print(f"optim_type: {result['optim_type']}")
        print(f"lr: {result['lr']}")
        print(f"batch_size: {result['batch_size']}")
        print(f"per_gpu_batch_size: {result['per_gpu_batch_size']}")
    else:
        print("値の抽出に失敗しました。")

if __name__ == "__main__":
    main("simple_best_quality")

出力

(下の例は今回の結果ではありません)

simple_best_quality: {'accuracy': 0.9625}
checkpoint_name: swin_large_patch4_window7_224
total_parameters: 195MB
trainable_parameters: 195MB
optim_type: adamw
lr: 0.0001
batch_size: 128
per_gpu_batch_size: 1

補足

保存先を指定したり、求めるクオリティを変更したりする方法は以下の通りです。

import warnings
warnings.filterwarnings('ignore')

import pandas as pd
from autogluon.multimodal import MultiModalPredictor
from multiprocessing import freeze_support

def main():

    train_df = pd.read_pickle("new_train_df.pkl")

    predictor = MultiModalPredictor(
        problem_type="binary",
        label="label"
    )

    predictor.fit(
        train_data = train_df,
        presets="high_quality",
        save_path="high_quality"
    )

if __name__ == "__main__":
    freeze_support()  # Windows用
    main()

環境

Windows 11
Python 3.12
CUDA 12.6
pip install torch==2.6.0+cu126 --index-url https://download.pytorch.org/whl/cu126
pip install autogluon==1.3.1
pip install pynvml

MultiModalPredictor関連記事

touch-sp.hatenablog.com





以上の内容はhttps://touch-sp.hatenablog.com/entry/2025/07/03/200804より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14