忍者ブログ
統計、機械学習、AIを学んでいきたいと思います。 お役に立てば幸いです。

【DS検定対策】生成AIの心臓部!「拡散モデル」の仕組みを攻略

画像生成AIの急速な発展を支えているのが「拡散モデル」です。一見すると複雑ですが、その基本原理は「壊して、直す」というシンプルなプロセスの繰り返しにあります。

1. 問題:拡散モデルの生成プロセス

【 問題 】 拡散モデル(Diffusion Model)が新しい画像を生成する仕組みとして、最も適切な説明はどれでしょうか?

① 大量の画像をパズルのように切り貼りして合成する  
② 入力された「完全なノイズ」から、学習した知識を用いて段階的にノイズを取り除き、データを復元する  
③ 既存の画像の色や形をランダムに変化させて別の画像を作る  
④ 画像を一度テキストに変換し、それを再度画像に描き直す

【 正解: ② 】

2. 整理:拡散モデルの「学習」と「生成」

拡散モデルの最大の特徴は、ノイズを「敵」ではなく、データを理解するための「ヒント」として活用する点にあります。

【 世界の切り出し 】

[ ステップ1:学習(壊して学ぶ) ]
・元のデータに少しずつノイズを加えていく
・AIは「どの程度のノイズが加わったか(ノイズの成分)」を予測するように訓練される。
・これにより、AIは「データの戻し方」を習得する。

[ ステップ2:生成(無から生み出す) ]
・AIに「完全なノイズ(砂嵐のようなデータ)」を渡す。
・学習した知識を使い、ノイズを少しずつ除去していく。
・最終的に、元の学習データには存在しない、新しい出力が得られる。

結論:ノイズ除去のプロが、無から有を生む

--------------------------

活用例: Stable Diffusion, Midjourney などの画像生成

3. 解説プロセス

1. 学習段階: 綺麗な写真にわざと霧(ノイズ)をかけていき、その霧の晴らし方をAIに徹底的に教え込みます。
2. 生成段階: 霧しかない状態から、AIが「ここにはこんな形があるはずだ」と霧を晴らしていくことで、新しい絵が浮かび上がります。
3. 答えを出す: 拡散モデルの本質は「ノイズからの復元」による生成なので、 が正解です。


4. DS検定形式:実戦4択クイズ

問:拡散モデルにおいて、学習データにノイズを付加していく過程を何と呼ぶか。

① 逆拡散過程(リバース過程)   ② 順拡散過程(フォワード過程)   ③ 潜在変数変換   ④ 敵対的生成

【 正解: ② 】

解説: 元のデータからノイズを増やして壊していく方向を「順拡散過程(フォワード過程)」、逆にノイズからデータを取り出していく生成の方向を「逆拡散過程(リバース過程)」と呼びます。この2つのプロセスをセットで覚えるのがポイントです。


5. まとめ

拡散モデルは、「ノイズを予測する」というシンプルなタスクを積み重ねることで、驚くほど高精細な画像を生成します。DS検定においても、生成AIの代表的な手法として「ノイズの付加と除去」というキーワードをセットで押さえておきましょう!


PR

【Kaggle挑戦記】Spaceship Titanic 攻略 #13:IDに隠された「絆」を解く。グループ人数と転送率の意外な相関<

1. PassengerId の仕様から「集団」を定義する

これまで乗客一人ひとりのスペック(年齢や支出)に注目してきましたが、今回は視点を広げ、乗客が属する「グループ」に着目しました。PassengerId の前半4桁を抽出し、同じIDを持つメンバーの数をカウント。新特徴量 GroupSize としてモデルに投入しました。

2. 【実装】グループ解析機能付き・フルソースコード

支出の論理補完、Cabinの物理分解、そして今回のグループサイズ抽出を統合したコードです。最後に、データの裏側を暴くための分析ログを出力するように設計しています。

import pandas as pd
import numpy as np
import lightgbm as lgb

# 1. データの読み込み
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')

# グループサイズを正確に測るため全データを結合
all_df = pd.concat([train, test], axis=0)
all_groups = all_df['PassengerId'].apply(lambda x: x.split('_')[0]).value_counts()

