asd

CEJC(自宅・少人数)抽出 → 擬似日記(Top1話者) → IPIP-NEO-120(日本語) で Big5 採点 → 複数LLM平均(再現ログ)+ CSJ(対話D)検証ログ

Date: 2026-02-16
Owner: 福原玄
Last updated: 2026-02-17


0) これを一言で(何も知らない人向け)

「日常会話の文字起こし」から、性格テスト(Big Five)120問をLLMに回答させて点数化し、4つのLLMの平均を“最終スコア”として作る実験です。
論文が主張する「複数モデルの平均が強い(安定する)」を、今回の日本語会話データでも Cronbach’s α(内部一貫性)で確かめました。


1) 今回“完了”と判断した論文検証の範囲(何が言えて、何がまだか)

本mdで完了したのは、論文の主張のうち次の部分です:

一方、論文のもう一つの大きな主張である


2) 全体フロー(図)

flowchart TD
  A[CEJC convlist(会話メタ)取得] --> B[条件抽出: 自宅 & 話者数2-3]
  B --> C[utterances から Top1話者を抽出]
  C --> D[Top1話者の発話を連結して擬似モノローグ化]
  D --> E[擬似モノローグを v1 として固定(sha256付与)]
  E --> F[IPIP-NEO-120 日本語 120問]
  F --> G[Bedrockで4モデル採点(0-4, reverse対応)]
  G --> H[QC: fallback=0, rows一致, n_models=4]
  H --> I[4モデル平均ensembleを作成]
  I --> J[α(単体 vs ensemble)で“平均が強い”を検証]

3) なぜ CSJ ではなく CEJC を選んだか(コーパス選定の理由)

※ただし、次ステップとして「発話量確保」「形式差(対話/講演)の影響」を見るために、CSJ(対話D)も追試対象とした(§14)。


4) 今回のデータセット定義(固定化・バージョニング)

4.1 50 subject(会話×話者)を作る方法

4.2 v1固定(sha256を付ける)

実測 sha256:

ファイル:

※ shaファイル作成で tee を誤用して詰まった場合は、下の“正しい一発”を使う(§7.5)。


5) 質問紙(items)とスコアリング(論文互換性の要)

5.1 items は IPIP-NEO-120(日本語 120項目)

注意:過去の試行で items.csv が 30問の軽量版だった時期があるため、 本mdの「最終結果」は 必ず 120問版(ipipneo120ja120)を指すよう、出力ディレクトリ名でも識別している(§6.2)。

5.2 0–4の5件法(reverse対応)


6) LLM採点プロトコル(論文に寄せた設計)

論文側の要点(本実装で必須化した点):

本実装では、この方針を strict として固定し、 “下流で fallback になった”などを検出できるよう、QC(§9)も必ず残す。


7) 再現手順(コマンド)— 最初から最後まで

前提:

7.1 入力・前提確認

python -V
aws sts get-caller-identity

7.2 CEJC メタデータ(convlist.parquet)確認

ls -la artifacts/tmp_meta/cejc_convlist.parquet

python - <<'PY'
import pandas as pd
p="artifacts/tmp_meta/cejc_convlist.parquet"
df=pd.read_parquet(p)
print("shape=", df.shape)
print("cols =", list(df.columns))
print(df.head(3).to_string(index=False))
PY

(convlist.parquet が無い場合:scrape)

python scripts/cejc/scrape_cejc_convlist_no_lxml.py \
  --out_parquet artifacts/tmp_meta/cejc_convlist.parquet \
  --out_top_tsv artifacts/tmp_meta/cejc_diary_candidates_top200.tsv \
  --topk 200 \
| tee artifacts/tmp_meta/cejc_meta_profile.txt

7.3 diary-like(自宅・少人数)IDリスト作成(メタデータ駆動)

