佐賀人のIT技術者のブログ

IT技術、日常のブログです。たまに地元・佐賀について書きます。

FlaskFormにてformを動的に生成する

アフィリエイト紹介

Webアプリの作り方からAPI化、デプロイメントまで エンジニアとデータサイエンティストのためのFlask入門は、こちら!


プログラム

サーバサイド側

cols = {
    'username': StringField('Username', [validators.Length(min=4, max=25)]),
    'email': StringField('Email Address', [validators.Length(min=6, max=35)]),
    'password': PasswordField('New Password', [
        validators.DataRequired(),
        validators.EqualTo('confirm', message='Passwords must match')
    ]),
    'confirm': PasswordField('Repeat Password'),
    'accept_tos': BooleanField('I accept the TOS', [validators.DataRequired()]),
    'photo': FileField(validators=[
        FileRequired(),
        FileAllowed(['py'], '.py only!')
    ])
}
cols['add'] = IntegerField('Add', [validators.Length(min=4, max=25)])

dform = type('RegistrationForm', (FlaskForm, ), cols)
form = dform()
if flask.request.method == 'GET':
    return render_template('test/dform.html', form=form)
else:
    if form.validate_on_submit():
        return redirect(url_for('test.dform'))
    else:
        return flask.render_template('test/dform.html', form=form)

フロントエンド側(Jinja)

{% for key, value in form.__dict__.items() %}
  {% if is_render_field(value, 'wtforms.fields.core.Field') %}
    {{ render_field(value) }}
  {% endif %}
{% endfor %}

説明

サーバサイド

1~15行目でformの項目を定義している。

17行目でRegistrationFormというクラス名で、FlaskFormを継承元として、メンバ変数にcolsの内容を保持したうえでクラスを動的に定義する。

18行目でクラスからインスタンスを作成する。

作成したインスタンスは事前定義したものと同じように扱える。

formの項目定義をJSONファイル等に保存しておけば、ユーザがカスタマイズできるフォーム等を作成することが可能になる。

フロントエンド

formを動的生成する場合、すべての項目を知る必要がある。

form.__dict__.items()でformのすべてのメンバ変数にアクセスできる。

ただ、不要なものも取得してしまうので、is_render_field(value, 'wtforms.fields.core.Field')wtforms.fields.core.Fieldを継承しているものだけを処理するようにする。

is_render_field, render_fieldは下記。

import wtforms
def is_render_field(obj, _class):
    return isinstance(obj, eval(_class))
{% macro render_field(field) %}
  {% if field.label != 'CSRF Token' %}
    <dt>{{ field.label }}
    <dd>{{ field(**kwargs)|safe }}
    {% if field.errors %}
      <ul class=errors>
      {% for error in field.errors %}
        <li>{{ error }}</li>
      {% endfor %}
      </ul>
    {% endif %}
    </dd>
  {% endif %}
{% endmacro %}

参考

https://docs.python.org/ja/3/library/functions.html#type

yoloで転移学習を行う

アフィリエイト紹介

LLMの仕組みを理解し、独自のモデルを一から構築する方法を学びたいと考えている方は、こちら!


目標

サンプルとして、ペットボトルのみを検知するモデルを作成します。

ディレクトリ構成

.
├── datasets
│   ├── bottle.v1i.yolov11 # 学習対象のデータセット
│   │   ├── README.dataset.txt
│   │   ├── README.roboflow.txt
│   │   ├── data.yaml
│   │   ├── test # 推論用データ
│   │   ├── train # 訓練用データ
│   └   └── valid # 検証用データ
├── runs
│   └── detect
│       ├── train # 学習結果
│       └── predict # 推論結果
├── venv

学習元モデル

学習元モデルはyolo11n.ptを利用します。

yolo11n.ptはCOCOデータセットで事前学習済みのモデルです。身の回りにある様々な物体を検知できます。
ここでは、最軽量であるyolo11nを使います。詳細はこちら。

ペットボトルのデータセットを用意する

転移学習を行うにはデータセットが必要です。データセットには数百~数千画像くらいのあればいいようです。