# 2. 特徴量エンジニアリング
spend_cols = ["RoomService", "FoodCourt", "ShoppingMall", "Spa", "VRDeck"]

for df in [train, test]:
    # --- A. 支出の論理補完 ---
    df[spend_cols] = df[spend_cols].fillna(0)
    total_spend = df[spend_cols].sum(axis=1)
    df.loc[(df['CryoSleep'].isnull()) & (total_spend > 0), 'CryoSleep'] = False
    df.loc[(df['CryoSleep'].isnull()) & (total_spend == 0), 'CryoSleep'] = True
    df['Age'] = df['Age'].fillna(df['Age'].median())

    # --- B. Cabinの物理分解 ---
    df['Cabin'] = df['Cabin'].fillna('U/U/U')
    df['Cabin_Deck'] = df['Cabin'].apply(lambda x: x.split('/')[0])
    df['Cabin_Side'] = df['Cabin'].apply(lambda x: x.split('/')[-1])

    # --- C. GroupSize(グループ人数)の抽出 ---
    df['Group_ID'] = df['PassengerId'].apply(lambda x: x.split('_')[0])
    df['GroupSize'] = df['Group_ID'].map(all_groups)

# 3. 学習の準備
features = ["CryoSleep", "Age", "RoomService", "FoodCourt", "ShoppingMall", "Spa", "VRDeck", "Cabin_Deck", "Cabin_Side", "GroupSize"]
X = pd.get_dummies(train[features], drop_first=True)
y = train["Transported"].astype(int)
X_test = pd.get_dummies(test[features], drop_first=True)
X, X_test = X.align(X_test, join='left', axis=1, fill_value=0)

# 4. モデル学習
model = lgb.LGBMClassifier(n_estimators=100, learning_rate=0.05, random_state=1)
model.fit(X, y)

# 5. 予測と保存
predictions = model.predict(X_test)
output = pd.DataFrame({'PassengerId': test['PassengerId'], 'Transported': predictions.astype(bool)})
output.to_csv('sub_v13_groupsize.csv', index=False)

# 6. 分析ログの出力
print("\n グループサイズ別 統計データ")
analysis = train.copy()
analysis['GroupSize'] = analysis['PassengerId'].apply(lambda x: x.split('_')[0]).map(all_groups)
summary = analysis.groupby('GroupSize')['Transported'].mean()
for size, rate in summary.items():
    print(f"グループ人数 {int(size)}人 : 転送率 {rate:.2%}")

print("\n 特徴量寄与度 (Importance)")
importances = pd.DataFrame({'Feature': X.columns, 'Importance': model.feature_importances_}).sort_values(by='Importance', ascending=False)
print(importances.head(15)) # 傾向把握のため上位15件を表示

3. 結果と考察:データが語る「家族の運命」

リーダーボードの結果は 0.80243。前回のベストスコア(0.80406)には一歩届きませんでしたが、コンソールが出力した統計データには驚くべき事実が隠されていました。

 グループサイズ別 統計データ
グループ人数 1人 : 転送率 45.24%
グループ人数 2人 : 転送率 53.80%
グループ人数 3人 : 転送率 59.31%
グループ人数 4人 : 転送率 64.08%
グループ人数 8人 : 転送率 39.42%

1人旅の転送率が約45%なのに対し、4人家族(グループ)では64.08%と跳ね上がっています。一方で、8人の大家族になると39.42%まで急落します。「中規模な家族ほど、何らかの理由で揃って異次元へ転送されやすかった」というドラマチックな偏りが見て取れます。

4. Importanceが示す「支出データの壁」

スコアが伸び悩んだ理由は、モデルが弾き出した Importance(重要度) の数値に如実に表れていました。

 特徴量寄与度 (Importance)
1. Spa           : 436
2. VRDeck        : 429
3. FoodCourt     : 414
4. Age           : 375
...
10. Cabin_Deck_E : 89
(GroupSize は圏外)

上位を占めるのは依然として Spa, VRDeck, FoodCourt といった「個人の状態」を示す支出データです。今回投入した GroupSize は、統計的な傾向(4人組は危ない、など)こそあるものの、LightGBMが「Spaに金を使っているかどうか」以上に優先すべき判断基準とは見なさなかったようです。

