
Figure 1: 本記事の最終成果。/studio の LoRA dropdown に nipple_v2 が新規追加され、既存の kotonia03 (anime + 美的) と stack されて生成パラメータが調整されている。右下の star rating まで含めた dataset labeling 用 UI も同時に動いている。
「LoRA の重みを @1.0 で使うより @1.5 で使った方が品質が上がる」
これは一見ただの「強度を上げると効きが強くなる」現象に見えるが、実は LoRA の under-fit を示す不気味なシグナルだ。
うちの kotonia.ai では HiDream-O1-Image というオープンウェイトの画像生成モデルを fp8 で常駐させていて、その上に複数の自前 LoRA を乗せて差分を出している。乳首の描写品質を底上げするための nipple_v1 という LoRA があったが、ユーザーから「強度 1.0 より 1.5 の方が良い」と言われた瞬間、これは under-fit だなと察した。
そこから 1 セッションで「データ集める → caption 設計を新発明する → 17 倍に拡大 → 訓練 → A/B → deploy」までやった話を書く。
道中で NSFW dataset の世界が CSAM 汚染と DRM で構造的に閉じてる事実を発見したので、ここから始めて、最後に「3-stage Caption」という方法論にたどり着いた経緯まで全部書く。
1. 「@1.5 > @1.0」が示していた under-fit シグナル
LoRA というのは base model の weight に低ランク行列の差分 ΔW を加える仕組みで、推論時に強度 α を掛けて W' = W + α・ΔW とする。訓練時の α は 1.0 を「name plate」として、推論時に 0.5 や 1.5 にユーザーが調整できる。
通常、@1.0 が natural sweet spot で、@1.5 だと「効き過ぎ」「破綻する」「不自然になる」のどれかが起きるはず。それが kotonia.ai の nipple_v1 では、@1.0 より @1.5 の方が anatomy 質感が高かった。
これが意味するのは、訓練時に LoRA の magnitude が伸び切らずに早期収束したということ。具体的には:
- rank が足りなくて表現空間が狭かった
- LR が低すぎて単位 step あたりの進む距離が小さかった
- そもそもデータが少なくて variability を吸収しきれなかった
v1 は 525 枚 × rank 32 × 1500 step だった。これは smoke test 用のミニ訓練設定で、実運用にスケールしてなかった結果が「@1.5 で使ってください」というユーザー UX に現れた形。
直すには rank を上げ、データを増やし、step を回し、そして caption 品質を本質的に上げる必要がある。データを集めに行くところから話が始まる。
2. NSFW dataset の世界が CSAM で閉じてる、を踏んだ
「specialized domain LoRA を作りたいので、photoreal な人体描写データセットが欲しい」というのは ML 界隈ではごく普通の要求で、過去には NudeNet とか LAION-NSFW subset とか Figshare 14495367 (Image and video dataset for adult content detection) みたいな academic dataset が「使える」とされていた。
2025 年に空気が変わった。
Canadian Centre for Child Protection (C3P) が NudeNet classifier dataset (~700,000 枚) を audit し、680 枚の CSAM と 120 枚の identified victim (法執行機関データベースに照合済) を発見した。Academic Torrents は通報を受けて takedown、archive.org の元 URL も 404 になった。NudeNet を引用していた 250 件以上の学術論文が一斉に立場危うくなった。
これは突発事故ではなく構造で、Stanford Internet Observatory が 2023 年に LAION-5B で 3,200 枚以上の CSAM を発見していたパターンの後続。原因は「ポルノサイトから scrape」という共通の dataset 作成方法論で、そこから派生したすべての公開 NSFW dataset が同じ構造的リスクを抱えている。
Figshare 14495367 も「ポルノサイトをブラウズして集めた」と明記されてるし、audit 履歴がないが、scrape 方法論が同じなら統計的に同じ contamination rate (~0.1%) が期待される。30,800 枚 × 0.1% = 統計的に約 30 枚の CSAM 混入が予想される。
ここで重要なのは:
- CSAM の所持自体が刑事犯。AI training 目的の download も possession を構成する
- 「filter してから使う」では救えない。download した時点で犯罪が成立し、CSAM を自力で安全に検出する手段がない
- traceability も実在する。LoRA の重みに対する membership inference attack で「この dataset で訓練したか」を後追い検証可能
ここで「公開 NSFW dataset 経路は構造的に詰みだな」と判明し、次の候補を探した。
3. DMM/FANZA で買えば合法、は半分しか正しくなかった
「正規購入した商業 AV から frame 抽出すれば合法的に photoreal dataset を作れる」というのが次の仮説だった。著作権法 30 条の私的使用範囲なら、自分でストリーミング download した動画から frame を抜くのは個人利用の範囲だろう、と。
FANZA で S1 系の作品を一つ買って download した。.dcv という拡張子のファイルが落ちてきた。file コマンドは「ISO Media MP4 v1」と認識する。一見ただの MP4。
しかし ffprobe -read_intervals "%+#1" -show_packets で覗くと:
[PACKET]
codec_type=video
flags=K__
[SIDE_DATA]
side_data_type=Encryption info ← これ
[/SIDE_DATA]
CENC (Common Encryption, MPEG-DASH 系 DRM) で video sample が暗号化されてた。container (MP4 ftyp/moov) は読めるが、H.264 の NAL ユニット全部が暗号化バイト列。再生時に DMM Player が DRM サーバーから鍵を取得してメモリ上で復号する設計 (Widevine/PlayReady 相当)。
ここで日本の著作権法 30 条 1 項 2 号 + 2 項 (2012 改正、2018 強化) の壁にぶつかる。技術的保護手段の回避を行って行う複製は、私的使用目的でも違法で、刑事罰 (2 年以下/200 万以下) の対象。「自分で買ったから」「個人内で AI 学習に使うだけだから」では救えない。DRM 回避自体が違法行為。
Blu-ray/DVD 物理メディアも AACS/CSS で同じ DRM 構造、同じ問題。
つまり主要な商業 AV download 経路は AI training data としては構造的に閉じている。9.6 GB のファイルは購入が無駄になったが、frame 抽出への流用は不可能。
4. CivitAI RED に到達する — AI 生成 path だけが残った
公開 NSFW dataset は CSAM 汚染で全滅、AV 系商業作品は DRM で全滅、自前撮影は indie の予算規模で現実的じゃない。残ったのは CivitAI の AI 生成画像 だった。
CivitAI には community が AI で生成した画像が browsingLevel (1=PG ... 16=XXX) で分類されて API 経由で取れる。これは:
- 構造的に CSAM を内包しない (AI 生成物に identified victim は原理的に存在しない)
- age policy が明示的 (CivitAI が minor 描写を TOS 違反として削除している)
- API access OK、token 認証で大量取得可能
- provenance がクリア (各画像に prompt + model name + creator が紐付いている)
懸念は「AI 生成画像で再訓練すると mode collapse する」というよく聞く話だが、これは self-distillation (同じ model の出力を同じ model に与える) で起きる現象で、CivitAI は PonyDiffusion / Flux / SDXL / 各 community LoRA が generate した多様な分布の集合体。HiDream とは別系統 (cross-architecture) なので self-distillation にならない。
civitai_collect.py を書いて --target 20000 --weights "8:0.25,16:0.75" --min_hearts 30 で叩いた。CivitAI API が途中 500/503 を頻発したので cursor resume で複数回 retry、最終的に lvl 8: 5000 + lvl 16: 4707 = 9707 枚 で確定。20k 目標未達だが v1 の 525 枚から見れば 18.5×、十分実用域。
これで素材が揃った。次は caption 設計。
5. データ 17× してもダメだった理由 — caption mode collapse
「データ拡大すれば under-fit は解ける」という素朴な仮説は、半分しか正しくない。
v1 の 525 枚 caption は Grok 2-stage で生成していて、構造は: Stage 1 で attribute を JSON で抽出 → Stage 2 で「全部 verbatim で使って書け」と caption 合成、というもの。これを 9707 枚に適用するとどうなるか。
実は試したら、全 caption がほぼ同じ構造になった。なぜなら:
- Stage 1 で抽出する attribute 種類が固定 (7 個)
- Stage 2 prompt が単一テンプレート
- temperature 0.25 で決定論的に近い生成
→ 1 画像あたりの uniqueness は確かに attribute レベルで違うが、文構造・語彙選択・接続詞・段落配置が全画像でほぼ同じ。caption 同士が structural に同一で、TF-IDF では検出できない種類の重複が起きる。
これに気付いた瞬間「caption mode collapse」だと識別できた。LoRA はこの caption 群を学習して、特定の構文パターンに対する視覚対応を強く焼いてしまう。プロンプトのバリエーションが効かなくなる。
直す方向は 「caption 生成時点で多様化させる」。post-hoc dedup (TF-IDF / cosine 類似度で fold) は語彙重複しか捉えないので構造同型を見逃す。生成側の確率分布を変える必要がある。
ここで 3-stage caption の設計が始まる。
6. 3-stage Caption の設計 — informational value frame
設計 goal:
- 1 画像から より多くの構造的 uniqueness fodder を抽出する
- caption 生成時に 同型構造の出現確率を下げる
- nipple 領域の 語彙密度を上げる (色 + 形 + state + areola で 4 attribute を natural に織り込む)
- ローカル LLM (Gemma 4 26B A4B Uncensored on vLLM port 8899) で 9707 枚を並列処理
3 段に分けた:
Stage 1: Factual inventory (T=0.1)
12 フィールドの構造化 JSON 抽出。従来 7 個に加えて nipple_detail (color/shape/state/areola)、breast_detail (size/shape/skin_finish)、pose_framing、expression_mood、color_palette を追加。
Stage 2: Analytical reasoning (T=0.4) ← ここが新規
Stage 1 の JSON を input に「この画像が training data としてなぜ informative なのか、2-3 文で分析しろ」と書かせる。明示的に:
Do NOT make subjective quality judgments — no "beautiful", "attractive", "well-composed", "high quality", "stunning". Describe informational value only.
これが informational value frame で、reasoning に subjective quality 語彙が混入するのを構造的に防ぐ。理由は、quality 語彙を caption に焼いてしまうとモデルが「beautiful と書かないと nipple が出ない」みたいな副作用を学習してしまうから。
各画像で reasoning trace が異なる (画像ごとに distinctive feature が違うから) ので、これが最終 caption の多様性ドライバになる。
Stage 3: Caption synthesis with skeleton rotation (T=0.7)
Stage 1 の attribute + Stage 2 の reasoning を input に、最終 caption を生成。8 種の文構造 skeleton を round-robin で割り振り、画像ごとに異なる文構造で書かせる:
S0: "Close-up of [subject + pose]. [nipple/breast detail]. [lighting], [style]."
S1: "[Style] image: [pose_framing], [breast detail]. [nipple detail]. [mood], [lighting]."
S2: "[Mood] [subject] in [pose]; visible anatomy [list]. [props]. [lighting], [style]."
... (S3-S7)
そして nipple 領域の語彙密度を上げるための明示的ガイダンス:
For the nipple region, weave 2-4 complementary terms naturally into the caption. Use complementary attributes (color + shape + state + areola) rather than synonym-stuffing. Vary which subset appears across captions in this batch.
これで 1 caption に「pink, pointed nipples with prominent areolae」のような 4 attribute 自然織り込みが入る。booru tag 風の nipple, areola, breast 羅列じゃなく自然文。HiDream の Qwen3VL text encoder は自然文 caption が前提なのでこっちが効く。

