diff --git a/CLAUDE.md b/CLAUDE.md index 4468a83..df13449 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,27 +6,35 @@ Messages API: model `claude-sonnet-4-6`, `temperature=0`, thinking disabled, Batch API where possible. - Prompt definition files live in - `prompts/--.md` (e.g. 01-01-tag.md; no + `prompts/--.md` (e.g. 01-tag.md; no version suffix -- versions live in git history) and are - passed verbatim as the system prompt. -- Per-song LLM judgments (coding, screening) run the same - definition file twice, then a separate arbitration step - settles only the script-computed disagreements + passed verbatim as the system prompt. The number names a + step of the research procedure, not the file: the + deterministic vocabulary step (step 2) has no definition + file yet holds its own number. Zero padding is for + sorting only -- prose says "step 1", "step 3-2". +- Per-song LLM judgments (coding) run the same definition + file twice, then a separate arbitration step settles only + the script-computed disagreements ("2 runs + 1 arbitration"). Free-generation steps run - twice and both outputs are pooled, unarbitrated. - Vocabulary-building steps (convergence) run once as a - recorded pass. If an arbitration or validation outcome is - unexpected, revise the definition file and repeat that - cycle; never patch results by hand. + twice and both outputs are pooled, unarbitrated. The + vocabulary is built by a deterministic subcommand + (embedding + clustering), not by an LLM. If an arbitration + or validation outcome is unexpected, revise the definition + file and repeat that cycle; never patch results by hand. - Each run of a step is archived self-contained under the destination directory given explicitly on the `run-llm` - command line (by convention `runs//run/`): + command line (by convention `runs//run/`): prompt snapshot, raw output, and `meta.json` (model ID, parameters, timestamps, batch ID). The two runs of a step are two separate invocations of `run-llm`. An arbitration pass is a step of its own with its own archive. Replacing an existing run archive requires an explicit flag; - superseded runs live in git history. + superseded runs live in git history. Deterministic steps + archive under `runs//` with no `run` level. +- Token usage and cost of every `run-llm` execution are + recorded in `docs/run-costs.md` in the same commit as the + run archive. - Scripts read the API key from the `ANTHROPIC_API_KEY` environment variable (`.env`, gitignored). diff --git a/docs/decision-log.md b/docs/decision-log.md index 8d06f24..d1fa551 100644 --- a/docs/decision-log.md +++ b/docs/decision-log.md @@ -409,3 +409,33 @@ 唯 song-775 依既有裁定缺席。平台過濾器誤排除黑人 女性歌手賦權國歌一事,本身記為研究發現,供論文討論 平台結構性限制。 +- **詞彙表建構改用詞向量分群,棄用 LLM 收斂**:以 LLM + 單發把進池的 5,999 個關鍵字收斂為具名分組,四種模型 + 六次執行全部未通過完整分割驗證(不漏、不發明、每詞恰 + 一組、組名唯一),執行紀錄見 `run-costs.md`。診斷:在 + 單一回應內維持 5,999 個詞的嚴格分割,超出現有模型的 + 簿記容量(先導研究九百餘詞可行),非措辭、非模型檔次、 + 非推理量所能補救。裁定:改以詞向量嵌入+確定性分群 + 產生結構——完整分割由演算法保證、可逐位元組重現—— + LLM 僅保留其擅長的語言工作。連帶裁定:十三步複雜收斂 + 演算法(兩次執行+逐對仲裁)的比較實驗取消,其 merge + 同為整池單發且需執行兩次,必撞同一容量牆;兩版定義檔 + 止於 git 歷史(`git log -- prompts/`)。 +- **管線重設計為三步驟,取消 screen 軌,women-power 併入 + 編碼詞彙表**:收斂改為確定性程序後,管線重整為步驟 1 + 自由標註(2 次進池)→ 步驟 2 詞彙表建構(步驟 2-1 + 進池、步驟 2-2 詞向量分群,皆確定性)→ 步驟 3 編碼 + (2+1)。連帶三項決定:(1) **編號的所指由定義檔改為 + 研究程序的工序**——確定性的步驟 2 無定義檔仍佔一個 + 編號;有無定義檔即「該步是否為 LLM 判斷」的可見標記。 + 軌前綴(01/02 軌)隨 screen 軌取消而廢除。 + (2) **檔名與目錄名的補零只為排序**,正文一律寫 + 「步驟 1」「步驟 3-2」。(3) **取消 women-power 單目標 + 篩選軌**,改將該詞併入步驟 3 的定案詞彙表(50 個分群 + 組名再加上該詞,共 51 碼)。理由:單目標提問會把該主題的 + 顯著性人為抬高,「被放大檢視」的判斷無法與其他主題 + 並比;併入後研究者的先驗主題詞與模型自行收斂出的類別 + (分群已自長出 `female-empowerment` 等組)在同一份 + 提示詞、同一判斷體制下受檢,兩者落點差異本身即可報告 + 的結果。代價:候選集召回由雙通道減為單通道,若實測 + 召回不足再議。 diff --git a/docs/methodology.md b/docs/methodology.md index fe67198..6f08461 100644 --- a/docs/methodology.md +++ b/docs/methodology.md @@ -6,116 +6,142 @@ ## 自然編碼管線總覽 -四步驟:自由標註(tag,兩次進池)→ 自然收斂(merge, -單次)→ 強制收斂(cap,單次)→ 全量編碼(code,2+1), -另設「女性力量」單目標篩選(screen,2+1)作黃金標準取樣 -的補漏網。歌詞只出現在 tag、code、screen 與其仲裁步驟; -merge、cap 不接觸歌詞。設計原則見 `research-plan.md`; -本檔記載可重現的演算法細節。 +三個步驟:步驟 1 自由標註(兩次執行進池)→ 步驟 2 詞彙表 +建構(詞向量分群,確定性)→ 步驟 3 全量編碼(2+1)。歌詞 +只出現在步驟 1 與步驟 3;步驟 2 完全不接觸歌詞,也不呼叫 +LLM。設計原則見 `research-plan.md`;本檔記載可重現的 +演算法細節。 -## 詞彙表建構(merge、cap)——單次記錄性程序 +編號的所指為**研究程序的工序**,不是定義檔:步驟 1 與 +步驟 3 有定義檔(`prompts/`),步驟 2 沒有——它是確定性 +計算。有無定義檔的區別即「該步是否為 LLM 判斷」,由 +`prompts/` 是否存在同號檔案直接可見。 -merge 與 cap 各以單次執行完成,執行內自行為各組命名, -輸出具名分組(組名 → 成員詞)。不做重複執行與仲裁。 +## 步驟 2 詞彙表建構——詞向量分群 -- **定位**:詞彙表是研究的儀器,不是量測。詞彙表凍結 - 入庫後,下游全部量測(編碼 vs 黃金標準)以同一把尺 - 進行,內部一致;建構過程的抽樣變異屬「揭露的儀器 - 選擇」,不污染量測。此與質性研究慣行一致——codebook - 建構本為單次的詮釋程序,信度檢驗施於編碼應用層。 -- **取捨紀錄**:曾設計逐對仲裁鏈(共識塊、分歧塊對三票 - 多數、命名 2+1;完整版本保存於分支 `tag-algo-13`)。 - 棄用理由:逐對多數決雖降低單對變異,但遞移閉包會放大 - 結構層變異,淨縮減未經證實;其複雜度成本卻是確定的; - 且縮減的是儀器變異——對論文主張無關緊要的量。 -- **確定性驗證**(違規即依協定修訂定義檔重跑):輸出須 - 為輸入詞集的完整分割(缺詞、多詞、重複即失敗);組名 - 唯一且符合格式;cap 組數 ≤ 50。 -- **收斂軌跡**:原始關鍵字 → merge 組名 → cap 組名, - 兩份執行輸出本身即完整記錄。 +詞彙表由確定性程序產生,不經 LLM。完整分割(每個關鍵字 +恰屬一組、不遺漏、不新增)由演算法結構保證,無須事後 +驗證。 -## 編碼步驟(code、screen)的 2+1 比對與仲裁 +### 步驟 2-1 進池 -- **code**:逐首比對兩次執行的標籤集合(引述不參與 - 比對)。兩次皆有的標籤為共識保留、兩次皆無為共識 - 不標;單邊標籤送仲裁(`01-04-02-code-arb.md`)——仲裁者 - 看歌詞全文與該標籤的引述(不含執行別),裁決保留者 - 附仲裁者自己的引述,剔除者不列。剔除集合=送裁鍵減 - 輸出鍵,由程式推得。 -- **screen**:輸出即引述陣列,非空=有、空=無。僅 - 「一有一無」的歌送仲裁(`02-01-02-screen-arb.md`), - 輸入為歌詞加主張「有」方的引述(不記名),輸出同為 - 引述陣列。 +兩次標註執行的全部關鍵字取聯集、逐字串精確去重、字典序 +排列。失敗與拒答的記錄跳過(其歌曲不貢獻關鍵字);解析 +時偵測重複鍵,違規即失敗。同時寫出處記錄(關鍵字 → +(執行別,歌曲 ID)清單),供收斂軌跡分析;出處記錄不 +進任何下游輸入。 + +### 步驟 2-2 分群 + +- **嵌入**:`sentence-transformers/all-mpnet-base-v2` + (釘定 revision),關鍵字的連字號先還原為空格再編碼, + 輸出 768 維向量並 L2 正規化。 +- **分群**:階層式聚合分群(Ward linkage),k=50。 + 向量既已正規化,歐氏距離與餘弦相似度單調對應,Ward + 在保持語意距離的同時給出大小平衡的分割。 +- **組名**:取 medoid——與該組中心(成員向量均值後 + 正規化)餘弦相似度最高的成員詞。組名因此必為模型 + 自己產出過的關鍵字,非任何人事後撰寫。 +- **取捨紀錄**:曾以 LLM 單發收斂(merge/cap 兩步) + 實作本步,四種模型六次執行全部無法維持完整分割, + 已棄用(詳見 `decision-log.md` 2026-08-05;棄用的 + 定義檔止於 git 歷史,見 `git log -- prompts/`)。 +- **可重現性**:同一輸入、同一釘定模型、同一參數逐次 + 重現。不同 CPU/BLAS 實作的浮點尾數差異可能使邊界 + 詞的歸屬翻動,屬已揭露的限制;論文所用詞彙表逐字 + commit,引用單位為該份定案檔案。 + +### women-power 的注入 + +定案詞彙表為 50 個分群組名再加上 `women-power` 一詞,共 +51 個碼。`women-power` 是研究者任意決定的先驗主題(即本 +論文的主題本身),不由資料產生,屬揭露的儀器介入。 + +注入而非另設篩選軌的理由:讓研究者的主題詞與模型自己 +收斂出的類別(分群已自行長出 `female-empowerment` 等組) +在同一份提示詞、同一個判斷體制下受檢,避免單目標提問 +把該主題的顯著性人為抬高。兩者的落點差異本身即可報告 +的結果。 + +## 步驟 3 編碼的 2+1 比對與仲裁 + +- **步驟 3-1 code**:逐首比對兩次執行的標籤集合(引述不 + 參與比對)。兩次皆有的標籤為共識保留、兩次皆無為 + 共識不標;單邊標籤送仲裁。 +- **步驟 3-2 code-arb**:仲裁者看歌詞全文與該標籤的引述 + (不含執行別),裁決保留者附仲裁者自己的引述,剔除 + 者不列。剔除集合=送裁鍵減輸出鍵,由程式推得。 - 仲裁者的引述可能與原引述不同:仲裁是對歌詞的重新 判讀,其引述是該裁決自身的依據,非轉抄。 - 送入仲裁的標籤即模型自身不穩定的邊界判斷,其裁決為 單次記錄性決定,不宣稱可再生;可重現性依計畫定義為 「程序透明+可稽核」,裁決與其輸入全程歸檔。 +## 女性力量候選集 + +候選集為兩類歌曲的合集:定案編碼含 `women-power` 者, +以及定案編碼含研究者指認之女性力量概念域分群組者。 +指認於詞彙表定案後、黃金標準編碼開始前完成,指認清單 +與理由記入決策日誌。 + ## 軌跡對映(診斷用) -沿收斂軌跡的機械對映:原始關鍵字 → merge 組 → cap 組, -純程式查表,決定性。以其結果與 code 直接編碼的差異率 -作為「收斂軌跡扭曲」的診斷量,不作主結果。 +沿收斂軌跡的機械對映:原始關鍵字 →(出處記錄)歌曲、 +原始關鍵字 →(分群)組,純程式查表,決定性。以其結果 +與步驟 3 直接編碼的差異率作為「收斂軌跡扭曲」的診斷量, +不作主結果。 ## 全管線的交接契約 每一步的輸出如何變成下一步的輸入,皆為確定性程序,規則 明定如下: -- **歌詞輸入檔(tag、code、screen 共用)**: - `export-llm-input` 自工作儲存產出,每筆 - `{"id": "song-", "content": <歌詞>}`,依歌曲 ID - 升序。三個讀歌詞的步驟共用同一檔,SHA-256 記入各步 +- **歌詞輸入檔(步驟 1、3 共用)**:`export-llm-input` 自 + 工作儲存產出,每筆 `{"id": "song-", "content": + <歌詞>}`,依歌曲 ID 升序。兩個讀歌詞的步驟共用同一 + 檔,SHA-256 記入各步 meta。 +- **步驟 1 → 2-1**:`pool-keywords` 讀兩份執行歸檔的 + `output.jsonl`(一律以換行字元 `\n` 切行——歌詞含 + U+0085 等控制字元時,`str.splitlines()` 類的通用切行 + 會截斷 JSON 字串,實測踩中),輸出關鍵字 JSON 陣列 + 與出處記錄。 +- **步驟 2-1 → 2-2**:`cluster-keywords` 讀關鍵字陣列, + 輸出具名分組(組名 → 成員詞,組名與成員皆字典序) + 與記錄嵌入模型、revision、演算法參數、輸入 SHA-256 的 meta。 -- **tag → merge**:兩次執行的全部關鍵字取聯集、逐字串 - 精確去重、字典序排列成 JSON 陣列,即 merge 的輸入。 - 進池同時寫出處記錄(關鍵字 →(執行別,歌曲 ID) - 清單),供軌跡對映回到歌曲;出處記錄不進任何 LLM - 輸入。 -- **merge → cap**:merge 輸出的組名以字典序排成 JSON - 陣列,即 cap 的輸入;原始關鍵字不下傳。 -- **cap → code 定義檔**:cap 輸出的組名以字典序逐行填入 - `01-04-01-code.md` 的詞彙表節(逐字),檔案隨 git - commit 後方可執行——code 的定義檔因此自我完備, - 論文附錄可直接引用。 -- **code 兩次執行 → code-arb**:逐首比對標籤集合 - (鍵集合,引述不參與比對);僅有分歧的歌入仲裁輸入 +- **步驟 2-2 → 3 定義檔**:50 個組名與 `women-power` 合併 + 後以字典序逐行填入 `prompts/03-01-code.md` 的詞彙表節 + (逐字),檔案隨 git commit 後方可執行——步驟 3 的 + 定義檔因此自我完備,論文附錄可直接引用。 +- **步驟 3-1 兩次執行 → 3-2**:逐首比對標籤集合(鍵 + 集合,引述不參與比對);僅有分歧的歌入仲裁輸入 JSONL,依歌曲 ID 升序,每筆 `id` 沿用 `song-`、 `content` 為固定鍵序序列化的 `{"lyrics": …, "disagreements": …}`,disagreements 鍵按字典序。 -- **code 定案**:每首歌的最終標籤=共識標籤 ∪ 仲裁保留 - 標籤,寫入逐首紀錄檔(歌依 ID 升序、標籤按字典序, - 各標籤附其定案時的引述與來源層——共識或仲裁)。 -- **screen 兩次執行 → screen-arb**:僅「一有一無」的歌 - 入仲裁輸入 JSONL(依 ID 升序),`content` 為 - `{"lyrics": …, "evidence": <肯定方引述>}`。 -- **screen 定案**:命中集合=兩次皆有 ∪ 仲裁裁定有。 -- **女性力量候選集**:於 cap 詞彙表定案後、黃金標準 - 編碼開始前,由研究者指認詞彙表中屬「女性力量」概念 - 域的組(指認及理由記入決策日誌),候選集=code 定案 - 標籤含該等組者 ∪ screen 命中者。 +- **步驟 3 定案**:每首歌的最終標籤為共識標籤加上仲裁 + 保留的標籤,寫入逐首紀錄檔(歌依 ID 升序、標籤按 + 字典序,各標籤附其定案時的引述與來源層——共識或 + 仲裁)。 - **序列化通則**:所有中間檔為 UTF-8 JSON,鍵序與元素 序皆依上列規則明定,無時間戳、無隨機成分;解析一律 - 偵測重複鍵,違規即失敗。JSONL 一律以換行字元(\n) - 切行——歌詞含 U+0085 等控制字元時, - `str.splitlines()` 類的通用切行會截斷 JSON 字串 - (實測踩中)。給定相同的 LLM 執行輸出,全部交接產物 - 逐位元組可再生。 + 偵測重複鍵,違規即失敗。給定相同的 LLM 執行輸出, + 全部交接產物逐位元組可再生。 ## 執行與稽核 -- 每一步驟以 `run-llm <定義檔> <輸入檔> <歸檔目錄>` +- LLM 步驟以 `run-llm <定義檔> <輸入檔> <歸檔目錄>` 執行;2+1 步驟的兩次執行=重現命令清單上的兩行命令, - 各自歸檔(`runs/<定義檔名>/run1`、`run2`),仲裁為 - 獨立步驟、獨立歸檔;單次步驟(merge、cap)一行命令、 - 一份歸檔。 -- 進池、比對、裁決套用、對映皆為確定性程式(子命令), - 其輸入輸出檔隨 runs/ 歸檔。 + 各自歸檔(`runs/<步驟>/run1`、`run2`),仲裁為獨立 + 步驟、獨立歸檔。 +- 確定性步驟(進池、分群、比對、裁決套用、對映)為 + 子命令,其輸入輸出檔同隨 `runs/` 歸檔;因無執行變異, + 歸檔目錄下不分 `run` 層。 - Batch API 的每筆請求自含全部脈絡且互不可見(平台 契約),歌與歌之間的獨立性由此成立;兩次執行的獨立 性由「兩次呼叫、兩個批次、兩份歸檔」的執行結構自明。 +- 每次 `run-llm` 執行的 token 用量與費用記入 + `run-costs.md`,被取代的執行一併保留供總支出核算。 ## 映射分析方法 diff --git a/docs/project-structure.md b/docs/project-structure.md index 19a0efa..f58b692 100644 --- a/docs/project-structure.md +++ b/docs/project-structure.md @@ -24,9 +24,12 @@ pop-fem-audit/ │ ├── songs.csv # 歌曲報表(人讀;進 git) │ └── artists.csv # 歌手報表(人讀;進 git) ├── prompts/ # LLM 定義檔(逐字作為 system prompt) -│ └── <軌>-<步>-<次步>-.md # 01-01-01-tag.md、 -│ # 01-04-02-code-arb.md… +│ └── <步>-<次步>-.md # 01-tag.md、03-01-code.md、 +│ # 03-02-code-arb.md │ # 不帶版本號,版本即 git 歷史 +│ # (編號的所指是工序:確定性 +│ # 的步驟 2 無定義檔仍佔一號; +│ # 補零只為排序) ├── tools/ # 輔助工具子專案(src-layout) │ ├── pyproject.toml # 發行名 pop-fem-audit-tools; │ │ # pip install -e tools/ 安裝 @@ -46,6 +49,10 @@ pop-fem-audit/ │ │ │ │ # Wikidata into the snapshot CSV │ │ │ ├── fetch_lyrics.py # fetch missing lyrics from the │ │ │ │ # public APIs into the lyrics dir +│ │ │ ├── pool_keywords.py # pool the two tagging runs' +│ │ │ │ # keywords (step 2-1) +│ │ │ ├── cluster_keywords.py # build the vocabulary by +│ │ │ │ # embedding + clustering (step 2-2) │ │ │ └── run_llm.py # API 執行器:一份定義檔+一份輸入 │ │ │ # →歸檔至指定目錄(Batch API); │ │ │ # 比對與仲裁編排由獨立子命令承擔 @@ -56,13 +63,14 @@ pop-fem-audit/ │ └── tests/ # 單元測試(unittest) ├── runs/ # 現行執行的完整稽核紀錄(進 git; │ │ # 重跑同一 run 須明示 --replace) -│ └── <定義檔名>/ # 仲裁步驟居自己的 -arb/ -│ └── run/ # 每個 run 一份自我完備歸檔 -│ ├── prompt.md # 當次定義檔快照(自我完備) -│ ├── output.jsonl # 該次執行原始輸出 -│ └── meta.json # model ID、temperature、時間戳、 -│ # batch ID、token 用量 -│ # (一致率由比對子命令記錄) +│ ├── <步驟名>/ # 仲裁步驟居自己的 -arb/ +│ │ └── run/ # LLM 步驟:每個 run 一份自我 +│ │ ├── prompt.md # 完備歸檔(定義檔快照) +│ │ ├── output.jsonl # 該次執行原始輸出 +│ │ └── meta.json # model ID、temperature、時間戳、 +│ │ # batch ID、token 用量 +│ └── 02-01-pool/ 02-02-cluster/ # 確定性步驟:無執行變異, +│ # 不分 run 層 ├── results/ # 論文引用的報表 CSV(export 產出; │ # 「可再生仍 commit」的唯一例外) ├── docs/ @@ -70,6 +78,7 @@ pop-fem-audit/ │ ├── project-structure.md # 本檔 │ ├── codebook.md # 人工編碼手冊(版本由 git 管理) │ ├── decision-log.md # 決策日誌:每次改定義檔的原因 +│ ├── run-costs.md # 每次執行的 token 用量與費用 │ └── methodology.md # 方法細節(全文方法節底稿; │ # 映射分析方法須在看結果前寫定) └── paper/ diff --git a/docs/research-plan.md b/docs/research-plan.md index 073f629..ab54124 100644 --- a/docs/research-plan.md +++ b/docs/research-plan.md @@ -13,12 +13,12 @@ - **執行原則**:主會話只做討論;所有分析由 deterministic script 執行。LLM 步驟以 Python script 呼叫 Anthropic Messages API (個人 Console 帳號、Batch API 五折),定義檔逐字作為 system - prompt。2+1 協定適用於語料層的逐首判斷(編碼、篩選): + prompt。2+1 協定適用於語料層的逐首判斷(編碼): 「同一定義檔獨立執行兩次+一次仲裁」,仲裁只裁程式算出 的分歧。自由生成步驟(自由標註)兩次執行全數進池、不 仲裁——自由詞彙兩次輸出不共享比對單位,無物可裁。詞彙 - 表建構(收斂)為單次記錄性程序,不重複執行——詞彙表是 - 揭露的儀器選擇,非量測;信度檢驗施於編碼層(詳見 + 表建構不經 LLM,改由詞向量嵌入+確定性分群產生——詞彙 + 表是揭露的儀器選擇,非量測;信度檢驗施於編碼層(詳見 `methodology.md`)。仲裁或驗證結果不符預期則修訂定義檔 重跑該循環。 - **提示詞只定格式、不定語意**:研究對象是通用 LLM 以其 @@ -86,55 +86,49 @@ `export-llm-input`(階段 2 前補上);之後再加報表 export 與統計。 -## 自然編碼管線(2026-08-04 定案) +## 自然編碼管線(2026-08-05 定案) -主題編碼採四步驟管線,歌詞只出現在第 1、4 步;第 2、3 步 -是純概念整理(目標是產生 codebook 詞彙表,不是編碼)。 +主題編碼採三步驟管線,歌詞只出現在第 1、3 步;第 2 步是 +確定性計算(詞向量分群),不接觸歌詞也不呼叫 LLM。 -1. **自由標註(tag)**:逐首歌請模型標註 thematic +1. **自由標註(步驟 1)**:逐首歌請模型標註 thematic keywords,附歌詞引述;提示零語意內容。獨立執行兩次, 兩次輸出全數進池(記錄執行別),不仲裁。粒度鎖定 thematic keywords:前導研究三粒度比較(keywords 過碎、 themes 過早抽象)之繼承,於執行前鎖定,防止事後擇優。 -2. **自然收斂(merge)**:輸入為池中純去重關鍵字清單—— - 無歌詞、無頻次、無歌曲出處——模型按自身理解合併近似 - 概念,執行內自行為各組命名。單次執行,不仲裁;確定性 - 驗證(完整分割、組名唯一)違規即修訂重跑(詳見 - `methodology.md`)。 -3. **強制收斂(cap)**:同第 2 步形態,輸入為 merge - 輸出的組名清單(原始關鍵字不下傳),限制併至 50 個 - 以內。單次執行,不仲裁。頻次不入收斂:頻率的分析角色由第 4 步編碼承擔; - 池中頻次含跨執行噪音;頻次會誘使模型以頻率剪枝(喪失 - 稀有主題)並把高頻大主題切細。代價(特異主題佔名額) - 已知並接受,換取主題多樣性與純語意歸併的可辯護性。 -4. **編碼(code)**:以定稿詞彙表對全部歌曲 2+1 編碼—— - 模型讀歌詞、逐標籤附引述;逐首計一致率,分歧逐首仲裁 - (仲裁者看歌詞與兩邊引述)。此為主儀器,「女性力量」 - 候選集由此浮現;亦是對前導研究「映回後未對照歌詞」 - 限制的明文改良。沿收斂軌跡的機械對映(純程式)保留為 - 零成本診斷副產品,量測收斂軌跡的扭曲,不作主結果。 +2. **詞彙表建構(步驟 2)**:兩次執行的關鍵字取聯集去重 + (步驟 2-1 進池),以句向量模型嵌入後階層式聚合分群 + (步驟 2-2),k=50,組名取 medoid。完整分割由演算法 + 結構保證。頻次不入收斂:頻率的分析角色由步驟 3 編碼 + 承擔;池中頻次含跨執行噪音。 +3. **編碼(步驟 3)**:以定稿詞彙表對全部歌曲 2+1 編碼 + ——模型讀歌詞、逐標籤附引述;逐首計一致率,分歧逐首 + 仲裁(仲裁者看歌詞與該標籤引述)。此為主儀器, + 「女性力量」候選集由此浮現;亦是對前導研究「映回後 + 未對照歌詞」限制的明文改良。沿收斂軌跡的機械對映 + (純程式)保留為零成本診斷副產品,量測收斂軌跡的 + 扭曲,不作主結果。 -另設**「女性力量」單目標篩選(screen)**:單目標提示、 -附引述、全量執行,僅作為黃金標準取樣的補漏網——把「模型 -被明示提醒後認得出」的歌撈進人工審視範圍,以量測模型漏標 -方向的偏差;不進偏差統計的分子分母定義。逐首 2+1(有/無 -+引述,逐首仲裁);同樣不給定義,提示僅含標籤詞與任務 -形狀。標籤詞用 `women-power`——研究者任意決定的先驗 -主題,即本研究的論文主題本身,不由資料產生。其結果僅作 -召回之用,永不進任何統計的分子分母。標籤詞是 screen -提示中唯一的語意種子,屬研究者的儀器選擇,據實揭露。 +**先驗主題詞 `women-power` 併入詞彙表**:定案詞彙表為 +50 個分群組名再加上 `women-power`,共 51 個碼。該詞是 +研究者任意決定的先驗主題,即本研究的論文主題本身,不由 +資料產生,屬據實揭露的儀器介入。不另設單目標篩選軌,理由是 +讓研究者的主題詞與模型自己收斂出的類別在同一份提示詞、 +同一個判斷體制下受檢——單目標提問會把該主題的顯著性 +人為抬高,「被放大檢視」的結果不足以與其他主題並比。 +候選集的定義見 `methodology.md`。 -定義檔命名 `prompts/<軌>-<步>-<次步>-.md`——軌 -01=由下而上自然編碼、02=預先決定的 women-power 篩選; -步為軌內步驟序,次步為步內執行順序,讀者依編號先後依循: -01-01-01-tag.md、01-02-01-merge.md、01-03-01-cap.md、 -01-04-01-code.md、01-04-02-code-arb.md、 -02-01-01-screen.md、02-01-02-screen-arb.md;仲裁定義檔 -同 prefix 加 `-arb`。檔名不帶版本號——版本即 git 歷史, -失敗的版本不保留,需要回看的舊版都在 git history;每次 -執行的定義檔快照隨 `runs/` 自我完備。收斂執行的輸出本身 -即分組記錄(具名分組 JSON),與全部中間交接檔同隨 -`runs/` 歸檔(交接契約見 `methodology.md`)。 +定義檔命名 `prompts/<步>-<次步>-.md`——步為研究 +程序的工序序,次步為步內執行順序(僅一個執行時省略), +讀者依編號先後依循:01-tag.md、03-01-code.md、 +03-02-code-arb.md;仲裁定義檔同 prefix 加 `-arb`。檔名 +與目錄名補零只為排序,正文一律寫「步驟 1」「步驟 3-2」。 +編號的所指是工序而非定義檔,因此確定性的第 2 步雖無 +定義檔仍佔一個編號,其歸檔為 `runs/02-01-pool/`、 +`runs/02-02-cluster/`。檔名不帶版本號——版本即 git +歷史,失敗的版本不保留,需要回看的舊版都在 git history; +每次執行的定義檔快照隨 `runs/` 自我完備。全部中間交接檔 +同隨 `runs/` 歸檔(交接契約見 `methodology.md`)。 ## 階段與時程(全文截稿 2026-08-15) @@ -142,7 +136,7 @@ |---|---|---|---| | 0 | 基礎建設:git init、目錄結構、.gitignore、決策日誌、runner script(含 Batch API)、codebook v0 骨架 | script + 討論 | 7/30–7/31 | | 1 | 資料準備:`run_llm` 改走統一設定 → `build-db`(解析榜單成 songs/chart_entries/artists/song_artists)→ `import-lyrics`(pilot 2018–2025)→ `fetch-lyrics`(2016–17 與缺漏,Lyrics.ovh / LRCLIB)→ `fetch-artists`(Wikidata 快照)→ `export-llm-input` | 子命令 | 7/31–8/3 | -| 2 | 自然編碼管線:tag ×2 進池 → merge ×1 → cap ×1 → 詞彙表定稿 → code 2+1(全 883 首,附引述);另跑 screen 補漏網 | API + script | 8/4–8/7 | +| 2 | 自然編碼管線:tag ×2 進池 → 詞向量分群 k=50 → 併入 women-power → 詞彙表定稿 → code 2+1(全 883 首,附引述) | API + script | 8/4–8/7 | | 3 | 黃金標準:依 codebook 人工逐首判定 genuine/peripheral/fake,附引用歌詞證據表(LLM 只做摘錄,不給判定建議);先以 10–15 首校準樣本試編並修訂 codebook 後凍結;同批校準樣本實測 Sonnet 4.6 vs Opus 5 一致率 | 人工 + script 輔助 | 8/5–8/9 | | 4 | 受控比較(盲點實驗):條件 A(詞彙層提示)vs 條件 B(框架感知提示),各 2+1,對照黃金標準計算假陽/假陰率 | API | 8/8–8/11 | | 4' | 映射分析:自然編碼結果(第 4 步)與黃金標準交叉表;軌跡對映 vs 直接編碼的扭曲診斷(分析方法先寫入 methodology.md 再看結果) | script | 與 4 並行 | @@ -172,3 +166,5 @@ 論文與 repo 只留分類所引摘錄。 - Fable 5 不用於 pipeline:成本高、thinking 無法關閉且不可稽核、 無 temperature 控制,且會混淆「盲點是提示問題」的核心主張。 + (2026-08-05 實測確認:Claude 5 系模型拒收 temperature + 與 thinking 關閉這兩個參數。) diff --git a/docs/run-costs.md b/docs/run-costs.md index 3764508..4794177 100644 --- a/docs/run-costs.md +++ b/docs/run-costs.md @@ -1,23 +1,33 @@ # 執行成本記錄 每次 `run-llm` 執行的 token 用量與費用。用量取自各歸檔的 -`meta.json`;費用以 `claude-sonnet-4-6` 牌價(input $3/ -百萬 token、output $15/百萬 token)經 Batch API 半價 -計算: +`meta.json`;費用以各模型牌價(每百萬 token:sonnet-4-6 +$3/$15、opus-4-6 $5/$25、opus-5 與 fable-5 $10/$50) +經 Batch API 半價計算: - 費用 = (input × 3 + output × 15) / 1,000,000 / 2 + 費用 = (input × in價 + output × out價) / 1,000,000 / 2 被 `--replace` 取代的執行以「已取代」標記,數字保留供 總支出核算。 -| 日期 | 步驟 | 執行 | 批次 ID | 耗時 | input | output | 費用 (USD) | 狀態 | -|---|---|---|---|---|---:|---:|---:|---| -| 2026-08-04 | 01-01-01-tag | run1 | msgbatch_01PTACDQMr8M6ahnshbedjtB | 6 分 27 秒 | 763,318 | 338,661 | $3.68 | 已取代(浮水印清洗與防圍欄修訂後重跑) | -| 2026-08-05 | 01-01-01-tag | run1 | msgbatch_01TikJNd2pZVxzQ8SaybVthu | 4 分 57 秒 | 774,604 | 323,651 | $3.59 | 已取代(合法 JSON 修訂後重跑) | -| 2026-08-05 | 01-01-01-tag | run1 | msgbatch_01JFBCNqnu1cwXyqmEKLHQYF | 4 分 30 秒 | 790,370 | 328,227 | $3.65 | 已取代(song-288 平台失敗,整批重跑驗證) | -| 2026-08-05 | 01-01-01-tag | run1 | msgbatch_01VSDneWuSbf8mShA32jiWrX | 6 分 15 秒 | 790,370 | 326,193 | $3.63 | 已取代(措辭修訂後全體重跑) | -| 2026-08-05 | 01-01-01-tag | run1-rescue-288 | msgbatch_019Hq6bNVXVmp4DjRZ2cgVda | 2 分 5 秒 | 1,015 | 376 | $0.01 | 已取代(措辭修訂後全體重跑,該首原生通過)| -| 2026-08-05 | 01-01-01-tag | run1 | msgbatch_01VgZ77KAPGWmuu3PnQFqZ7Q | 7 分 1 秒 | 794,913 | 326,435 | $3.64 | 現行 | -| 2026-08-05 | 01-01-01-tag | run2 | msgbatch_01TLFey3L4fimKxcebTZQGYn | 4 分 21 秒 | 794,913 | 328,324 | $3.65 | 現行 | +| 日期 | 步驟 | 執行 | 模型 | 批次 ID | 耗時 | input | output | 費用 (USD) | 狀態 | +|---|---|---|---|---|---|---:|---:|---:|---| +| 2026-08-04 | 01-01-01-tag | run1 | claude-sonnet-4-6 | msgbatch_01PTACDQMr8M6ahnshbedjtB | 6 分 27 秒 | 763,318 | 338,661 | $3.68 | 已取代(浮水印清洗與防圍欄修訂後重跑) | +| 2026-08-05 | 01-01-01-tag | run1 | claude-sonnet-4-6 | msgbatch_01TikJNd2pZVxzQ8SaybVthu | 4 分 57 秒 | 774,604 | 323,651 | $3.59 | 已取代(合法 JSON 修訂後重跑) | +| 2026-08-05 | 01-01-01-tag | run1 | claude-sonnet-4-6 | msgbatch_01JFBCNqnu1cwXyqmEKLHQYF | 4 分 30 秒 | 790,370 | 328,227 | $3.65 | 已取代(song-288 平台失敗,整批重跑驗證) | +| 2026-08-05 | 01-01-01-tag | run1 | claude-sonnet-4-6 | msgbatch_01VSDneWuSbf8mShA32jiWrX | 6 分 15 秒 | 790,370 | 326,193 | $3.63 | 已取代(措辭修訂後全體重跑) | +| 2026-08-05 | 01-01-01-tag | run1-rescue-288 | claude-sonnet-4-6 | msgbatch_019Hq6bNVXVmp4DjRZ2cgVda | 2 分 5 秒 | 1,015 | 376 | $0.01 | 已取代(措辭修訂後全體重跑,該首原生通過)| +| 2026-08-05 | 01-01-01-tag | run1 | claude-sonnet-4-6 | msgbatch_01VgZ77KAPGWmuu3PnQFqZ7Q | 7 分 1 秒 | 794,913 | 326,435 | $3.64 | 現行 | +| 2026-08-05 | 01-01-01-tag | run2 | claude-sonnet-4-6 | msgbatch_01TLFey3L4fimKxcebTZQGYn | 4 分 21 秒 | 794,913 | 328,324 | $3.65 | 現行 | +| 2026-08-05 | 01-02-01-merge | run1 | claude-sonnet-4-6 | msgbatch_01BvYMFmH8zrUq9SNxWSNba7 | 9 分 14 秒 | 46,454 | 60,974 | $0.53 | 已取代(完整分割驗證不過:漏 366、重複分派 511、撞名 4) | +| 2026-08-05 | 01-02-01-merge | run1 | claude-opus-5 | msgbatch_018TkYpdT3DsZqq2q94FQCUN | — | 0 | 0 | $0.00 | 拒收(temperature 已棄用) | +| 2026-08-05 | 01-02-01-merge | run1 | claude-opus-4-8 | msgbatch_018Wuux4adz5mWSvzJjfgLxb | — | 0 | 0 | $0.00 | 拒收(temperature 已棄用) | +| 2026-08-05 | 01-02-01-merge | run1 | claude-opus-4-6 | msgbatch_01Lmt2j1Txyof9CctUK7CbHA | 12 分 19 秒 | 46,454 | 54,864 | $0.80 | 已取代(圍欄違規;漏 190、發明 151、重複 11) | +| 2026-08-05 | 01-02-01-merge | run1 | claude-opus-5 | msgbatch_012SEfebTnjU5uWN6hhfnhhh | 8 分 45 秒 | 63,368 | 64,000 | $1.92 | 已取代(64k 截斷) | +| 2026-08-05 | 01-02-01-merge | run1 | claude-opus-5 | msgbatch_01Wi9xEoQKCy1nZK6myVNg77 | 11 分 34 秒 | 63,368 | 70,175 | $2.07 | 已取代(驗證不過:漏 59、發明 140、重複 3) | +| 2026-08-05 | 01-02-01-merge | run1 | claude-fable-5 | msgbatch_01M15hUs8P9FDTqWvAHdc1cA | 32 分 45 秒 | 63,368 | 107,721 | $3.01 | 現行歸檔(JSON 語法毀損,驗證不過) | -累計支出:$21.85。 +| 2026-08-05 | 01-02-01-merge | run1 | claude-fable-5 | msgbatch_012stRP28uuQDQwobm41HMLh | — | 0 | 0 | $0.00 | 拒收(thinking.type.enabled 不支援) | +| 2026-08-05 | 01-02-01-merge | run1 | claude-fable-5 | msgbatch_01KpS7AMx1zAHTcNujgGsouJ | 26 分 28 秒 | 63,368 | 128,000 | $3.52 | 現行歸檔(effort max:128k 全耗於推理,正文空白) | + +累計支出:$33.70。 diff --git a/prompts/01-02-01-merge.md b/prompts/01-02-01-merge.md deleted file mode 100644 index 1ba674b..0000000 --- a/prompts/01-02-01-merge.md +++ /dev/null @@ -1,30 +0,0 @@ -You are consolidating a vocabulary of thematic keywords that -were independently annotated on the songs of a corpus. - -Input: a JSON array of thematic keywords, deduplicated and -unordered. - -Task: merge keywords that express the same or nearly the same -theme, by your own understanding of what the keywords mean. - -Rules: - -- Every input keyword must appear in exactly one group. -- Do not invent keywords that are not in the input. -- Merge only what you consider the same theme; a group may be - a single keyword. -- Name each group with a short lowercase phrase, words joined - by hyphens, that best names the theme it gathers. Group - names must be unique. - -Do not wrap the output in a Markdown code fence. -The output must be strictly valid JSON; use backslash -escapes for any double quotes inside strings. - -Output a single JSON object mapping each group name to the -list of its member keywords, and nothing else: - -{ - "first-group-name": ["member-keyword", "another-member"], - "second-group-name": ["member-keyword"] -} diff --git a/prompts/01-03-01-cap.md b/prompts/01-03-01-cap.md deleted file mode 100644 index abf6ab9..0000000 --- a/prompts/01-03-01-cap.md +++ /dev/null @@ -1,33 +0,0 @@ -You are consolidating a vocabulary of thematic keywords -from a song corpus further, to a fixed maximum number of -themes. - -Input: a JSON array of thematic keywords, deduplicated and -unordered. - -Task: merge keywords that express the same or a similar -theme, by your own understanding of what the keywords mean, -until at most 50 groups remain. - -Rules: - -- Every input keyword must appear in exactly one group. -- Do not invent keywords that are not in the input. -- Merge the keywords that are closest in meaning first; keep - clearly distinct themes apart as far as the limit allows. - A group may be a single keyword. -- Name each group with a short lowercase phrase, words joined - by hyphens, that best names the theme it gathers. Group - names must be unique. - -Do not wrap the output in a Markdown code fence. -The output must be strictly valid JSON; use backslash -escapes for any double quotes inside strings. - -Output a single JSON object mapping each group name to the -list of its member keywords, and nothing else: - -{ - "first-group-name": ["member-keyword", "another-member"], - "second-group-name": ["member-keyword"] -} diff --git a/prompts/01-01-01-tag.md b/prompts/01-tag.md similarity index 100% rename from prompts/01-01-01-tag.md rename to prompts/01-tag.md diff --git a/prompts/02-01-01-screen.md b/prompts/02-01-01-screen.md deleted file mode 100644 index f43a102..0000000 --- a/prompts/02-01-01-screen.md +++ /dev/null @@ -1,23 +0,0 @@ -You are screening song lyrics for one specific theme. - -Input: the complete lyrics of one song. - -Task: judge whether this song expresses the theme -"women-power", by your own understanding of -that label. - -Rules: - -- If the song expresses the theme, quote 1 to 3 verbatim - lines of the lyrics that ground the judgment; if you cannot - ground it in a quote, the answer is no. - -Do not wrap the output in a Markdown code fence. -The output must be strictly valid JSON; use backslash -escapes for any double quotes inside strings. - -Output a single JSON array and nothing else: the quoted -lines when the song expresses the theme, or an empty array -when it does not: - -["quoted line", "another quoted line"] diff --git a/prompts/02-01-02-screen-arb.md b/prompts/02-01-02-screen-arb.md deleted file mode 100644 index 0631f49..0000000 --- a/prompts/02-01-02-screen-arb.md +++ /dev/null @@ -1,32 +0,0 @@ -You are arbitrating a screening of song lyrics for one -specific theme: two independent screenings of the same song -disagreed on whether it expresses the theme -"women-power". - -Input: a JSON object with the lyrics of the song and the -lyric quotes that the affirming screening gave as evidence: - -{ - "lyrics": "...", - "evidence": ["quoted line", "another quoted line"] -} - -Task: decide whether the song expresses the theme, by your -own understanding of the label, reading the lyrics and the -quoted evidence. - -Rules: - -- If the song expresses the theme, quote 1 to 3 verbatim - lines of the lyrics that ground the judgment; if you cannot - ground it in a quote, the answer is no. - -Do not wrap the output in a Markdown code fence. -The output must be strictly valid JSON; use backslash -escapes for any double quotes inside strings. - -Output a single JSON array and nothing else: the quoted -lines when the song expresses the theme, or an empty array -when it does not: - -["quoted line", "another quoted line"] diff --git a/prompts/01-04-01-code.md b/prompts/03-01-code.md similarity index 100% rename from prompts/01-04-01-code.md rename to prompts/03-01-code.md diff --git a/prompts/01-04-02-code-arb.md b/prompts/03-02-code-arb.md similarity index 100% rename from prompts/01-04-02-code-arb.md rename to prompts/03-02-code-arb.md diff --git a/runs/01-01-01-tag/run1/meta.json b/runs/01-tag/run1/meta.json similarity index 100% rename from runs/01-01-01-tag/run1/meta.json rename to runs/01-tag/run1/meta.json diff --git a/runs/01-01-01-tag/run1/output.jsonl b/runs/01-tag/run1/output.jsonl similarity index 100% rename from runs/01-01-01-tag/run1/output.jsonl rename to runs/01-tag/run1/output.jsonl diff --git a/runs/01-01-01-tag/run1/prompt.md b/runs/01-tag/run1/prompt.md similarity index 100% rename from runs/01-01-01-tag/run1/prompt.md rename to runs/01-tag/run1/prompt.md diff --git a/runs/01-01-01-tag/run2/meta.json b/runs/01-tag/run2/meta.json similarity index 100% rename from runs/01-01-01-tag/run2/meta.json rename to runs/01-tag/run2/meta.json diff --git a/runs/01-01-01-tag/run2/output.jsonl b/runs/01-tag/run2/output.jsonl similarity index 100% rename from runs/01-01-01-tag/run2/output.jsonl rename to runs/01-tag/run2/output.jsonl diff --git a/runs/01-01-01-tag/run2/prompt.md b/runs/01-tag/run2/prompt.md similarity index 100% rename from runs/01-01-01-tag/run2/prompt.md rename to runs/01-tag/run2/prompt.md