今回はこちらのデータセットを利用します。

https://universe.roboflow.com/sdp2/bottle-f2u4m

ざっくり中身をみるとペットボトル画像とラベル情報があることが分かります。

モデルを転移学習させる

下記のコマンドで学習させることができる。

yolo detect train data="datasets/bottle.v1i.yolov11/data.yaml" model=yolo11n.pt epochs=100 imgsz=640

yolo11n.ptを利用して、bottle.v1i.yolov11のデータセット100回学習させます。画像サイズは640pxでサイジングします。

実行後、runs/detect/trainが作成され、ベストなモデルや学習結果が保存されます。

学習済みモデルでペットボトルの検知を行う

学習済みモデルはruns/detect/train/weightにあります。

last.ptが最終エポック完了時のモデル、best.ptが100エポックのうち最良のモデルになりますので、best.ptを使います。

下記のコマンドで推論を実行します。

yolo detect predict model="runs/detect/train/weight/best.pt" source='datasets/bottle.v1i.yolov11/test/107_jpg.rf.0f67c515444729f6bf492f1d160abd6d.jpg'

出力結果はruns/detect/predictに結果が保存されます。

関連記事

yoloで学習・検知・追跡・ポーズ推定する - 佐賀人のIT技術者のブログ

YOLOにおける学習のパフォーマンス指標まとめ - 佐賀人のIT技術者のブログ

yoloで学習・検知・追跡・ポーズ推定する

アフィリエイト紹介

LLMの仕組みを理解し、独自のモデルを一から構築する方法を学びたいと考えている方は、こちら!


学習

yolo detect train data="coco8.yaml" model=yolo11n.pt epochs=100 imgsz=640

こちらにあるデータセットなら自動ダウンロードされる。 https://docs.ultralytics.com/ja/datasets/detect/#supported-datasets

オブジェクト検知

yolo detect predict model=yolo11n.pt source="test.mp4"

オブジェクト追跡

yolo track model=yolo11n.pt source="test.mp4" tracker="botsort.yaml"
yolo track model=yolo11n.pt source="test.mp4" tracker="bytetrack.yaml"
yolo track model=yolo11n.pt source="test.mp4" tracker="mytrack.yaml"

trackerのデフォルトコンフィグの場所

venv\Lib\site-packages\ultralytics\cfg\trackers

オブジェクト追跡 + ポーズ推定

yolo track model=yolo11n-pose.pt source="test.mp4" tracker="mytrack.yaml"

参考

https://docs.ultralytics.com/ja/tasks/detect/#train https://docs.ultralytics.com/ja/modes/track/#tracker-selection

chromadbでmetadataを追加してfilterする

アフィリエイト紹介

「生成AIやRAGに初めて触れるが、RAGの仕組みを理解したい方」「すぐに動作するRAGの構築方法を知りたい方」は、こちら!


ChromaはオープンソースのAIアプリケーションデータベースです。Chromaは、LLMのために知識、事実、スキルをプラグインできるようにすることで、LLMアプリを簡単に構築できます。 RAG構築にも利用できます。

metadata追加

metadatasを指定して追加できる。

collection.add(
    documents=[
        "This is a document about pineapple",
        "This is a document about hawaii",
        "This is a document about oranges"
    ],
    ids=["id1", "id2", "id3"],
    metadatas=[
        {"chapter": "3", "enable": False, "created": "2025/02/13"},
        {"chapter": "4", "created": "2025/02/15"},
        {"chapter": "3", "enable": True, "created": "2024/02/13"}
    ]
)

metadataフィルタリング

chapterが3のものだけとる

whereを指定してフィルタリングする。SQLと同じようにANDやORなども利用できる。

    results = collection.query(
        query_texts=["This is a query document about hawaii"], # Chroma will embed this for you
        n_results=2, # how many results to return
        where={"chapter": "3"}
        # ↓は上記と同義
        where={
            "chapter": {
                "$eq": "3"
            }
        }
    )

chapterが3でenableがTrueのものをとる

