本日0件のお問い合わせがありました。

✉️FIELDにお問い合わせ

システム開発

GPTに頼らずに機械学習を自前でやる方法

簡単にできるのでご紹介します。

判定したい画像と、間違いの画像を複数保有している時に
これらを学習させて
モデルを作成し、
そのモデルを使って新しく判定したい画像をpostすると
判定結果が出るというようなコードがあると色々な現場に役立ちそうですよね。

そこで、
ラーメンの画像がたくさんと
ラーメンじゃない画像がたくさんあるときに
それを学習させて、
新しく画像を投げた時にそれがラーメンかどうか判定するコードを紹介します。

ラーメンでは無くて例えば
不良品の野菜判定とか、食品のカビ判定とか、金属のヒビ判定とか
そういうのに使えます。

まずファイルツリー構造は

ファイルツリー構造

deeplearning/
├── Dockerfile
├── docker-compose.yml
├── data/
│   ├── ramen/          ← ラーメン画像を置く
│   └── not_ramen/      ← ラーメンではない画像を置く
└── app/
    ├── app.py          ← Flask サーバー(判定用API)
    ├── train_model.py  ← モデル学習スクリプト
    ├── requirements.txt
    ├── templates/
    │   ├── index.html  ← 画像アップロードフォーム
    │   └── result.html ← 判定結果表示ページ
    └── static/uploads/ ← アップロード画像保存先

それぞれのコードを紹介します。

Dockerfile

FROM python:3.10-slim

WORKDIR /app

# 依存ライブラリをインストール
COPY app/requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r /app/requirements.txt

# アプリとデータをコピー
COPY app /app
COPY data /data

EXPOSE 8080

# モデルがなければ自動学習→Flask起動
CMD ["bash", "-c", "if [ ! -f model/ramen_model.h5 ]; then python train_model.py; fi && python app.py"]

docker-compose.yml

version: '3'
services:
  ramen_ai:
    build: .
    container_name: ramen_ai_app
    ports:
      - "8080:8080"
    volumes:
      - ./data:/data
      - ./app/model:/app/model
      - ./app/static/uploads:/app/static/uploads

app/requirements.txt

flask
tensorflow
pillow
numpy

app/train_model.py(完全版:壊れた画像スキップ+クリーンデータ学習)

import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten
from PIL import Image, UnidentifiedImageError
import shutil
import os
import pathlib

RAW_DIR = "/data"
CLEAN_DIR = "/tmp/clean_data"
MODEL_PATH = "model/ramen_model.h5"

# ===========================
# クリーンデータセット作成
# ===========================
def prepare_clean_dataset(raw_dir, clean_dir):
    print("🔍 画像チェック&コピー中...")
    if os.path.exists(clean_dir):
        shutil.rmtree(clean_dir)
    os.makedirs(clean_dir, exist_ok=True)

    for cls_name in ["ramen", "not_ramen"]:
        src_dir = pathlib.Path(raw_dir) / cls_name
        dst_dir = pathlib.Path(clean_dir) / cls_name
        os.makedirs(dst_dir, exist_ok=True)

        for path in src_dir.glob("*"):
            if path.suffix.lower() not in [".jpg", ".jpeg", ".png"]:
                print(f"🗑️ 非画像ファイルスキップ: {path.name}")
                continue
            try:
                with Image.open(path) as im:
                    im.verify()
                shutil.copy(path, dst_dir / path.name)
            except UnidentifiedImageError:
                print(f"⚠️ 壊れた画像スキップ: {path.name}")
            except Exception as e:
                print(f"⚠️ 読み込みエラー: {path.name} ({e})")

    print("✅ クリーンデータセット作成完了:", clean_dir)

# ===========================
# モデル学習
# ===========================
def train_model():
    os.makedirs("model", exist_ok=True)
    if os.path.exists(MODEL_PATH):
        print("✅ 既存モデルがあります。再学習をスキップします。")
        return

    prepare_clean_dataset(RAW_DIR, CLEAN_DIR)

    datagen = ImageDataGenerator(
        rescale=1.0/255,
        validation_split=0.2,
        rotation_range=20,
        zoom_range=0.2,
        horizontal_flip=True
    )

    train_gen = datagen.flow_from_directory(
        CLEAN_DIR,
        target_size=(150, 150),
        batch_size=8,
        class_mode="binary",
        subset="training"
    )

    val_gen = datagen.flow_from_directory(
        CLEAN_DIR,
        target_size=(150, 150),
        batch_size=8,
        class_mode="binary",
        subset="validation"
    )

    model = Sequential([
        Flatten(input_shape=(150, 150, 3)),
        Dense(128, activation="relu"),
        Dense(1, activation="sigmoid")
    ])

    model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"])

    print("🧠 学習開始...")
    model.fit(train_gen, validation_data=val_gen, epochs=5)

    model.save(MODEL_PATH)
    print("✅ モデルを保存しました:", MODEL_PATH)

if __name__ == "__main__":
    train_model()

app/app.py