python - <<'PY'
import pandas as pd, re, os
inp="artifacts/tmp_meta/cejc_convlist.parquet"
out_ids="artifacts/tmp_meta/cejc_diary_home_small_ids.txt"
out_preview="artifacts/tmp_meta/cejc_diary_home_small_preview.tsv"
df=pd.read_parquet(inp)

# 話者数の数値化(列名は環境差があり得るため存在列で処理)
if "speaker_n" not in df.columns:
    src = "話者数" if "話者数" in df.columns else None
    if src:
        def to_int(x):
            s=str(x).strip()
            s=re.sub(r'^[^0-9]*','',s)
            s=re.sub(r'[^0-9].*$','',s)
            return int(s) if s.isdigit() else None
        df["speaker_n"]=df[src].map(to_int)

# 自宅 & 少人数(2-3)
place_col = "場所" if "場所" in df.columns else None
if place_col is None:
    raise SystemExit("場所 列が見つからないため抽出条件を調整してください")

sub=df.copy()
sub=sub[sub[place_col].astype(str).str.contains("自宅", na=False)]
sub=sub[sub["speaker_n"].isin([2,3])]

os.makedirs("artifacts/tmp_meta", exist_ok=True)
id_col = "会話ID" if "会話ID" in sub.columns else "conversation_id"
sub[id_col].dropna().astype(str).to_csv(out_ids, index=False, header=False)

keep=[c for c in ["会話ID","会話概要","話者数","形式","場所","活動","話者間の関係性","speaker_n"] if c in sub.columns]
sub[keep].head(100).to_csv(out_preview, sep="\t", index=False)

print("OK:", out_ids, "n_unique=", sub[id_col].nunique())
print("OK:", out_preview)
PY

7.4 utterances から Top1 話者を抽出し擬似モノローグ化(Top1)

python - <<'PY'
import pandas as pd, os
utt_path="artifacts/_tmp_utt/cejc_utterances/part-00000.parquet"
ids_path="artifacts/tmp_meta/cejc_diary_home_small_ids.txt"
out_parquet="artifacts/cejc/monologues_diary_home_top1.parquet"
os.makedirs("artifacts/cejc", exist_ok=True)

utt=pd.read_parquet(utt_path)
ids=set(pd.read_csv(ids_path, header=None)[0].astype(str).tolist())
u=utt[utt["conversation_id"].astype(str).isin(ids)].copy()

u["dur"]=(u["end_time"].astype(float)-u["start_time"].astype(float)).clip(lower=0.0)

g=(u.groupby(["conversation_id","speaker_id"], as_index=False)
     .agg(speaker_dur=("dur","sum"), n_utt=("utterance_id","count"),
          n_chars=("text", lambda s: int("".join(map(str,s)).__len__()))))
tot=(u.groupby("conversation_id", as_index=False).agg(total_dur=("dur","sum")))
g=g.merge(tot,on="conversation_id",how="left")
g["dominance"]=g["speaker_dur"]/g["total_dur"].replace({0: pd.NA})

top1=(g.sort_values(["conversation_id","dominance"], ascending=[True,False])
        .groupby("conversation_id", as_index=False).head(1))

u=u.merge(top1[["conversation_id","speaker_id"]], on=["conversation_id","speaker_id"], how="inner")
u=u.sort_values(["conversation_id","start_time","end_time"])
text=(u.groupby(["conversation_id","speaker_id"], as_index=False)
        .agg(text=("text", lambda s: "\n".join(map(str,s)))))

out=top1.merge(text,on=["conversation_id","speaker_id"],how="left")
out=out.rename(columns={"speaker_dur":"main_dur"})
out=out[["conversation_id","speaker_id","text","dominance","total_dur","main_dur","n_utt","n_chars"]]

# 速度・筋のバランスで上位50会話に限定
out=out.sort_values(["dominance","n_chars"], ascending=[False,False]).head(50).reset_index(drop=True)
out.to_parquet(out_parquet, index=False)
print("OK:", out_parquet, "n_conversations=", out["conversation_id"].nunique(), "rows=", len(out))
print(out.head(5).to_string(index=False))
PY