5. まとめ:0.8突破のその先にある「壁」

今回の実験で、宇宙船内の「社会的な繋がり」が運命を左右している確証は得られました。しかし、単なる「人数」という数字だけでは、支出データが持つ圧倒的な情報量には勝てないことも浮き彫りになりました。

「傾向はあるが、決め手にならない」。このジレンマを解消するには、次は単なる人数だけでなく、グループ内での「全員寝ていたか?」「全員同じデッキか?」といった、より深い関係性の抽出――つまり、集団としての「文脈」をさらに深掘りする必要がありそうです。

【Kaggle挑戦記】Spaceship Titanic 攻略 #12:最強タッグ「LGBM × XGBoost」結成。しかし、正解は一つとは限らない

1. 次なる一手:二大巨頭のアンサンブル

前回、物理情報「Cabin」の導入により、ついに 0.80406 という大台を突破しました。 さらなる高みを目指し、今回は Kaggle の定石である「アンサンブル(学習器の平均化)」に挑戦します。 単独で高スコアを出した鋭い LightGBM に、手堅い XGBoost を組み合わせることで、予測の「揺らぎ」を抑え、さらなる精度向上を狙いました。

2. 【実装】アンサンブル・ハイブリッドモデル全文

性格の違う2つのAIに「確率」を出させ、その平均をとる合議制ロジックです。Macのターミナルで実行した、今回のフルコードを公開します。

import pandas as pd
import numpy as np
import lightgbm as lgb
import xgboost as xgb

# 1. データの読み込み
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')

# 2. 特徴量エンジニアリング(最高スコア時のロジックを継承)
spend_cols = ["RoomService", "FoodCourt", "ShoppingMall", "Spa", "VRDeck"]

for df in [train, test]:
    # 支出の論理補完
    df[spend_cols] = df[spend_cols].fillna(0)
    total_spend = df[spend_cols].sum(axis=1)
    df.loc[(df['CryoSleep'].isnull()) & (total_spend > 0), 'CryoSleep'] = False
    df.loc[(df['CryoSleep'].isnull()) & (total_spend == 0), 'CryoSleep'] = True
    df['Age'] = df['Age'].fillna(df['Age'].median())

    # Cabinの分解(物理情報の追加)
    df['Cabin'] = df['Cabin'].fillna('U/U/U')
    df['Cabin_Deck'] = df['Cabin'].apply(lambda x: x.split('/')[0])
    df['Cabin_Side'] = df['Cabin'].apply(lambda x: x.split('/')[-1])

# 3. 特徴量の選定と整形
features = ["CryoSleep", "Age", "RoomService", "FoodCourt", "ShoppingMall", "Spa", "VRDeck", "Cabin_Deck", "Cabin_Side"]
X = pd.get_dummies(train[features], drop_first=True)
y = train["Transported"].astype(int)
X_test = pd.get_dummies(test[features], drop_first=True)
X, X_test = X.align(X_test, join='left', axis=1, fill_value=0)

# 4. モデル1:LightGBM の学習と確率予測
model_lgb = lgb.LGBMClassifier(n_estimators=100, learning_rate=0.05, random_state=1)
model_lgb.fit(X, y)
prob_lgb = model_lgb.predict_proba(X_test)[:, 1]

# 5. モデル2:XGBoost の学習と確率予測
model_xgb = xgb.XGBClassifier(n_estimators=100, learning_rate=0.05, max_depth=6, random_state=1)
model_xgb.fit(X, y)
prob_xgb = model_xgb.predict_proba(X_test)[:, 1]

# 6. アンサンブル(二人の予測確率を平均する)
final_prob = (prob_lgb + prob_xgb) / 2

# 7. 最終判定(0.5を閾値とする)
final_predictions = (final_prob >= 0.5)

# 8. 保存
output = pd.DataFrame({'PassengerId': test['PassengerId'], 'Transported': final_predictions.astype(bool)})
output.to_csv('sub_v12_ensemble.csv', index=False)

3. コンソールが示した「完璧な調和」

実行後、コンソールに出力された数字は驚くほど均衡が取れていました。

 Ensemble Complete!
