はじめに
以前「犬と猫の画像分類」を行いました。久しぶりに画像分類が必要になったので「キノコの分類」を行ってみました。
データの準備
こちらからデータをダウンロードさせて頂きました。今回は「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