7.5 v1固定(コピー+sha256付与)— 正しい一発

cd ~/cpsy
cp -a artifacts/cejc/monologues_diary_home_top1.parquet \
      artifacts/cejc/monologues_diary_home_top1_v1.parquet

python - <<'PY' | tee artifacts/cejc/monologues_diary_home_top1_v1.sha256.txt
import hashlib, pathlib
p=pathlib.Path("artifacts/cejc/monologues_diary_home_top1_v1.parquet")
h=hashlib.sha256(p.read_bytes()).hexdigest()
print("sha256", h)
PY

7.6 Bedrock で Big5 採点(4モデル × 120 items)

モデル

cat > artifacts/big5/models.txt <<'TXT'
qwen.qwen3-235b-a22b-2507-v1:0
global.anthropic.claude-sonnet-4-20250514-v1:0
deepseek.v3-v1:0
openai.gpt-oss-120b-1:0
TXT
nl -ba artifacts/big5/models.txt

実行(dataset名に sha と items を埋め込む)

MONO="artifacts/cejc/monologues_diary_home_top1_v1.parquet"
ITEMS="artifacts/big5/items_ipipneo120_ja.csv"   # 120 items はこちら
ROOT="artifacts/big5/llm_scores"
SHA="a71b14ef"  # sha256先頭8桁(見やすさ用)

DATASET="dataset=cejc_diary_home_top1_v1__sha=${SHA}__items=ipipneo120ja120"
OUTROOT="$ROOT/$DATASET"

while IFS= read -r MODEL; do
  [ -z "$MODEL" ] && continue
  SAFE="$(echo "$MODEL" | tr ':/' '__')"
  OUTDIR="$OUTROOT/model=$SAFE"
  echo "== RUN $SAFE ($MODEL) =="

  python scripts/big5/score_big5_bedrock.py \
    --monologues_parquet "$MONO" \
    --items_csv "$ITEMS" \
    --model_id "$MODEL" \
    --region "ap-northeast-1" \
    --out_dir "$OUTDIR" \
  || exit 1
done < artifacts/big5/models.txt

詰まりポイント(再現メモ)


8) 主結果(論文主張「複数モデル平均が強い」を “内部一貫性(α)” でも検証)

8.0 原著(Sample2: daily diaries)で報告されている α(比較用ベンチマーク)

原著は item-level 応答を使って、各trait×各LLMの Cronbach’s α を算出している。要点は以下:

注:原著の「平均α=0.88」は “全trait×全LLMをまとめた平均”。本mdの ensemble は “4モデル平均で1つのスコア系列を作ってから trait別α”なので、厳密に同一集計ではないが、目安として比較可能。


8.1 単体モデルの Cronbach’s α(120 items = 各trait24問)

凡例:🟩>=0.70 / 🟨0.50–0.70 / 🟥<0.50

Model (Bedrock) A C E N O
Qwen3-235B 🟥0.470250 🟩0.739250 🟩0.816709 🟩0.704363 🟨0.641231
Claude Sonnet 4 🟩0.760230 🟩0.818102 🟩0.779544 🟩0.835077 🟩0.859147
DeepSeek V3 🟥0.293894 🟩0.704180 🟩0.736994 🟩0.794459 🟨0.689223
GPT-OSS 120B 🟩0.824672 🟩0.899156 🟩0.796702 🟩0.842254 🟩0.780861

観察:


8.2 4モデル平均(ensemble)の Cronbach’s α(今回の核心)

ensemble=item_mean_4models(itemごとに4モデル平均→trait集計)

Ensemble A C E N O
item_mean_4models 🟩0.781606 🟩0.904440 🟩0.874387 🟩0.875451 🟩0.862863

結論(このmd内での論文検証として言えること):

参考:原著が主に評価している “self-report との相関(収束妥当性)” は本mdでは未検証(外部基準未付与)。