from flask import Flask, render_template, request
from tensorflow.keras.models import load_model
from tensorflow.keras.preprocessing import image
import numpy as np
import os

app = Flask(__name__)
MODEL_PATH = "model/ramen_model.h5"

if not os.path.exists(MODEL_PATH):
    raise FileNotFoundError("モデルが存在しません。train_model.pyで学習を行ってください。")

model = load_model(MODEL_PATH)
UPLOAD_FOLDER = "static/uploads"
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

@app.route("/", methods=["GET", "POST"])
def index():
    if request.method == "POST":
        file = request.files["file"]
        if not file:
            return "ファイルがありません"

        filepath = os.path.join(UPLOAD_FOLDER, file.filename)
        file.save(filepath)

        img = image.load_img(filepath, target_size=(150, 150))
        x = image.img_to_array(img)
        x = np.expand_dims(x, axis=0) / 255.0

        pred = model.predict(x)[0][0]
        prob = round(float(pred), 3)
        threshold = 0.4  # ← 閾値調整可能
        result = f"確率: {prob} → {'🍜ラーメンです' if prob > threshold else '🙅‍♂️ラーメンではないです'}"

        return render_template("result.html", result=result, image=file.filename)

    return render_template("index.html")

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

app/templates/index.html 画面のコード

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>ラーメン画像判定</title>
</head>
<body>
  <h1>🍥 ラーメン画像をアップロードしてください</h1>
  <form method="POST" enctype="multipart/form-data">
    <input type="file" name="file" accept="image/*" required>
    <button type="submit">判定する</button>
  </form>
</body>
</html>

app/templates/result.html 結果HTML

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>判定結果</title>
</head>
<body>
  <h1>{{ result }}</h1>
  <img src="{{ url_for('static', filename='uploads/' + image) }}" width="300">
  <br><a href="/">戻る</a>
</body>
</html>

そしてモデル学習を実行してFlask実行

docker-compose run ramen_ai python train_model.py
docker-compose up -d

そしたら、
127.0.0.1:8080でアクセスできます。

💻 コード解説

train_model.py(モデル学習)

docker-compose run ramen_ai python train_model.py

を実行すると、data/ 以下の画像を読み込み、自動的にラーメン判定モデル(app/model/ramen_model.h5)を作成します。

ログには以下のように出力されます:

🔍 画像チェック&コピー中...
⚠️ 壊れた画像スキップ: IMG_6290.jpg
✅ クリーンデータセット作成完了: /tmp/clean_data
Epoch 1/5 ...
✅ モデルを保存しました: model/ramen_model.h5

app.py(推論・判定)

Flask アプリが立ち上がり、ブラウザからアクセスできます。

docker-compose up -d

アクセスURL:

http://127.0.0.1:8080

画像をアップロードすると、学習済みモデルを使って
「🍜ラーメンです」または「🙅‍♂️ラーメンではないです」
の結果が即時に返ります。


🧠 使い方の流れ

  1. data/ramen にラーメン画像、data/not_ramen にラーメンでない画像を配置。
  2. モデルを学習: docker-compose run ramen_ai python train_model.py
  3. Flaskサーバーを起動: docker-compose up -d
  4. ブラウザでアクセスして判定!

⚙️ データが少ない場合の設定変更

学習データが少ない場合は、データ拡張(augmentation)を有効にします。
train_model.py のこの部分を変更👇

train_datagen = ImageDataGenerator(
    rescale=1.0/255,
    validation_split=0.2,
    rotation_range=20,
    zoom_range=0.2,
    horizontal_flip=True,
)

これで少ない画像でもモデルがより多様な特徴を学習できます。


🚀 精度を上げたい場合

もし画像枚数が少ないけど精度を上げたいなら、転移学習を使いましょう。

train_model.py のモデル部分を以下のように差し替えるだけです👇

from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras.models import Model
from tensorflow.keras.layers import GlobalAveragePooling2D, Dense

base = MobileNetV2(weights='imagenet', include_top=False, input_shape=(150,150,3))
x = base.output
x = GlobalAveragePooling2D()(x)
x = Dense(64, activation='relu')(x)
output = Dense(1, activation='sigmoid')(x)
model = Model(inputs=base.input, outputs=output)

for layer in base.layers:
    layer.trainable = False  # 転移学習の基本

model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

→ これで、少数データでも精度が格段に向上します。


🎚 判定を“緩く”したい場合

Flask の判定ロジック(app.py)で、
出力確率の閾値を変更します👇

pred = model.predict(x)[0][0]
result = "ラーメンです 🍜" if pred > 0.4 else "ラーメンではないです 🙅‍♂️"

0.50.4 のように下げると「ラーメン」と判定しやすくなります。

実際に動かしてみて動いているコードなので

何かの参考になるのではないかと思います。

Yamamoto Yuya

プロフェッショナルとしての高いスキルと知識を持ち、誠実さと責任感を大切にする。常に向上心を持ち、新たな挑戦にも積極的に取り組む努力家。