LGBM Mean Prob: 0.5029
XGB Mean Prob: 0.5039

学習データの正解割合 0.5036 に対して、両モデルとも極めて近い数値を算出。二つのAIが、どちらもデータの全体像(分布)を正確に捉えていたことがわかります。

4. 結果と考察:安定を選んだ代償

リーダーボードの結果は 0.80360。前回(0.80406)からわずか 0.00046 の微減となりました。 なぜ「最強の二人」を混ぜたのに下がったのか?ここにはエンジニアリングの面白い側面があります。

  • 「尖った正解」がマイルドになった: LightGBMがギリギリの判断で正解していた難問を、XGBoostの慎重な判断が打ち消してしまった可能性があります。
  • 汎化性能の向上: スコアは僅かに下がりましたが、平均確率が安定したことで、未知のデータに対して「大外し」しにくい、より頑健なモデルになったと言えます。

5. まとめ:次なるフロンティアへ

「混ぜれば上がる」という神話を、自分のコードで検証した今回の実験。 0.8台を安定して出せるようになったことは大きな前進です。現在の特徴量においてアンサンブルが「安定」に寄ったということは、さらなるスコアアップには「新たな特徴量」が必要であるというサインでもあります。


次は、もう一つの物理情報、「グループ(PassengerId)」の解析に切り込み、さらなる高みを目指します。

【Kaggle挑戦記】Spaceship Titanic 攻略 #11:ついに0.8突破!物理情報「Cabin」の導入が運命の分かれ道となった

1. 前回の敗北から原点回帰へ

前回、統計的な分布調整による「閾値最適化」に挑みましたが、結果は 0.79003 へのダウン。 エンジニアとしての仮説「学習データとテストデータの分布は同じはず」は間違っていないと確信しつつも、AIに与える「判断材料(特徴量)」そのものを強化する必要性を痛感しました。 そこで今回、満を持して投入したのが、船内の物理的な位置を示す Cabin(客室番号) です。

2. 実装:ドメイン知識と物理情報の融合

これまでの最高得点(0.79611)を出した「支出データからの睡眠状態逆算」というドメイン知識に基づく論理補完に、Cabinからパースした「Deck(デッキ)」と「Side(右舷・左舷)」を掛け合わせました。 Macのターミナルで実行した、今回の決定版コードがこちらです。

import pandas as pd
import numpy as np
import lightgbm as lgb

# 1. データの読み込み
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')

# 2. 特徴量エンジニアリング(論理補完 & Cabin分解)
spend_cols = ["RoomService", "FoodCourt", "ShoppingMall", "Spa", "VRDeck"]

for df in [train, test]:
    # --- A. 支出実績から CryoSleep を論理的に推論 ---
    df[spend_cols] = df[spend_cols].fillna(0)
    total_spend = df[spend_cols].sum(axis=1)
    
    # 支出があれば起きている(False)、なければ寝ている(True)
    df.loc[(df['CryoSleep'].isnull()) & (total_spend > 0), 'CryoSleep'] = False
    df.loc[(df['CryoSleep'].isnull()) & (total_spend == 0), 'CryoSleep'] = True
    
    # 年齢の欠損値を中央値で補完
    df['Age'] = df['Age'].fillna(df['Age'].median())

    # --- B. Cabin(客室)を Deck/Num/Side に分解 ---
    df['Cabin'] = df['Cabin'].fillna('U/U/U')
    df['Cabin_Deck'] = df['Cabin'].apply(lambda x: x.split('/')[0])
    df['Cabin_Side'] = df['Cabin'].apply(lambda x: x.split('/')[-1])

# 3. 学習に使用する特徴量の選定
features = [
    "CryoSleep", "Age", "RoomService", "FoodCourt", 
    "ShoppingMall", "Spa", "VRDeck", "Cabin_Deck", "Cabin_Side"
]

# 4. データの整形(ダミー変数化)
X = pd.get_dummies(train[features], drop_first=True)
y = train["Transported"].astype(int)
X_test = pd.get_dummies(test[features], drop_first=True)

# 列の整合性を整える
X, X_test = X.align(X_test, join='left', axis=1, fill_value=0)