8.3 論文(Sample2)との “α” 定量比較(先生向けワンメッセージ)

ここでは、比較しやすいように「平均」と「最小」を並べる。

Setting 対象 αの集計 平均α 最小α
原著 Sample2 nightly daily diaries(N=108, IPIP-NEO-120) 全trait×全LLM平均 0.88 0.70
本実験 CEJC「自宅・少人数」Top1擬似モノローグ(N=50, IPIP-NEO-120 日本語版120項目, 4models ensemble) trait別α(IPIP-NEO-120 120項目の ensemble 系列) 0.860 0.782

解釈(控えめな主張):


8.4 (参考)原著の「複数モデル平均が強い」は “相関 r” でも確認されている

原著 Sample2 では、self-report との収束相関について:

本mdは相関(外部基準)未検証だが、今回 αがensembleで改善しているのは、原著が述べる “wisdom of the crowds(複数rater平均)” と整合的。


9) QC(再現性・監査のためのチェック)

9.1 行数・fallback の検証(4モデルすべてOK)

以下のような検証を必ず残す:

python - <<'PY'
import pandas as pd
from pathlib import Path

root = Path("artifacts/big5/llm_scores/dataset=cejc_diary_home_top1_v1__sha=a71b14ef__items=ipipneo120ja120")
items = pd.read_csv("artifacts/big5/items_ipipneo120_ja.csv")
n_items = len(items)

print("root =", root)
print("n_items =", n_items)
print()

for d in sorted(root.glob("model=*")):
    item_pq = d/"item_scores.parquet"
    if not item_pq.exists():
        continue

    df = pd.read_parquet(item_pq)
    n_subj = df[["conversation_id","speaker_id"]].drop_duplicates().shape[0]
    expected = n_subj * n_items
    n_rows = len(df)

    fb = 0
    for col in ["choice_raw","choice_norm"]:
        if col in df.columns:
            fb += int((df[col].astype(str)=="NEUTRAL_FALLBACK").sum())

    ok = (n_rows==expected and fb==0)
    print(d.name)
    print(f"  subjects={n_subj}  expected_rows={expected}  item_rows={n_rows}  fallback={fb}  OK={ok}")
PY

9.2 ensembleの “n_models が常に4” を確認(穴なし)

python - <<'PY'
import pandas as pd
from pathlib import Path

root = Path("artifacts/big5/llm_scores/dataset=cejc_diary_home_top1_v1__sha=a71b14ef__items=ipipneo120ja120")
ens = root/"ensemble=item_mean_4models"/"item_scores_ensemble_mean.parquet"
df = pd.read_parquet(ens)

print(df[["n_models"]].describe())
print("n_models_min =", df["n_models"].min())
PY

10) 生成物(ファイル一覧)

10.1 単体モデル

artifacts/big5/llm_scores/dataset=cejc_diary_home_top1_v1__sha=a71b14ef__items=ipipneo120ja120/model=<MODEL_SAFE>/

10.2 ensemble

.../ensemble=item_mean_4models/


11) Appendix A(2026-02-15)Smoke strict_v2:NEUTRAL_FALLBACK 多発の修正ログ(証跡)

本番の 120items×50subjects とは別に、初期の smoke 実行で発生した不具合の“原因→対処→復旧”を残す。 結論として、本番系(§8〜§10)は fallback=0 を達成している。

A.1 事象(症状)

smoke(120 items, 1 subject)で NEUTRAL_FALLBACK が大量発生。

A.2 原因(root cause)

attempts.jsonl に保存される choice_raw / choice_norm途中で切り捨て(truncate)され、 固定選択肢と完全一致せず valid:false 扱い → downstream で fallback になっていた。

例:

A.3 対処(復旧)

(詳細スクリプトは、当該 OUTDIR 内に残しているものを参照)


12) Appendix B(2026-02-16)本番系:NaN 1件(GPT-OSS / item21)の修復ログ(bak付き)