Figure 2: 3-stage caption pipeline で訓練した V2 LoRA を kotonia03 (anime aesthetic) と stack した生成例。前述の "pink, pointed nipples with prominent areolae" 系の自然文 caption がモデル側に焼き込まれた結果、areola texture と nipple shape の anatomy 一貫性が出ている。
525 枚で smoke test した結果:
- areola 言及: v1 は 0/15、V2 は 4/15 (新規語彙)
- erect 言及: 2 → 7 (3.5x)
- pointed 言及: 0 → 3 (新規)
- unique trigram ratio: 0.873 → 0.919 (+5.3pp)
- self-BLEU proxy: 0.018 → 0.008 (-55%、重複減)
9707 枚 full scale で run すると 51 分で全 caption 完了 (concurrency 16、Gemma 26B uncensored の vLLM continuous batching が効く)。
7. 訓練と A/B — under-fit は本当に解けた
訓練設定:
- 9138 枚 (9707 から age safety filter 515 + discriminator score < 3.0 で 54 弾)
- rank 64 / alpha 64 (v1 の 2 倍)
- LR 1e-4 (v1 の 2 倍)
- 30000 step / batch 1 (v1 の 20 倍)
- fresh from base (v1 から continual せず、anime bias 引き継がない)
- patch_align で natural aspect 維持 (batch>1 化は aspect 歪み問題で断念)
GPU 0 (Blackwell 6000 96GB) で image_server / LTX-2 / Gemma 26B を停止して dedicated、約 3 時間で完走。loss は 0.03-0.06 で安定推移、divergence なし。

