Compare commits
9
Commits
2f3fc78f7f
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
502d8b6b5a | ||
|
|
47bef84e7b | ||
|
|
9b6450fcbb | ||
|
|
309667e82b | ||
|
|
195605826f | ||
|
|
e569b3481c | ||
|
|
f0547208ed | ||
|
|
eef434acf7 | ||
|
|
7a625d50fc |
@@ -1,6 +1,6 @@
|
||||
# 流行音樂中「女性力量」語彙的挪用與污染——以 Billboard Year-End Hot 100(2016-2025)為例的內容分析
|
||||
|
||||
這是研究論文《流行音樂中「女性力量」語彙的挪用與污染——以 Billboard Year-End Hot 100(2016-2025)為例的內容分析》的專案資料,包括論文本身、文件、紀錄、資料、工具程式、AI提示詞,等等。
|
||||
這是研究論文《流行音樂中「女性力量」語彙的挪用與污染——以 Billboard Year-End Hot 100(2016-2025)為例的內容分析》的專案資料,包括論文本身、文件、紀錄、資料、工具程式、LLM提示詞,等等。
|
||||
|
||||
## 論文和摘要
|
||||
|
||||
@@ -10,9 +10,17 @@
|
||||
|
||||
研究方法、記錄等文件,請參閱 docs/ 資料夾。
|
||||
|
||||
## LLM提示
|
||||
|
||||
LLM提示請參閱 prompts/ 資料夾。
|
||||
|
||||
## 輔助工具程式
|
||||
|
||||
輔助工具程式,請參閱 tools/ 資料夾。
|
||||
輔助工具程式請參閱 tools/ 資料夾。
|
||||
|
||||
## 研究資料
|
||||
|
||||
研究資料請參閱 data/ 資料夾。
|
||||
|
||||
## 授權
|
||||
|
||||
|
||||
|
Can't render this file because it is too large.
|
+9
-6
@@ -31,11 +31,11 @@ agent 不得見之)。凡於本專案工作的主會話,動手管線、
|
||||
不符預期時,修訂定義檔並重複該循環;絕不手改結果。
|
||||
- 一步的每次執行皆自我完備歸檔於 `run-llm` 命令列上
|
||||
明示指定的目的目錄下(慣例為
|
||||
`runs/<步驟>/run<N>/`):定義檔快照、原始輸出與
|
||||
`data/runs/<步驟>/run<N>/`):定義檔快照、原始輸出與
|
||||
`meta.json`(model ID、參數、時間戳、batch ID)。
|
||||
一步的 N 次執行即 N 次各自的 `run-llm` 呼叫。覆蓋
|
||||
既有執行歸檔須明示旗標;被取代的執行留在 git 歷史。
|
||||
確定性步驟歸檔於 `runs/<步驟>/`,不分 `run<N>` 層。
|
||||
確定性步驟歸檔於 `data/runs/<步驟>/`,不分 `run<N>` 層。
|
||||
- 每次 `run-llm` 執行的 token 用量與費用記入
|
||||
`docs/run-costs.md`,與執行歸檔同一 commit。
|
||||
- 腳本自環境變數 `ANTHROPIC_API_KEY` 讀取 API key
|
||||
@@ -46,19 +46,22 @@ agent 不得見之)。凡於本專案工作的主會話,動手管線、
|
||||
- `data/source/` 存手放後不動的原始檔;
|
||||
`data/captures/` 只由 fetch 命令與私人匯入腳本
|
||||
寫入;`data/manual/` 只由研究者親手寫入;
|
||||
`data/derived/` 只由 `build-db` 子命令寫入。
|
||||
`data/derived/` 只由 `build-db` 子命令寫入;
|
||||
`data/runs/` 存 LLM 執行的原始歸檔,只由執行程序
|
||||
寫入;`data/results/` 存論文引用的定案表,只由
|
||||
計票程序寫入。
|
||||
- 歌詞全文有版權:一律置於 `data/captures/lyrics/`
|
||||
(gitignored),絕不 commit,亦絕不於 repo 任何處
|
||||
全文重現。
|
||||
|
||||
## 文件
|
||||
|
||||
- `results/` 存計票後的定案表(論文所引);`runs/` 存
|
||||
原始稽核紀錄。論文只引 `results/`。
|
||||
- `data/results/` 存計票後的定案表(論文所引);
|
||||
`data/runs/` 存原始稽核紀錄。論文只引 `data/results/`。
|
||||
- **Commit 判準**:凡能由「committed 的輸入+committed
|
||||
的程式」決定性再生者不 commit;凡不能者一律以文字
|
||||
格式 commit,格式跟著上文「資料規則」一節所定的
|
||||
層次走。例外:`results/` 定案表與
|
||||
層次走。例外:`data/results/` 定案表與
|
||||
`data/derived/` 人讀報表雖可再生仍 commit——理由是
|
||||
引用穩定性、審稿人零門檻、撰稿期數字變動可 diff;
|
||||
兩者皆與工作儲存同一動作產出,稽核鏈無中間空缺。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# LLM 輸出的契約查核
|
||||
|
||||
(2026-08-06 量測。對象為步驟 3 的三份執行歸檔
|
||||
`runs/3a-code/run1`–`run3`,共 883 首歌、44,149 筆標籤
|
||||
`data/runs/3a-code/run1`–`run3`,共 883 首歌、44,149 筆標籤
|
||||
指派、44,146 句引述。歌詞以模型實際看到的那一份為準,即
|
||||
`tools/instance/llm-input-code.jsonl`。)
|
||||
|
||||
|
||||
+14
-29
@@ -42,52 +42,37 @@ frame-aware 提示修正實驗,均由當時協作的 Claude Code 設計並
|
||||
研究問題。
|
||||
- **粒度選擇**:正式研究鎖定 thematic keywords 這一粒度,
|
||||
係繼承先導研究三種粒度的比較結果(keywords 過碎、themes
|
||||
過早抽象),且於執行前鎖定以防事後擇優。
|
||||
過早抽象)。
|
||||
- **`women-power` 一詞的來歷**:考據先導研究的 local agent
|
||||
存檔可知,其第一步指令含數十個範例 thematic keywords,
|
||||
其中即有 women-power——為當時協作的 Claude Code 依研究者
|
||||
長期表達的關注主動加入(研究者端播種,非明示指定);先導
|
||||
的標籤 `women-power-and-empowerment` 則是第三步強制合併
|
||||
兩個關鍵字的管線人工產物。正式研究的探針因此指向被播種的
|
||||
本詞 `women-power`,不用合併假影(見 `decision-log.md`
|
||||
2026-08-04 條)。**附帶認清:先導第一步並非零語意提示,
|
||||
此即正式研究「提示詞只定格式、不定語意」設計所矯正者。**
|
||||
兩個關鍵字的管線人工產物(見 `decision-log.md` 2026-08-04
|
||||
條)。
|
||||
- **簿記容量的教訓**:先導研究九百餘詞可以在單一回應內完成
|
||||
分組,正式研究的關鍵字規模已非此法所能及,詞彙表遂改由
|
||||
確定性程序產生(見 `decision-log.md` 2026-08-06 條)。
|
||||
分組;此法未沿用,詞彙表改由確定性程序產生(見
|
||||
`decision-log.md` 2026-08-06 條)。
|
||||
|
||||
## 四、被棄用的成果
|
||||
|
||||
以下先導研究的產物未進入正式研究,論文亦未引用:
|
||||
|
||||
- **genuine/peripheral/fake 三分類與「44% 假女性力量」**:
|
||||
構念與分母皆與正式研究不同(正式研究為 145 首女性力量群
|
||||
歌曲中 111 首有性別問題),兩者不可對讀。
|
||||
- **五種「假女性力量」類型(A–E)**:正式研究改由儀器分三個
|
||||
發話脈絡各自歸納,得 43 條問題樣態(`results/patterns.csv`)。
|
||||
- **pussy 一詞的修辭分類**:正式研究範圍收斂至語彙的挪用與
|
||||
污染,未納入。
|
||||
構念與分母均未經稽核回溯,未沿用。
|
||||
- **五種「假女性力量」類型(A–E)**:未沿用。
|
||||
- **pussy 一詞的修辭分類**:未沿用。
|
||||
- **frame-aware(框架感知)提示修正法**:先導研究以框架判準
|
||||
寫進提示以提高準確率;正式研究刻意不定義編碼,因為研究
|
||||
對象正是 LLM 未受引導的自然編碼——把框架寫進提示,即無法
|
||||
再以其輸出為批判對象。兩者的設計方向相反,故未沿用。
|
||||
- **三個獨立 LLM subagent+人工仲裁的流程**:正式研究改以
|
||||
Anthropic API 逐首獨立呼叫,原因見 `decision-log.md`
|
||||
2026-07-30 條。
|
||||
寫進提示以提高準確率;未沿用。
|
||||
- **三個獨立 LLM subagent+人工仲裁的流程**:未沿用,原因見
|
||||
`decision-log.md` 2026-07-30 條。
|
||||
|
||||
## 五、它如何促成正式研究的設計
|
||||
## 五、它促成了可稽核的設計
|
||||
|
||||
先導研究的根本限制不在結論對錯,而在**不可稽核**:沒有定義檔
|
||||
快照、沒有原始輸出歸檔、沒有參數紀錄,因此任何一個數字都無法
|
||||
回溯到產生它的那一次執行。正式研究的幾項設計正是針對這一點:
|
||||
|
||||
- 所有 LLM 步驟改以 API script 執行,每次執行自我完備歸檔於
|
||||
`runs/`;
|
||||
- 可逐項機械比對的判斷採三次執行+多數決,並量測其穩定性
|
||||
(見 `reliability.md`);
|
||||
- 定義檔只規定任務形狀,不給主題定義、判準或範例;
|
||||
- 每一筆編碼必附歌詞引述,供逐筆查核;
|
||||
- 輸出的契約遵從另行查核(見 `output-validation.md`)。
|
||||
回溯到產生它的那一次執行。正式研究的可稽核設計,正是針對這一點
|
||||
而立。
|
||||
|
||||
換言之,正式研究對 LLM 輸出所採取的「不信任、須查核」立場,
|
||||
其第一個案例就是先導研究本身。
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
# Authors:
|
||||
# imacat@mail.imacat.idv.tw (imacat), 2026/7/31
|
||||
|
||||
# The SQLAlchemy database URL.
|
||||
SQLALCHEMY_DATABASE_URL="postgresql://user:password@host/db"
|
||||
# The SQLAlchemy database URI.
|
||||
SQLALCHEMY_DATABASE_URI="postgresql://user:password@host/db"
|
||||
# The Anthropic API key
|
||||
ANTHROPIC_API_KEY=sk-ant-...
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
Change Log
|
||||
==========
|
||||
|
||||
|
||||
version 1.0.0
|
||||
-------------
|
||||
|
||||
Released 2026/8/19
|
||||
|
||||
Initial release.
|
||||
@@ -13,6 +13,8 @@ This is a collection of supporting tools for the conference paper "流行音樂
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
changelog
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"""Tools for A Feminist Audit of Pop Music."""
|
||||
|
||||
|
||||
VERSION: str = "0.0.0"
|
||||
VERSION: str = "1.0.0"
|
||||
"""The package version."""
|
||||
|
||||
@@ -14,12 +14,12 @@ class Settings(BaseSettings):
|
||||
"""The application name."""
|
||||
admin_email: str = "imacat@mail.imacat.idv.tw"
|
||||
"""The administrator email address."""
|
||||
SQLALCHEMY_DATABASE_URL: str
|
||||
SQLALCHEMY_DATABASE_URI: str
|
||||
"""The SQLAlchemy database URL."""
|
||||
ANTHROPIC_API_KEY: str
|
||||
"""The Anthropic API key."""
|
||||
|
||||
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
||||
model_config = SettingsConfigDict(env_file=".env")
|
||||
"""The model configuration."""
|
||||
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
"""
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.engine.interfaces import DBAPICursor, DBAPIConnection
|
||||
from sqlalchemy.orm import DeclarativeBase, sessionmaker, Session
|
||||
from sqlalchemy.pool import ConnectionPoolEntry
|
||||
|
||||
from .config import Settings, get_settings
|
||||
|
||||
@@ -29,7 +30,7 @@ class DataSource:
|
||||
:return: The database engine.
|
||||
"""
|
||||
settings: Settings = get_settings()
|
||||
return self.__create_engine(settings.SQLALCHEMY_DATABASE_URL)
|
||||
return self.__create_engine(settings.SQLALCHEMY_DATABASE_URI)
|
||||
|
||||
@cached_property
|
||||
def __session_local(self) -> sessionmaker:
|
||||
@@ -51,9 +52,6 @@ class DataSource:
|
||||
def __create_engine(cls, url: str) -> sa.Engine:
|
||||
"""Constructs and returns the database engine.
|
||||
|
||||
The foreign key enforcement is enabled on every connection
|
||||
of a SQLite engine.
|
||||
|
||||
:param url: The SQLAlchemy database URL.
|
||||
:return: The database engine.
|
||||
"""
|
||||
@@ -65,24 +63,10 @@ class DataSource:
|
||||
poolclass=sa.StaticPool)
|
||||
else:
|
||||
engine = sa.create_engine(url)
|
||||
if engine.url.get_backend_name() == "sqlite":
|
||||
sa.event.listen(engine, "connect",
|
||||
cls.__enable_sqlite_foreign_keys)
|
||||
if engine.dialect.name == "sqlite":
|
||||
cls.__enable_sqlite_foreign_keys(engine)
|
||||
return engine
|
||||
|
||||
@staticmethod
|
||||
def __enable_sqlite_foreign_keys(dbapi_connection: Any,
|
||||
_: Any) -> None:
|
||||
"""Enables the foreign key enforcement on a new connection.
|
||||
|
||||
:param dbapi_connection: The DBAPI connection.
|
||||
:param _: The connection record (unused).
|
||||
:return: None.
|
||||
"""
|
||||
cursor: Any = dbapi_connection.cursor()
|
||||
cursor.execute("PRAGMA foreign_keys=ON")
|
||||
cursor.close()
|
||||
|
||||
@staticmethod
|
||||
def __resolve_sqlite_relative_url(url: str) -> str:
|
||||
"""Resolves the SQLite relative URL to the instance folder.
|
||||
@@ -101,6 +85,31 @@ class DataSource:
|
||||
path = base / "instance" / path
|
||||
return f"sqlite:///{path}"
|
||||
|
||||
@staticmethod
|
||||
def __enable_sqlite_foreign_keys(engine: sa.Engine) -> None:
|
||||
"""Turns on the foreign key enforcement of SQLite.
|
||||
|
||||
The ``foreign_keys`` pragma is turned on for every
|
||||
connection of the engine, so that the ``ON DELETE``
|
||||
actions of the schema run.
|
||||
|
||||
:param engine: The SQLite database engine.
|
||||
:return: None.
|
||||
"""
|
||||
def on_connect(dbapi_connection: DBAPIConnection,
|
||||
_: ConnectionPoolEntry) -> None:
|
||||
"""Turns on the pragma on a new connection.
|
||||
|
||||
:param dbapi_connection: The DB-API connection.
|
||||
:param _: The connection record (unused).
|
||||
:return: None.
|
||||
"""
|
||||
cursor: DBAPICursor = dbapi_connection.cursor()
|
||||
cursor.execute("PRAGMA foreign_keys=ON")
|
||||
cursor.close()
|
||||
|
||||
sa.event.listen(engine, "connect", on_connect)
|
||||
|
||||
|
||||
ds: DataSource = DataSource()
|
||||
"""The data source."""
|
||||
|
||||
@@ -278,7 +278,7 @@ class TestBuildDB(unittest.TestCase):
|
||||
self.__annotations: Path = self.__dir / "annotations.csv"
|
||||
self.__write_chart(self.CHART_CSV)
|
||||
config.set_settings(config.Settings(
|
||||
SQLALCHEMY_DATABASE_URL="sqlite://",
|
||||
SQLALCHEMY_DATABASE_URI="sqlite://",
|
||||
ANTHROPIC_API_KEY="test-key"))
|
||||
self.__ds: DataSource = DataSource()
|
||||
self.addCleanup(self.__ds.engine.dispose)
|
||||
|
||||
@@ -36,7 +36,7 @@ class TestExportLlmInput(unittest.TestCase):
|
||||
self.__dir: Path = Path(tmp.name)
|
||||
self.__output: Path = self.__dir / "llm-input.jsonl"
|
||||
config.set_settings(config.Settings(
|
||||
SQLALCHEMY_DATABASE_URL="sqlite://",
|
||||
SQLALCHEMY_DATABASE_URI="sqlite://",
|
||||
ANTHROPIC_API_KEY="test-key"))
|
||||
self.__ds: DataSource = DataSource()
|
||||
self.addCleanup(self.__ds.engine.dispose)
|
||||
|
||||
@@ -45,7 +45,7 @@ class TestFetchArtists(unittest.TestCase):
|
||||
self.__snapshot: Path = \
|
||||
self.__dir / "artists_wikidata.csv"
|
||||
config.set_settings(config.Settings(
|
||||
SQLALCHEMY_DATABASE_URL="sqlite://",
|
||||
SQLALCHEMY_DATABASE_URI="sqlite://",
|
||||
ANTHROPIC_API_KEY="test-key"))
|
||||
self.__ds: DataSource = DataSource()
|
||||
self.addCleanup(self.__ds.engine.dispose)
|
||||
|
||||
@@ -44,7 +44,7 @@ class TestFetchLyrics(unittest.TestCase):
|
||||
self.__provenance: Path = \
|
||||
self.__dir / "lyrics-provenance.csv"
|
||||
config.set_settings(config.Settings(
|
||||
SQLALCHEMY_DATABASE_URL="sqlite://",
|
||||
SQLALCHEMY_DATABASE_URI="sqlite://",
|
||||
ANTHROPIC_API_KEY="test-key"))
|
||||
self.__ds: DataSource = DataSource()
|
||||
self.addCleanup(self.__ds.engine.dispose)
|
||||
|
||||
@@ -27,7 +27,7 @@ class TestModels(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
"""Create the schema on an in-memory SQLite database."""
|
||||
config.set_settings(config.Settings(
|
||||
SQLALCHEMY_DATABASE_URL="sqlite://",
|
||||
SQLALCHEMY_DATABASE_URI="sqlite://",
|
||||
ANTHROPIC_API_KEY="test-key"))
|
||||
self.__ds: DataSource = DataSource()
|
||||
self.addCleanup(self.__ds.engine.dispose)
|
||||
|
||||
@@ -188,7 +188,8 @@ class TestRequestBuilding(RunLLMTestCase):
|
||||
:return: The parsed request.
|
||||
"""
|
||||
stdout: io.StringIO = io.StringIO()
|
||||
with redirect_stdout(stdout):
|
||||
stderr: io.StringIO = io.StringIO()
|
||||
with redirect_stdout(stdout), redirect_stderr(stderr):
|
||||
run_llm.main([
|
||||
str(self.__prompt), str(self.__input),
|
||||
str(self.__archive_dir), "--dry-run"] + extra_argv)
|
||||
@@ -239,7 +240,7 @@ class TestMainFlow(RunLLMTestCase):
|
||||
str(self.__prompt), str(self.__input),
|
||||
str(self.__archive_dir)]
|
||||
self.__settings: config.Settings = config.Settings(
|
||||
SQLALCHEMY_DATABASE_URL="sqlite://",
|
||||
SQLALCHEMY_DATABASE_URI="sqlite://",
|
||||
ANTHROPIC_API_KEY="test-key")
|
||||
config.set_settings(self.__settings)
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class TestTallyAnnotations(unittest.TestCase):
|
||||
run_dir.mkdir()
|
||||
self.__runs.append(run_dir)
|
||||
config.set_settings(config.Settings(
|
||||
SQLALCHEMY_DATABASE_URL="sqlite://",
|
||||
SQLALCHEMY_DATABASE_URI="sqlite://",
|
||||
ANTHROPIC_API_KEY="test-key"))
|
||||
self.__ds: DataSource = DataSource()
|
||||
self.addCleanup(self.__ds.engine.dispose)
|
||||
|
||||
@@ -39,7 +39,7 @@ class TestTallyCodings(unittest.TestCase):
|
||||
self.__output_csv: Path \
|
||||
= self.__dir / "results" / "codings.csv"
|
||||
config.set_settings(config.Settings(
|
||||
SQLALCHEMY_DATABASE_URL="sqlite://",
|
||||
SQLALCHEMY_DATABASE_URI="sqlite://",
|
||||
ANTHROPIC_API_KEY="test-key"))
|
||||
self.__ds: DataSource = DataSource()
|
||||
self.addCleanup(self.__ds.engine.dispose)
|
||||
|
||||
Reference in New Issue
Block a user