B.1 事象

症状:

B.2 対処方針(論文プロトコルに合わせた扱い)

B.3 証跡(バックアップと最終出力)

バックアップが残っていること:

ensemble 出力:

B.4 修復手順(コピペ用:1回で完結する安全版)

ここは“先生に見せる”というより「再現性・監査」のための作業ログ。 必要ならそのまま再実行できる形にしてある。

cd ~/cpsy

python - <<'PY'
import pandas as pd
from pathlib import Path
import shutil

ROOT = Path("artifacts/big5/llm_scores/dataset=cejc_diary_home_top1_v1__sha=a71b14ef__items=ipipneo120ja120")
MODEL_DIR = ROOT/"model=openai.gpt-oss-120b-1_0"

orig_p = MODEL_DIR/"item_scores.parquet"
bak_p  = MODEL_DIR/"item_scores.parquet.bak_nanfix"

# 単発再実行で得た “正しい1行だけ” のparquet(ユーザー側で生成済みのものを指定)
fix_p  = Path("artifacts/tmp_fix/out_item21_gptoss/item_scores.parquet")

# 1) fixが1行であることを確認
fix = pd.read_parquet(fix_p)
assert len(fix)==1, f"fix rows={len(fix)}"
r = fix.iloc[0]
assert r["conversation_id"]=="S001_011" and r["speaker_id"]=="IC02" and int(r["item_id"])==21, "fix row key mismatch"

# 2) バックアップ(未作成なら作る)
if not bak_p.exists():
    shutil.copy2(orig_p, bak_p)

# 3) 置換(該当1行を落としてfixを追加)
df = pd.read_parquet(orig_p)
key = (df["conversation_id"]=="S001_011") & (df["speaker_id"]=="IC02") & (df["item_id"]==21)
print("target_rows_in_orig =", int(key.sum()))
assert int(key.sum())==1, "target row not found or duplicated"

df2 = pd.concat([df[~key], fix], ignore_index=True)

# NaNが残ってないことを確認(score)
na = int(df2["score"].isna().sum())
print("score_na_after_patch =", na)
assert na==0, "score NaN still exists after patch"

# 4) item_scores を書き戻し
df2.to_parquet(orig_p, index=False)

# 5) trait_scores 再生成
trait = (df2.groupby(["conversation_id","speaker_id","trait"], as_index=False)
           .agg(trait_score=("score","mean")))
trait.to_parquet(MODEL_DIR/"trait_scores.parquet", index=False)

# 6) cronbach_alpha 再生成(complete-case)
def cronbach_alpha(wide: pd.DataFrame) -> float:
    wide = wide.dropna(axis=1, how="all").dropna(axis=0, how="any")
    k = wide.shape[1]
    if k < 2:
        return float("nan")
    item_vars = wide.var(axis=0, ddof=1)
    total_var = wide.sum(axis=1).var(ddof=1)
    if total_var == 0 or pd.isna(total_var):
        return float("nan")
    return (k/(k-1)) * (1 - item_vars.sum()/total_var)

rows=[]
for t in sorted(df2["trait"].unique()):
    sub = df2[df2["trait"]==t]
    wide = sub.pivot_table(index=["conversation_id","speaker_id"], columns="item_id", values="score")
    rows.append({
        "trait": t,
        "alpha": cronbach_alpha(wide),
        "n_subjects": wide.dropna(axis=0, how="any").shape[0],
        "k_items": wide.shape[1]
    })

alpha_df = pd.DataFrame(rows).sort_values("trait")
alpha_df.to_csv(MODEL_DIR/"cronbach_alpha.csv", index=False)

print("\n== updated cronbach_alpha ==")
print(alpha_df.to_string(index=False))
print("\nbackup =", bak_p)
PY

修復後の該当行(例):


13) データ取り扱い(重要)


14) CSJチャレンジ(対話D): 自然寄り side 抽出 → Big5 採点 → 安定性/感度分析(N=6 → N=36)