results = collection.query(
    query_texts=["This is a query document about hawaii"], # Chroma will embed this for you
    n_results=2, # how many results to return
    where={
        "$and": [
            {"chapter": "3"},
            {"enable": True}
        ]
    }
)

演算子一覧

  • $eq: = (string, int, float)
  • $ne: != (string, int, float)
  • $gt: > (int, float)
  • $gte: >= (int, float)
  • $lt: < (int, float)
  • $lte: <= (int, float)
  • $and
  • $or
  • $in
  • $nin: not in
{
  "chapter": {
    "$in": ["1", "2"]
  }
}

全文検索

where_documentを指定することで、文書の内容をフィルタリングできます。

利用できる演算子

  • $contains
  • $not_contains
results = collection.query(
    query_texts=["This is a query document about hawaii"], # Chroma will embed this for you
    n_results=2, # how many results to return
    where_document={"contains": "hawaii"}
)

参考

Add Data - Chroma Docs Metadata Filtering - Chroma Docs

YOLOにおける学習のパフォーマンス指標まとめ

アフィリエイト紹介

YOLOを実際に体験し、その可能性を自らの手で確認したい方、 YOLOによる物体追跡を理解することで新しいアイデアを具現化したい方は、こちら!


はじめに

yolo11n.ptとcoco8データセットを利用して下記のコマンドで学習を行った。

yolo detect train data=coco8.yaml model=yolo11n.yaml conf=0.25 epochs=100 imgsz=640 device="mps"

すると下記のようなresults.pngが得られる。

AIモデルの学習結果
AIモデルの学習結果

この内容についてまとめる。

coco8データセットとは

検証で利用しているcoco8データセットについてです。

公式による下記の説明がある。

COCO8データセットは、コンパクトでありながら強力な物体検出データセットであり、COCO train 2017セットから最初の8枚の画像(トレーニング用4枚、検証用4枚)で構成されています。このデータセットは、迅速なテスト、デバッグ、および YOLOモデルとトレーニングパイプラインの迅速なテスト、デバッグ、実験のために特別に設計されています。サイズが小さいため非常に管理しやすく、多様性があるため、より大きなデータセットにスケールアップする前の効果的なサニティチェックとして機能します。

検証用データセットとしては非常にコンパクトでありながらテストや分析を行いやすいため利用します。 CPUのみ環境でも十分な速度で動作します。

パフォーマンス指標

説明のため画像再掲。

項目 内容
box_loss バウンディングボックスの位置誤差。数値が低いほど誤差が少ない
cls_loss 物体のクラス分類の誤差。数値が低いほど誤差が少ない
dfl_loss aaa
precision モデルが「これは物体だ」と予測したうち、正解だった割合。数値が高いほど誤検知が少ない
recall 本当に存在する物体のうち、モデルがどれだけ見つけたかの割合。数値が高いほど見逃しが少ない
mAP50 IoU=0.50で固定。検出が「まあまあ」合っていればOK。緩めの評価指標
mAP50-95 IoU を 0.50~0.95 まで0.05刻みで評価し、平均を取る。厳しく評価
AP 各クラスごとに、そのクラスに属する物体がどれだけ正しく検出されたかを測る。mAP(mean AP): IoU、Precision、Recall、Precision-Recall-Curve、APが必要。全クラスの AP の平均で、高ければ高いほど総合的な性能が良い。
IoU 検出ボックスと正解ボックスの重なり具合を示す指標。0〜1で表され、1が完全一致。対象物の正確な位置が重要な場合に重要。
Precision Recall Curve PrecisionとRecall 曲線の面積

視覚的な指標

F1得点曲線(F1_curve.png)

F1カーブ
F1カーブ

この曲線は F1スコア を様々なしきい値にわたって示す。この曲線を解釈することで、様々な閾値における偽陽性偽陰性のモデルのバランスについて洞察することができる。

見方を調査中

Precision-recall曲線(PR_curve.png)

Presicion Recall 曲線
Presicion Recall 曲線

あらゆる分類問題に不可欠な視覚化であるこの曲線は、精度と分類のトレードオフを示します。 リコール を様々な閾値で設定することができる。これは、不均衡なクラスを扱うときに特に重要になる。

