はじめに
この記事は、私個人用に Cursor でエージェントと対話して記事生成プログラムを構築し作成したものをベースにしています。
ハルシネーションチェックや動作検証など、まだ行うものも多いですが、情報共有の観点として投稿します。
問題点などございましたらコメントいただけるとありがたいです。
そのうえで、初級エンジニア向けに画像認識の基礎から実装・評価・運用までを 猫画像 で学ぶ記事にしています。
実行環境の前提(重要)
Python 3.10+,TensorFlow 2.13+(CPU/GPU どちらでも可)- データは
cats/とdogs/の 両クラス を用意(Kaggle の “Dogs vs. Cats” 等)- 画像は学習時と推論時で 同じ前処理(リサイズ・正規化)を適用
- 本稿は
TensorFlow/Kerasを採用(PyTorchでも概念は同じ)
1.基本概念の整理
1.1 コンピュータビジョンと画像認識
コンピュータビジョンは画像・動画から情報を取り出す技術全般、画像認識はその中でも 画像をクラスに振り分ける 行為です。
1.2 典型的なプロセス
- 前処理 → 2. 特徴抽出/学習 → 3. 評価/改善 → 4. 推論/運用
※ 二値分類では 正例(猫)と負例(犬/その他) の 両方 が必要。
2.データセットの準備
2.1 ディレクトリ構成(例)
dataset/
train/
cats/ ... *.jpg
dogs/ ... *.jpg
val/
cats/ ... *.jpg
dogs/ ... *.jpg
test/
cats/ ... *.jpg
dogs/ ... *.jpg
- 学習/検証/テストを 物理的に分割(データ漏れを防ぐ)。
- もし 1 つのフォルダしかない場合は
validation_splitで分割可。
2.2 tf.data による読み込み(再現性と効率)
import tensorflow as tf
from tensorflow.keras import layers
IMG_SIZE = (224, 224)
BATCH = 32
SEED = 42
AUTOTUNE = tf.data.AUTOTUNE
# 生データ(0–255のfloat32)
train_raw = tf.keras.utils.image_dataset_from_directory(
"dataset/train",
labels="inferred", label_mode="binary",
image_size=IMG_SIZE, batch_size=BATCH, shuffle=True, seed=SEED
)
val_raw = tf.keras.utils.image_dataset_from_directory(
"dataset/val",
labels="inferred", label_mode="binary",
image_size=IMG_SIZE, batch_size=BATCH, shuffle=False
)
test_raw = tf.keras.utils.image_dataset_from_directory(
"dataset/test",
labels="inferred", label_mode="binary",
image_size=IMG_SIZE, batch_size=BATCH, shuffle=False
)
class_names = train_raw.class_names # 例: ['cats', 'dogs']
# ベースライン用: 0–1に正規化
def baseline_pipeline(ds):
norm = layers.Rescaling(1./255)
return ds.map(lambda x, y: (norm(x), y), num_parallel_calls=AUTOTUNE)\
.prefetch(AUTOTUNE)
train_ds = baseline_pipeline(train_raw)
val_ds = baseline_pipeline(val_raw)
test_ds = baseline_pipeline(test_raw)
3.モデルの構築(ベースラインと転移学習)
3.1 ベースライン CNN(学習の流れを掴む)
from tensorflow.keras import Sequential, layers
import tensorflow as tf
def make_baseline(input_shape=IMG_SIZE + (3,)):
data_aug = Sequential([
layers.RandomFlip("horizontal"),
layers.RandomRotation(0.05),
layers.RandomZoom(0.1),
])
model = Sequential([
layers.Input(shape=input_shape),
data_aug,
layers.Conv2D(32, 3, activation="relu"),
layers.MaxPooling2D(),
layers.Conv2D(64, 3, activation="relu"),
layers.MaxPooling2D(),
layers.Conv2D(128, 3, activation="relu"),
layers.MaxPooling2D(),
layers.Flatten(),
layers.Dense(128, activation="relu"),
layers.Dropout(0.3),
layers.Dense(1, activation="sigmoid") # 二値分類
])
model.compile(
optimizer="adam",
loss="binary_crossentropy",
metrics=["accuracy", tf.keras.metrics.AUC(name="auc")]
)
return model
baseline = make_baseline()
baseline.summary()
3.2 転移学習(推奨: 少データでも高精度)
from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras.applications.mobilenet_v2 import preprocess_input
# MobileNetV2 は [-1,1] 入力を期待するため、生画像(0–255)に preprocess_input を適用
def mobilenet_pipeline(ds):
return ds.map(lambda x, y: (preprocess_input(tf.cast(x, tf.float32)), y),
num_parallel_calls=AUTOTUNE).prefetch(AUTOTUNE)
train_t = mobilenet_pipeline(train_raw)
val_t = mobilenet_pipeline(val_raw)
test_t = mobilenet_pipeline(test_raw)
base = MobileNetV2(input_shape=IMG_SIZE + (3,), include_top=False, weights="imagenet")
base.trainable = False # まずは凍結
inputs = layers.Input(shape=IMG_SIZE + (3,))
x = base(inputs, training=False)
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dropout(0.2)(x)
outputs = layers.Dense(1, activation="sigmoid")(x)
transfer = tf.keras.Model(inputs, outputs)
transfer.compile(optimizer="adam", loss="binary_crossentropy",
metrics=["accuracy", tf.keras.metrics.AUC(name="auc")])
transfer.summary()
ポイント
- まず ベースを凍結 → ヘッドのみ学習 → 精度が頭打ちになったら 一部解凍して微調整(低学習率)。
- 転移学習は 学習を速く・安定 させます。
4.学習・評価・保存
cb = [
tf.keras.callbacks.EarlyStopping(patience=3, restore_best_weights=True, monitor="val_auc"),
tf.keras.callbacks.ModelCheckpoint("best.keras", save_best_only=True, monitor="val_auc")
]
# ベースライン
hist_base = baseline.fit(train_ds, validation_data=val_ds, epochs=10, callbacks=cb)
# 転移学習(凍結フェーズ)
hist_tr = transfer.fit(train_t, validation_data=val_t, epochs=5, callbacks=cb)
# 微調整: 上位20層だけ学習し、低LRで追い込む
base.trainable = True
for layer in base.layers[:-20]:
layer.trainable = False
transfer.compile(optimizer=tf.keras.optimizers.Adam(1e-5),
loss="binary_crossentropy",
metrics=["accuracy", tf.keras.metrics.AUC(name="auc")])
hist_ft = transfer.fit(train_t, validation_data=val_t, epochs=5, callbacks=cb)
# テスト評価
print(transfer.evaluate(test_t))
# 保存(推奨の新形式)
transfer.save("cat_vs_dog_classifier.keras")
4.1 混同行列・レポート
import numpy as np
from sklearn.metrics import confusion_matrix, classification_report
# label_mode="binary" → (batch,1) の float32 なので整形
y_true = np.concatenate([y.numpy().ravel() for _, y in test_t]).astype(int)
y_pred = (transfer.predict(test_t).ravel() > 0.5).astype(int)
print(confusion_matrix(y_true, y_pred))
print(classification_report(y_true, y_pred, target_names=class_names))
注: しきい値
0.5は用途に応じて最適化(感度/特異度のトレードオフ)。
5.推論(単一画像/リアルタイム)
5.1 単一画像の推論
from PIL import Image
import numpy as np
from tensorflow.keras.applications.mobilenet_v2 import preprocess_input
def predict_image(path, model):
img = Image.open(path).convert("RGB").resize(IMG_SIZE)
x = np.array(img, dtype=np.float32) # 0–255
x = preprocess_input(x)[None, ...] # [-1,1] に変換
prob = float(model.predict(x, verbose=0)[0][0])
label = "cat" if prob > 0.5 else "dog"
return label, prob
print(predict_image("sample.jpg", transfer))
5.2 OpenCV でリアルタイム(簡易)
import cv2
from tensorflow.keras.applications.mobilenet_v2 import preprocess_input
cap = cv2.VideoCapture(0)
while True:
ok, frame = cap.read()
if not ok:
break
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
rgb = cv2.resize(rgb, IMG_SIZE).astype("float32") # 0–255
x = preprocess_input(rgb)[None, ...] # [-1,1]
p = float(transfer.predict(x, verbose=0)[0][0])
label = "cat" if p > 0.5 else "dog"
cv2.putText(frame, f"{label}:{p:.2f}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow("demo", frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cap.release()
cv2.destroyAllWindows()
6.よくある失敗例と正しい対策
| 失敗例 | 原因 | 対策 |
|---|---|---|
猫だけを 1 で学習 |
負例が無く、分類になっていない | 両クラス のデータを準備し、学習/検証/テストを分割 |
| 学習時と推論時で前処理が違う | 正規化やサイズが不一致 | 前処理を ユーティリティ化し共通化 |
| 評価が甘い | テストを使わずバリデーションだけ | 独立したテストセット で最終評価 |
| 過学習 | データ不足/正則化不足 | データ拡張、Dropout、早期終了、転移学習 |
| クラス不均衡 | 片方の枚数が極端に少ない | クラス重み/サンプリング/データ追加 |
| データ漏れ | 同じ個体・連番が分割を跨ぐ | 物理分割・重複排除・ハッシュで検査 |
7.リスク・代替案・実務の注意
- 権利とプライバシー: 画像のライセンス遵守。商用用途は 利用規約 確認。
- 代替アプローチ: 「猫だけ分かればよい」要件なら 一クラス分類(異常検知)も選択肢。
- 運用: モデル更新は 再学習/再評価、データドリフト監視、推論ログの収集。
- ステークホルダー: 開発/運用/法務/PM で 精度指標・誤検知許容・更新フロー を合意。
8.なぜ?で深掘り(5 Whys)
Q1: なぜ、猫画像だけで学習すると失敗するのか?
A: 二値分類なのに負例が無く、境界が学べないから。
Q2: なぜ、負例が必要なのか?
A: モデルは 差分 で境界を学習するため。猫と 犬/その他 の違いが教師信号。
Q3: なぜ、同じ前処理を徹底すべきか?
A: 前処理差は 分布ずれ(ドメインシフト)を生み、精度を壊す。
Q4: なぜ、転移学習を推奨するのか?
A: 少枚数でも 汎化 しやすく、学習が速い。
Q5: なぜ、検証だけでなくテストが必要か?
A: ハイパーパラメータ探索により 検証へ過適合 するため、独立テスト が最終判断に必須。
9.仮説 → 根拠 → 再検証 → 示唆・次のアクション
-
仮説: 正例/負例の両方を揃え、
tf.data・転移学習・適切評価を入れれば、初級でも実務水準の精度に到達できる。 - 根拠: 画像分類の標準手順(前処理統一、データ分割、転移学習、混同行列/ROC 等)。
- 再検証: 本稿コードで学習 → 検証 → テスト → 単一画像推論まで通し、再現性を確認。
- 示唆/次アクション: 誤分類分析(Grad-CAM 等)→ しきい値最適化 → 軽量化(量子化/蒸留)で運用へ。
10.おわりに
この記事では、猫の画像を分類するための画像認識プロジェクトについて、前処理からモデル構築・学習・評価・推論まで一気通貫で解説しました。
まずはベースライン → 転移学習 → 部分解凍の順で試し、手元データで改善ループを回してみてください。継続的な学習が精度と生産性を引き上げます。
参考資料
- Kaggle: Dogs vs. Cats(要ログイン)
- TensorFlow Tutorials: Image classification
- OpenCV: Python tutorials