14.0 目的(CEJCの次ステップ)


14.1 データセット定義

14.1.1 Pilot(N=6)

14.1.2 ix36 all sides(N=36)


14.2 再現手順(ix36:monologues作成→v1固定)

14.2.1 monologues生成(36 sideを全文連結)

cd ~/cpsy
python - <<'PY'
import pandas as pd
from pathlib import Path

utt_path="artifacts/_tmp_utt/csj_utterances/part-00000.parquet"
ix_path ="artifacts/phase7/metrics_interaction_csj_v1.parquet"
out_parquet="artifacts/csj/monologues_csj_ix36_all_sides.parquet"

utt=pd.read_parquet(utt_path).copy()
utt["conversation_id"]=utt["conversation_id"].astype(str)
utt["speaker_id"]=utt["speaker_id"].astype(str)
utt["text"]=utt["text"].fillna("").astype(str)

ix=pd.read_parquet(ix_path).copy()
ix["conversation_id"]=ix["conversation_id"].astype(str)
ix["speaker_id"]=ix["speaker_id"].astype(str)

ids = ix[["conversation_id","speaker_id"]].drop_duplicates()

u=utt.merge(ids, on=["conversation_id","speaker_id"], how="inner").copy()
u=u.sort_values(["conversation_id","speaker_id","start_time","end_time"], kind="mergesort")

g=(u.groupby(["conversation_id","speaker_id"], as_index=False)
     .agg(n_utt=("text","count"),
          n_chars=("text", lambda s: int(sum(map(len, s.tolist())))),
          text=("text", lambda s: "\n".join(s.tolist()).strip())))

Path("artifacts/csj").mkdir(parents=True, exist_ok=True)
g.to_parquet(out_parquet, index=False)

prev=g.copy()
prev["head200"]=prev["text"].str.replace("\n"," ", regex=False).str.slice(0,200)
prev[["conversation_id","speaker_id","n_chars","n_utt","head200"]].to_csv(
    "artifacts/csj/monologues_csj_ix36_all_sides_preview.tsv", sep="\t", index=False
)

print("OK:", out_parquet, "rows=", len(g))
print("OK: artifacts/csj/monologues_csj_ix36_all_sides_preview.tsv")
print(g[["n_chars"]].describe().to_string())
PY

14.2.2 v1固定(コピー+sha256付与)

cd ~/cpsy
cp -a artifacts/csj/monologues_csj_ix36_all_sides.parquet \
      artifacts/csj/monologues_csj_ix36_all_sides_v1.parquet

python - <<'PY' | tee artifacts/csj/monologues_csj_ix36_all_sides_v1.sha256.txt
import hashlib, pathlib
p=pathlib.Path("artifacts/csj/monologues_csj_ix36_all_sides_v1.parquet")
print("sha256", hashlib.sha256(p.read_bytes()).hexdigest())
PY

14.3 Big5採点(ix36:4モデル×120items)

cd ~/cpsy

MONO="artifacts/csj/monologues_csj_ix36_all_sides_v1.parquet"
ITEMS="artifacts/big5/items_ipipneo120_ja.csv"
ROOT="artifacts/big5/llm_scores"
SHA="de3fd2e3"

DATASET="dataset=csj_ix36_all_sides_v1__sha=${SHA}__items=ipipneo120ja120"
OUTROOT="$ROOT/$DATASET"

while IFS= read -r MODEL; do
  [ -z "$MODEL" ] && continue
  SAFE="$(echo "$MODEL" | tr ':/' '__')"
  OUTDIR="$OUTROOT/model=$SAFE"
  echo "== RUN $SAFE ($MODEL) =="

  python scripts/big5/score_big5_bedrock.py \
    --monologues_parquet "$MONO" \
    --items_csv "$ITEMS" \
    --model_id "$MODEL" \
    --region "ap-northeast-1" \
    --out_dir "$OUTDIR" \
  || exit 1