Figure 3: A/B 評価グリッド (40 サンプル)。列は base / [email protected] / [email protected] / [email protected] / [email protected]、行は 8 種の eval prompt。v1 列で "topless" prompt にも関わらず clothed 出力されているケースが目立つ (prompt adherence 失敗) のに対し、v2 列では 8/8 で正しく topless 解釈されている。これが 3-stage caption による prompt adherence 大幅改善の証拠。
A/B 評価は 8 prompt × 5 config で実施:
| config | 観察 |
|---|---|
| base (no LoRA) | nipple 描写は base model 並み、控えめ |
| [email protected] | "topless" prompt を 4-5 回無視して clothed 出力する |
| [email protected] | overshoot で品質改善、ただし v1 の上限 |
| [email protected] | 8/8 で topless 正しく解釈、nipple detail (areola/shape) clean、aesthetic 同等以上 |
| [email protected] | [email protected] と差が小さい (overshoot 余地小) = under-fit 解消の証拠 |
[email protected] ≈ [email protected] が under-fit 解消のシグナル。[email protected] と [email protected] で大きな差があったのと対比的。
さらに 8 prompt × 6 strength で sweep:
- Nipple-focused prompt 4 本: 0.5 弱、0.75 sweet、1.0 強、1.25-1.5 で body inflation 兆候
- 汎化 prompt 4 本 (clothed 女性、風景、制服アニメ、ramen): 風景と食べ物は全 strength で完全に無影響、衣服は全 strength で維持 (strip しない)、breast emphasis のみ高 strength で軽微増
これは LoRA の specialization が nipple/breast domain に local 化されてることを示してて、無関係 domain (landscape / food) を corrupt してない。
8. これで見えた「indie の compounding loop」
deploy は /studio の LoRA dropdown に nipple_v2 (乳首品質 + 汎化保持) を default weight 0.75 で追加して完了。image_server の HIDREAM_LORAS env に登録、frontend rebuild、~10 分。
このセッション 1 個から得たもの:
- 方法論として 3-stage caption が transferable。次の specialized LoRA (例: hand quality / clothing detail / specific style) でも同じ pipeline 使い回せる
- infrastructure として age safety filter + discriminator + caption 存在の 3-filter assembly が generic。CivitAI scrape の標準後処理になる
- A/B + strength sweep + 汎化 probe の評価フレームが LoRA 一般に再利用可能
それと、indie で local 完結 stack を持ってる意味が改めて見えた。
公開 dataset 経路が CSAM で閉じ、商業 AV 経路が DRM で閉じてる中、自分で API 叩いて、自分で caption pipeline 設計して、自分で GPU 焼いて、自分で deploy する経路が残った。これは VC バック型の dumping startup が真似できない動き方で、外部依存度がゼロな分、毎回の試行が次の試行の精度を上げる compound loop が成立する。
「データを輸入する」じゃなく「データを自分で蒸留する」、「caption を購入する」じゃなく「caption を自分で設計する」、「training を委託する」じゃなく「training を自分で回す」。一つ一つは小さい delta だが、stack 全体を握ってる indie には他者依存ゼロの compounding が効く。
V3 への繰越課題も明確になった:
- vulva bias root fix: Stage 1 prompt で
anatomy_visibleから vulva を除外、もしくは training data の vulva-visible image を前段 detector で filter - lying pose elongation: CivitAI lying 画像が縦長 aspect 多くて訓練データで偏った仮説。aspect ratio weighted sampling で normalize
- stack rank mismatch (v2 rank 64 と k03 rank 32):
add_weighted_adapter linearが同 rank 要求するのでpeft_model.base_model.set_adapter([list])+ per-adapter scaling 経路で別途実装
これらは「データ拡大」じゃなく「caption と sampling の設計」で解ける問題で、V3 は方法論側のさらなる磨き込みになる予定。
夜中に思いついて朝までに deploy したので、自画自賛するなら「indie の compounding loop が一晩で 1 サイクル回った」というのが今夜の収穫。