Presicion: 80%, Recall 80%は達成できるが、Presicion: 90%, Recall 90%は達成できない模様。 Presicion: 90%のときは、Recall: 70%くらいある模様。

Presicion曲線(P_curve.png)

Presicion曲線
Presicion曲線

異なる閾値における精度値のグラフ表示。この曲線は、閾値の変化に応じて精度がどのように変化するかを理解するのに役立ちます。

confが0.4を超えると精度は80%を超える模様。

Recall曲線(R_curve.png)

Recall曲線
Recall曲線

これに対応して、このグラフは、異なる閾値の間で想起値がどのように変化するかを示している。

confが0.1くらいが実際のオブジェクトを90%見逃さず検知できる模様。

正しい指標を選ぶために

項目 内容
mAP モデルの性能を幅広く評価するのに適している
IoU 対象物の正確な位置が重要な場合に不可欠
Precision 誤検出を最小限に抑えることが優先される場合に重要
Recall オブジェクトのすべてのインスタンスを検出することが重要な場合に重要

結果の解釈

項目 内容
mAPが低い モデルの全般的な改良が必要であることを示す。
IoUが低い モデルがオブジェクトを正確に特定するのに苦労している可能性がある。バウンディングボックスの方法を変えれば解決する可能性がある。
Precisionが低い モデルが存在しない物体を検出しすぎている可能性がある。信頼度のしきい値を調整することで、これを減らすことができる。
Recallが低い モデルが実際のオブジェクトを見逃している可能性がある。特徴抽出を改善するか、より多くのデータを使用することで解決できるかもしれない。
クラス別のAP ここでのスコアが低いと、そのモデルが苦手とするクラスが浮き彫りになる。

参考

パフォーマンスメトリクスの深層 -Ultralytics YOLO Docs COCO8 データセット -Ultralytics YOLO Docs

大磯城山公園・旧吉田茂邸へ行ってきた

神奈川県大磯町にある大磯城山公園に行ってきた。

お隣に旧吉田茂邸もあったのでみてきた。

アクセスは大磯駅から歩いて2km20分くらい。

大磯駅から公園前までバスも出ていたので、今回はそれに乗った。

1時間に2〜3回出ているようで、利便性はそれなりにある様子。

到着後、展望台(海抜40mくらい?)を目指して歩いた。

時間にして10〜15分くらいだろうか?

展望台でお昼を食べて、園内散策をした。

一応スポットがいくつかあるらしく順番に回った。

眺めが良いスペース、紫陽花、郷土館(今回はスキップ)、史跡跡、庭園池など。

その後、吉田茂邸へ。

ガイドもお願いできるようだったが、ゆっくり回りたかったので今回はなし。

邸宅は数奇屋の檜作りで良い感じだった。庭園もあり、松や竹など様々な植物を観覧できた。

邸宅は有料で観覧できるが今回はスキップ。

松林を抜けた先の海の見晴らしが良いところにその偉業と共に吉田茂銅像もあった。

その後、休憩所でお茶を飲み、帰路についた。

磯城山公園と旧吉田茂邸、合わせて半日あればゆっくりみれるところだった。

www.kanagawa-park.or.jp

CSVをエクセルで開くと文字化けする

アフィリエイト紹介

社会人一年目のExcel初心者から、Excelのことをある程度知っている中級者まで。
「初歩からわからない」という人には嬉しい基本から、
「ある程度慣れているけれど実は知らなかった」という応用の機能を知りたい方は、こちら!


解決

UTF-8 BOMCP932で保存すれば文字化けせずに開くことができる。

エクセルはSJISとしてファイルを開いているため、UTF-8等で保存されたファイルを開くと文字化けをする。

保存方法

実はメモ帳でUTF-8 BOMで保存できる。
CP932では保存できない様子。

PythonUTF-8またはcp932としてファイルを書き出す方法は下記

open(filepath, 'w', encoding="utf_8_sig") # UTF-8 BOM
open(filepath, 'w', encoding="cp932") # cp932

参照

UTF-8 BOMCP932を詳しく知りたい方へ