done < artifacts/big5/models.txt

14.4 結果(Pilot N=6)

14.4.1 4モデル平均(ensemble) Cronbach’s α(N=6)

Ensemble(all4) A C E N O
item_mean_4models 0.541556 0.961744 0.843843 0.854445 0.720214

観察(N=6でも見える):


14.5 結果(ix36 all sides N=36)

14.5.1 単体モデル Cronbach’s α(N=36, k_items=24)

Model A C E N O
Qwen3-235B -0.178830 0.732876 0.805688 0.757652 0.747209
Claude Sonnet 4 0.603728 0.906816 0.764628 0.839628 0.831913
DeepSeek V3 0.326002 0.866219 0.756029 0.798751 0.799292
GPT-OSS 120B 0.687978 0.923408 0.770580 0.893105 0.778073

観察:

14.5.2 ensemble(all4) Cronbach’s α(N=36)

Ensemble(all4) A C E N O
item_mean_4models 0.590261 0.947658 0.869241 0.915664 0.883791

結論:


14.6 感度分析(leave-one-out:どのモデルが効いているか)

出力:ensemble_leave1out_alpha.tsv

variant A C E N O
all4 0.590261 0.947658 0.869241 0.915664 0.883791
drop DeepSeek 0.624589 0.946937 0.841302 0.914533 0.874604
drop Claude 0.581409 0.933294 0.859361 0.897980 0.865850
drop GPT-OSS 0.372993 0.925835 0.860233 0.886028 0.869685
drop Qwen 0.630030 0.945168 0.852622 0.911565 0.869690

要点(特にA):


14.7 発話量(長さ)感度(top-k by n_chars:25/50/75/100%)

出力:ensemble_alpha_by_topk_chars.tsv 定義:ix36を n_chars 降順に並べ、top9/top18/top27/top36(=25/50/75/100%)で ensemble α を算出。

topk A C E N O
9 0.754656 0.945333 0.805720 0.888911 0.844351
18 0.678777 0.932697 0.851317 0.930902 0.839405
27 0.635316 0.937458 0.855933 0.914748 0.843385
36 0.590261 0.947658 0.869241 0.915664 0.883791

観察(重要):


14.8 CSJのQC(再現性・監査)

(CEJCと同型。期待行数は N_subjects×120)

python - <<'PY'
import pandas as pd
from pathlib import Path

root = Path("artifacts/big5/llm_scores/dataset=csj_ix36_all_sides_v1__sha=de3fd2e3__items=ipipneo120ja120")
items = pd.read_csv("artifacts/big5/items_ipipneo120_ja.csv")
n_items = len(items)

print("root =", root)
print("n_items =", n_items)
print()

for d in sorted(root.glob("model=*")):
    item_pq = d/"item_scores.parquet"
    if not item_pq.exists():
        continue

    df = pd.read_parquet(item_pq)
    n_subj = df[["conversation_id","speaker_id"]].drop_duplicates().shape[0]
    expected = n_subj * n_items
    n_rows = len(df)

    fb = 0
    for col in ["choice_raw","choice_norm"]:
        if col in df.columns:
            fb += int((df[col].astype(str)=="NEUTRAL_FALLBACK").sum())

    ok = (n_rows==expected and fb==0)
    print(d.name)
    print(f"  subjects={n_subj}  expected_rows={expected}  item_rows={n_rows}  fallback={fb}  OK={ok}")
PY

14.9 ここまでの暫定結論(先生向けワンメッセージ)


14.10 CSJ生成物(ファイル一覧)

14.10.1 monologues

14.10.2 Big5(単体モデル)

artifacts/big5/llm_scores/dataset=csj_ix36_all_sides_v1__sha=de3fd2e3__items=ipipneo120ja120/model=<MODEL_SAFE>/

14.10.3 Big5(ensemble+分析)

.../ensemble=item_mean_4models/