# 5. モデル学習(LightGBM)
model = lgb.LGBMClassifier(
    n_estimators=100, 
    learning_rate=0.05, 
    random_state=1
)
model.fit(X, y)

# 6. 予測と提出ファイルの出力
predictions = model.predict(X_test)
output = pd.DataFrame({
    'PassengerId': test['PassengerId'], 
    'Transported': predictions.astype(bool)
})
output.to_csv('submission_v11.csv', index=False)

3. リーダーボードの結果:歓喜の瞬間

Kaggleにファイルをアップロードし、リーダーボードが更新された瞬間、思わずガッツポーズが出ました。

 Previous Best : 0.79611
 New Score     : 0.80406 (0.8の壁を突破!)

4. 考察:なぜ「Side」が効いたのか

今回追加した「Cabin_Side(右舷・左舷)」は、事故の被害がどちらから来たかという物理的な衝突面をモデルに示唆しました。 「寝ていたか、起きていたか(状態)」×「船のどちら側にいたか(位置)」。 このミクロな情報の掛け合わせが、これまでのマクロな調整を凌駕し、ついに 0.80 の大台へと連れて行ってくれました。


一つ一つの仮説を積み上げ、データで検証する。エンジニアとしての地道なアプローチが報われた瞬間でした。
しかし、まだ上には上がいます。この勢いを止めることなく、さらなる高みを目指します。



【Kaggle挑戦記】Spaceship Titanic 攻略 #10:統計的補正の罠。AIの「楽観」を抑えた結果、見えてきたもの

1. 独自の仮説:分布不変の原則

LightGBMで 0.796 まで到達した今、次なる一手として「学習データとテストデータの分布は同じはずだ」という統計的な仮説を立てました。 AIが一律「0.5」という閾値で判断するなら、その結果としての True(転送された)の割合は、学習データの事実(50.36%)に一致すべき。もしズレているなら、閾値を動かして矯正すべきではないか、と考えたのです。

2. 実装と、Macのターミナルが示した驚愕の数字

AIに「確率」を出させ、上位50.36%だけを True と判定するように閾値を調整したところ、コンソールには衝撃的なログが流れました。

 学習データの True 割合 (目標): 0.5036
...
[LightGBM] [Info] [binary:BoostFromScore]: pavg=0.503624 -> initscore=0.014495
⚙️ 算出された最適な閾値: 0.5950
   (デフォルトの 0.5 から +0.0950 調整されました)
------------------------------
 調整後の予測 True 割合: 0.5036 (目標との差: 0.0000)

なんと、AI(LightGBM)をそのまま信じると、True の割合が統計的予測を大きく上回ってしまっていたのです。私は判定ラインを約10%引き上げ、0.5950 という厳しい基準で「選別」を行いました。

3. リーダーボードの結果:無情な 0.79003

結果は、自己ベスト更新ならず。

 Current Best (LightGBM) : 0.79611
 Ratio Adjusted Result    : 0.79003 (▼ 0.00608)

あえて統計に寄せた判断が、スコアを落とす結果となりました。

4. 考察:なぜ「正論」が通じなかったのか?

エンジニアとして、この結果から2つの教訓を得ました。

  • テストデータの分布差: 「学習用」と「テスト用」のデータ分布は、必ずしも完全一致するとは限らない。今回の事故では、テストデータ側の転送率は 50.36% より高かった可能性があります。
  • 確率は「相対的」なもの: AIが出す確率は「確信度」であって、絶対的な数値ではない。AIが 0.6 と言っても、それは「0.5の人よりは可能性が高い」という順位付けには有効ですが、その数値そのものを統計に当てはめるのは時期尚早だったのかもしれません。

5. それでも、方向性は間違っていない

「0.5」というデフォルト設定を疑い、マクロな視点で補正を試みたことは、今後の複雑なコンペティションにおいて必ず活きる経験です。 AIに盲従せず、エンジニアとしての仮説をぶつけ、その反応をデータで確認する。 この試行錯誤こそが、0.8 への唯一の道。次はいよいよ、物理情報である「Cabin(客室)」のパースに挑みます。



データは正直だ。そして、だからこそ面白い。