
簡介面向經濟學與人工智能交叉研究者這是一套基于RAG技術和大語言模型剖析A股上市公司年報、評估人工智能對企業(yè)綠色全要素生產率影響的統(tǒng)計建模項目資源。項目完整覆蓋了從年報數(shù)據(jù)爬取、文本清洗嵌入、LLM打分到統(tǒng)計建模與穩(wěn)健性檢驗的流程并重點納入企業(yè)融資約束異質性分析可幫助讀者快速上手將NLP前沿技術用于經濟計量研究。資源包共20個文件壓縮后約2.29MB其中7個py腳本負責年報爬取、數(shù)據(jù)轉換、模型下載與GPT評分等7個ipynb筆記分步展示數(shù)據(jù)處理、建模與可視化另含txt說明、json配置、docx和md文檔構成清晰的操作指引。已有66人學習下載適合有Python與計量基礎、希望復現(xiàn)完整研究鏈路或借鑒文本分析思路的研究者使用。1. 把年報喂給大模型之前RAG 在這條實證鏈路里解決什么問題一個做實證研究的人拿到這個 RAG 與統(tǒng)計建模結合的項目第一反應大概率是「大語言模型和綠色全要素生產率有什么關系」。關系在于你想量化企業(yè)的人工智能應用程度但年報里從來沒有一列現(xiàn)成的「AI 采用率」。人工讀年報打分不現(xiàn)實純關鍵詞匹配又會把「人工智能可能帶來風險」這種反例當證據(jù)。這個項目用 RAG 檢索增強生成把鏈路自動化——爬年報、轉文本、語義切分、向量化檢索再用大語言模型按統(tǒng)一問題模板打分把分數(shù)變成統(tǒng)計模型里的核心解釋變量跑綠色全要素生產率的基準回歸、融資約束異質性分析和穩(wěn)健性檢驗。適合正在做 ESG、綠色經濟、數(shù)字化轉型實證的碩博生也適合想把文本分析接進計量流程的從業(yè)者。2. 年報爬取與文本預處理從 PDF 到可檢索語料的三道關2.1 按概念板塊爬年報增量更新比全量重跑更省事年報數(shù)據(jù)的第一道門檻是來源。項目里 熱門概念uri爬取.ipynb 走的是按熱門概念分類拿 uri、再回源下載 PDF 的路線好處是同一概念板塊的公司天然聚在一起后面做融資約束分組、行業(yè)異質性分析時可以直接復用這份分組。如果爬取過程遇到動態(tài)渲染的頁面配套的 安裝瀏覽器驅動.txt 就是給 selenium 準備的按里面寫的瀏覽器版本號匹配下載對應驅動版本不匹配會直接報 session not created。年報爬取_final.py 在下載層做了兩件實際的事本地已存在且文件大小正常的直接跳過每次請求之間強制 sleep。這兩件事看著簡單卻是整個爬取任務能跑完的關鍵。年報 PDF 一份動輒 5MB 上下幾千家公司全量重跑一次要十幾個小時且大概率中途被限流斷連。# 年報爬取_final.py 的增量下載邏輯 import os import time import requests def download_annual_reports(stock_codes, years, save_dirreports/): os.makedirs(save_dir, exist_okTrue) failed [] for code in stock_codes: for year in years: path os.path.join(save_dir, f{code}_{year}.pdf) if os.path.exists(path) and os.path.getsize(path) 1024 * 1024: continue # 已下載且大于 1MB跳過 url fhttps://api.cninfo.example/report/{code}/{year} # 按實際公告接口替換 try: r requests.get(url, timeout30, headers{User-Agent: Mozilla/5.0}) if r.status_code 200: with open(path, wb) as f: f.write(r.content) else: failed.append((code, year)) except Exception: failed.append((code, year)) time.sleep(1) # 限速年報 PDF 體積大請求頻率過高必被斷連 return failed這段代碼的重點不是 requests 本身而是「先查本地再決定是否請求」的增量判斷和 sleep 限速。我一般會把 failed 列表落盤成 csv下次只處理失敗項成功項不再碰。項目里 年報數(shù)量統(tǒng)計.ipynb 的作用也在這——先摸清每個板塊實際有多少份年報、缺失多少再決定要不要補爬。PDF 下載完真正的活是解析文本。年報的排版千奇百怪頁眉頁腳重復表格密集還有掃描件。直接整本丟給文本解析庫目錄、封面、封底的雜訊全都會混進來后面向量化時這些噪音會平分檢索權重。2.2 PDF 轉文本先保結構頁眉頁腳和表格是最大變量年報轉換_final.py 解決 PDF 到文本的轉換。處理這類文件我一般先看 PDF 有沒有內置文本層——絕大多數(shù)上市公司年報是排版軟件生成的有文本層直接用 pdfplumber 提取即可只有早年掃描版年報才需要走 OCR 兜底。項目根目錄的 說明文件.txt 把安裝步驟和數(shù)據(jù)流程圖寫得很清楚跑之前先讀一遍能省半天時間。# 年報轉換_final.py 的解析主流程 import pdfplumber def pdf_to_pages(pdf_path): pages [] with pdfplumber.open(pdf_path) as pdf: for page in pdf.pages: text page.extract_text() pages.append({page_no: page.page_number, text: text or }) return pages def ocr_fallback(img_path): # 掃描頁兜底常見做法是接 PaddleOCR from paddleocr import PaddleOCR ocr PaddleOCR(use_angle_clsTrue, langch) result ocr.ocr(img_path, clsTrue) return \n.join([line[1][0] for line in result[0]])pdfplumber 的 extract_text 按頁面坐標順序輸出文本遇到雙欄排版會出現(xiàn)左右欄串讀。我一般會先按行 y 坐標排序合并再輸出把同一水平線上的文字拼成一行再按 y 從大到小輸出到段落里。OCR 兜底只用于 pdfplumber 提取結果為空白的頁不要整本都走 OCR——速度差兩個量級而且識別誤差會傳導到后面的檢索和打分。判定標準很簡單如果連續(xù)五頁以上 extract_text 都是空基本可以認定這份年報是掃描件直接批量走 OCR。轉出來的文本還要過一遍清洗。頁眉的「XX 公司 2023 年年度報告」、頁腳的「第 X 頁」會在切片后大量重復嚴重污染向量相似度——檢索「人工智能」時模型可能因為頁眉里出現(xiàn)過這個詞而把不相關頁面召回。清洗的坑在于不能一刀切刪除所有數(shù)字年報里「營業(yè)收入 12.3 億元」「同比增長 5.2%」是核心信息刪了就廢了。正確做法是只在每頁開頭和結尾的固定位置做規(guī)則替換正文原樣保留。這一步在項目里對應 數(shù)據(jù)處理_final.ipynb列表數(shù)據(jù)處理.ipynb 則負責把清洗后的文本組織成公司-年份兩級結構方便后續(xù)切片時追溯來源。2.3 語義切分按章節(jié)邊界切別按固定長度硬切切分是 RAG 系統(tǒng)里最容易被低估的環(huán)節(jié)。固定按 512 字符硬切會把「第三節(jié) 管理層討論與分析」從中間攔腰截斷一個完整觀點被拆成兩半后續(xù)檢索拿到的上下文是殘缺的。embedding 模型再強輸入本身缺了一段語義就不可能完整。我的習慣是先按章節(jié)標題切大塊再對超長章節(jié)按段落邊界做二次切分。# 章節(jié)感知的切分實現(xiàn) import re SECTION_RE re.compile(r^(第[一二三四五六七八九十][節(jié)章])\s*(.*)$) def split_into_sections(pages): sections [] cur {title: 報告開頭, content: []} for page in pages: for line in page[text].splitlines(): stripped line.strip() m SECTION_RE.match(stripped) if m and len(stripped) 50: # 標題行通常很短防止誤判正文 sections.append(cur) cur {title: stripped, content: []} else: cur[content].append(line) sections.append(cur) return [s for s in sections if \n.join(s[content]).strip()] def chunk_section(section, max_len800, overlap80): paras section[content] chunks, buf, buf_len [], [], 0 for p in paras: buf_len len(p) buf.append(p) if buf_len max_len: chunks.append((section[title], \n.join(buf))) buf buf[-1:] # 保留上一塊的最后一行形成輕微重疊 buf_len sum(len(x) for x in buf) if buf: chunks.append((section[title], \n.join(buf))) return chunksmax_len800 是中文年報場景里比較穩(wěn)的起點兼容主流 embedding 模型的 token 上限overlap80 的輕微重疊讓跨塊的觀點至少在一個塊里完整出現(xiàn)。關鍵點在「len(stripped) 50 才認為是標題」這個約束——年報正文里經常出現(xiàn)「第三章」這種字樣不做長度限制會把正文當標題切碎導致章節(jié)結構完全亂掉。切片時把章節(jié)標題拼在每個 chunk 開頭檢索時命中「管理層討論」和「會計附注」的文本就能區(qū)分開。提示切分參數(shù)直接影響檢索命中率。同一套代碼max_len 從 800 改到 1200top-5 召回結果可能完全不同。定稿前拿幾十個疑難問題做一次命中率抽查別等全量跑完再后悔。3. RAG 檢索增強生成大語言模型如何把年報文本變成回歸變量3.1 embedding 選型與向量入庫中文年報場景的實際選擇檢索增強的第一層是召回。項目里的 RAGGPT embedding.py 負責把上一章切好的 chunk 向量化。中文金融文本場景我一般選 bge-large-zh 這類中文 embedding 模型對年報這種滿是財務術語的文本語義區(qū)分度明顯好于通用多語言模型。config.json 里配了模型路徑、API 密鑰和輸出目錄跑之前先確認模型路徑指向有效不然 SentenceTransformer 會嘗試現(xiàn)場下載等半天才報超時。download_model.py 的作用就是提前把模型權重拉到本地——bge-large-zh 的權重有好幾個 GB建議先跑一遍確認下載完整再去做向量化。# RAGGPT embedding.py 向量化入庫 from sentence_transformers import SentenceTransformer import json MODEL_PATH ./models/bge-large-zh # 本地權重提前用 download_model.py 拉取 model SentenceTransformer(MODEL_PATH, devicecuda:0) def build_vector_store(chunks, out_pathvector_store.jsonl): with open(out_path, w, encodingutf-8) as f: for title, text in chunks: vec model.encode(text, normalize_embeddingsTrue) f.write(json.dumps( {title: title, text: text, embedding: vec.tolist()}, ensure_asciiFalse ) \n)關鍵在 normalize_embeddingsTrue。年報的 chunk 長度差異很大不歸一化的話長文本向量模長天然偏大余弦相似度會被長度主導而不是語義主導。存儲用 jsonl 而不是單個 npy 文件是因為后面要按公司、年份、章節(jié)過濾jsonl 每行自帶字段過濾時逐行讀判斷就行。檢索量在幾萬條以內jsonl 配合 numpy 矩陣暴力算余弦完全夠用到了十萬級以上我才會建議換 FAISS 或 Milvus。實證項目我傾向于少引入基礎設施能用一個文件解決的就不上服務出問題好排查。3.2 questions.json問題模板決定了打分質量的天花板召回回來的是文本塊把文本塊變成變量的是大語言模型。questions.json 是整個項目里最值得細看的文件它定義了一套問題模板每個問題都對應一個能進回歸的變量。gpt_score.py 負責執(zhí)行打分questions.json 負責約束打分行為兩者分開設計的好處是換問題集不需要動代碼。{ ai_application: { question: 根據(jù)以下年報節(jié)選判斷該公司本年度是否在生產經營中實際應用了人工智能技術, options: [0-未提及或無應用, 1-僅提戰(zhàn)略規(guī)劃或風險討論, 2-有明確的研發(fā)或生產應用], output_format: 只輸出數(shù)字編號 }, ai_investment: { question: 年報節(jié)選中是否披露了與人工智能相關的研發(fā)投入金額, options: [0-未披露, 1-披露定性描述, 2-披露具體金額], output_format: 只輸出數(shù)字編號 }, green_action: { question: 年報節(jié)選中是否披露了污染治理、碳減排相關的具體舉措或數(shù)據(jù), options: [0-未披露, 1-定性描述, 2-披露定量數(shù)據(jù)], output_format: 只輸出數(shù)字編號 } }模板設計有三個原則。第一選項必須互斥且窮盡「僅提戰(zhàn)略規(guī)劃或風險討論」這一檔專門攔截那些虛晃一槍的公司避免把「提到了 AI」當成「應用了 AI」。第二output_format 強制只輸出數(shù)字編號不然模型寫一段「該公司在智能制造領域應用了……」后處理要花大量時間做文本匹配。第三問題必須綁定節(jié)選讓模型做判斷題而不是問答題——給定證據(jù)范圍幻覺空間小得多。我在另一個項目里吃過沒約束格式的虧模型自由發(fā)揮的結果是解析腳本寫了一天最后還是要人工核對。3.3 gpt_score.py調用、解析與抽檢閉環(huán)評分腳本負責把檢索到的 top-k 塊拼進提示詞、調用模型、解析輸出、寫回變量。targets_df.py 定義目標變量的數(shù)據(jù)結構跑完評分后生成公司-年份面板直接對接回歸。try.py 是調試接口用的臨時腳本正式跑批可以忽略它。# gpt_score.py 的打分與投票邏輯 import re import openai def score_one_chunk(chunk_text, question_cfg, client): prompt f{question_cfg[question]} 年報節(jié)選 {chunk_text[:1800]} {question_cfg[output_format]} resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], temperature0, # 打分任務必須 0 溫度保證逐次結果一致 max_tokens5, # 壓到最短只允許輸出編號 ) raw resp.choices[0].message.content.strip() m re.search(r[012], raw) return int(m.group()) if m else -1 def aggregate_company_votes(chunks, cfg, client): votes [score_one_chunk(c[text], cfg, client) for c in chunks] valid [v for v in votes if v 0] if not valid: return -1 return max(set(valid), keyvalid.count) # 眾數(shù)投票temperature0 是評分任務的基本紀律否則同一份年報兩次跑出來分數(shù)不一樣后面回歸的測量誤差就無法解釋。max_tokens 壓到 5 是防止模型開始解釋它一解釋就會超長而且解釋文本里的數(shù)字可能干擾正則匹配。取眾數(shù)而不是平均是因為同一家公司不同章節(jié)里的表述經常不一致——經營討論里大談自研大模型會計附注里只字未提眾數(shù)投票比求平均更貼近多數(shù)章節(jié)的狀態(tài)。解析失敗的置為 -1 并單獨落盤后面統(tǒng)一人工處理不要悄悄跳過變成缺失值——缺失值傳導到回歸里樣本量縮水不說還可能有選擇偏誤。提示每個評分結果都要保存原始 chunk 和模型原始輸出。我在項目交付前會抽 30 條人工復核把打分和原文對一遍答辯和審稿時這就是最直接的證據(jù)。沒有原始記錄的自動打分在審稿人眼里就是一個黑匣子。4. 統(tǒng)計建模GTFP 測算、基準回歸與融資約束異質性4.1 綠色全要素生產率SBM-GML 的計算口徑GTFP 的主流測算方法是 SBM-DEA 加 GMLGlobal Malmquist-Luenberger指數(shù)。投入端是勞動、資本、能源產出端同時放進期望產出營業(yè)收入和非期望產出污染排放或碳排放用全局參比技術集計算方向距離函數(shù)得到綠色全要素生產率增長率并累計成指數(shù)。附贈資源.docx 里對這部分補充了背景資料和文獻清單算之前先把口徑定下來。# SBM-GML 方向距離函數(shù)的簡化示意 import numpy as np def sbm_directional_distance(X, Y_good, Y_bad, weights): # X: (n, k) 投入矩陣Y_good: (n, m) 期望產出Y_bad: (n, h) 非期望產出 # 完整求解需要調用線性規(guī)劃這里示意松弛變量的計算結構 n X.shape[0] slacks np.zeros(n) for i in range(n): # 每個決策單元求最大松弛量約束是投入和非期望產出可縮減、期望產出可擴張 # 實際項目中建議直接用 pystoned 或 MaxDEA 求解不要自己寫單純形 pass return slacks這段代碼我故意留成骨架——DEA 的線性規(guī)劃求解不建議手寫直接用成熟的工具包驗證結果。真正要盯的是口徑資本投入用固定資產凈值還是總資產能源消耗用披露電量還是行業(yè)均值估算污染產出選哪類污染物這些選擇會讓 GTFP 的絕對值差出數(shù)量級。GML 相對當期 ML 指數(shù)的優(yōu)勢是跨期可比。用全局參比技術集不同年份的效率值才可以直接比較否則面板回歸里時間趨勢的估計會扭曲。項目里 lightgbm.ipynb 的存在說明作者還把機器學習模型拿來做了對照這類非參數(shù)方法對非線性關系的捕捉比線性回歸靈活可以作為穩(wěn)健性證據(jù)出現(xiàn)在附錄里。我的經驗是GTFP 的絕對值本來就有一定玄學成分但同一口徑下的排序和增速是可信的論文里要把「同比增速」作為主要解釋對象而不是糾結水平值。4.2 基準回歸雙向固定效應與聚類穩(wěn)健標準誤基準模型是公司-年份雙向固定效應面板回歸被解釋變量是 GTFP核心解釋變量是大模型打分得到的 AI 應用程度控制變量取企業(yè)規(guī)模、杠桿率、成長性、企業(yè)年齡等常規(guī)集合。項目里的 TJJM24-AILearning-EconometricsWithLLM-main 文件夾可以理解為主工作區(qū)基準回歸代碼就在這里面。# 基準回歸個體時間雙向固定效應 import statsmodels.api as sm import pandas as pd def baseline_regression(df): df df.set_index([stock_code, year]) X df[[ai_score, size, leverage, growth, firm_age]].copy() X pd.concat([ X, pd.get_dummies(df.index.get_level_values(stock_code), prefixfid), pd.get_dummies(df.index.get_level_values(year), prefixyr), ], axis1) X sm.add_constant(X) model sm.OLS(df[gtfp], X).fit( cov_typecluster, cov_kwds{groups: df.index.get_level_values(stock_code)} ) return model.params[ai_score], model.tvalues[ai_score]聚類穩(wěn)健標準誤是必須的——同一公司不同年份的觀測存在序列相關不聚類 t 值會被系統(tǒng)性高估顯著結果可能只是假象。我一般按公司聚類如果樣本覆蓋期內有行業(yè)政策沖擊再按行業(yè)聚類跑一遍做對照。固定效應吸收的是公司和年份層面的不隨時間變化的異質性。AI 打分變量是 0/1/2 離散值解釋上要表述為「AI 應用程度的檔位提升對 GTFP 的影響」不要寫成彈性或百分比。跑回歸之前先看描述性統(tǒng)計ai_score 的分布如果嚴重偏向 0說明問題模板的判定標準太嚴要回過去調整選項——這種情況我遇到不止一次。4.3 融資約束異質性交互項與分組回歸的配合融資約束的異質性是標題里明確標注的研究點。兩種標準做法一是按 SA 指數(shù)中位數(shù)分組高約束組和低約束組分別跑回歸對比系數(shù)二是全樣本加交互項 ai_score × SA。分組回歸回答「不同融資約束水平下 AI 的邊際效應是否不同」交互項回答「融資約束連續(xù)變化時 AI 效應怎么變」兩者口徑不同可以互為補充。# 融資約束分組與交互項 def financing_constraint_analysis(df): df[high_fc] (df[sa_index] df[sa_index].median()).astype(int) # SA 指數(shù)為負數(shù)數(shù)值越小代表約束越大注意方向 df[ai_x_fc] df[ai_score] * df[high_fc] beta_high baseline_regression(df[df[high_fc] 1]) beta_low baseline_regression(df[df[high_fc] 0]) # 交互項全樣本回歸看 ai_x_fc 的系數(shù)是否顯著 return {high_fc_beta: beta_high, low_fc_beta: beta_low}SA 指數(shù)取的是總資產對數(shù)和企業(yè)年齡的函數(shù)年報數(shù)據(jù)直接能算不用額外采集。方向別搞反——SA 指數(shù)是負數(shù)數(shù)值越小融資約束越大分組時要按小于中位數(shù)劃高約束組。我在第一次跑這個項目時就把方向寫反過高組低組結論完全顛倒排查了很久才發(fā)現(xiàn)是符號問題。交互項的解讀要謹慎如果 ai_x_fc 系數(shù)顯著為負說明高融資約束削弱了 AI 對 GTFP 的正向作用邏輯上說得通——AI 應用需要前期投入融資約束緊的公司投不起。分組回歸的結果應該和交互項方向一致如果不一致先檢查高組和低組的樣本量是否均衡再看是不是有極端值在作怪。5. 避坑指南RAG 加實證最容易翻車的五個現(xiàn)場5.1 現(xiàn)象檢索召回的是會計附注不是管理層討論向量檢索時明明問的是人工智能應用返回的 top-1 文本塊卻是財務報表附注里「開發(fā)支出」科目的會計解釋。原因是固定長度切分把「管理層討論與分析」和后面的附注塞進了同一個 chunk語義被稀釋查「人工智能」時附注里的關鍵詞也能匹配上。解決按章節(jié)邊界切分第 2.3 節(jié)的做法把章節(jié)標題拼在 chunk 開頭檢索時給「管理層討論與分析」相關章節(jié)更高的相似度權重附注降權。我一般會在向量檢索后加一道規(guī)則過濾——如果命中的 chunk 標題屬于「財務報表附注」直接跳過取下一個非附注結果。管理層討論與分析才是描述戰(zhàn)略和實際業(yè)務的核心章節(jié)附注里的會計術語對變量打分幾乎沒有貢獻。5.2 現(xiàn)象大模型輸出帶解釋正則匹配到錯誤的數(shù)字提示詞明明寫了「只輸出數(shù)字編號」模型偶爾輸出「2該公司有實質應用」。如果 max_tokens 設得太大模型有空間補解釋后處理時 re.search(r[012]) 匹配到的第一個數(shù)字可能不是答案。解決把 max_tokens 壓到 5提示詞里加「不要輸出任何解釋」后處理先按「」切分再取末位。仍失敗的置為 -1單獨人工處理。還有一類情況是 API 版本更新后模型行為變化同一段提示詞在舊版輸出「2」新版輸出「2?!苟嗔艘粋€句號正則沒問題但字符串比對會翻車。所以解析邏輯里不要做精確字符串匹配一律走正則取編號。5.3 現(xiàn)象年報里的「人工智能」根本不是技術應用年報常見表述是「人工智能技術的發(fā)展可能對公司業(yè)務產生不利影響」或「公司不涉及人工智能相關業(yè)務」這種句子按關鍵詞詞頻法會被計成 AI 應用證據(jù)系統(tǒng)性高估應用率。我見過有人用詞頻跑出來全市值前十大公司全部「應用了 AI」人工一翻全是風險提示。解決問題模板里把判斷標準從「是否提及」改成「是否在生產經營中實際應用」并增設「僅提戰(zhàn)略規(guī)劃或風險討論」一檔。這樣模型就不會把風險提示當成應用證據(jù)。人工抽檢時重點看負例樣本確認模型沒有把所有「提及」都打成「應用」。5.4 現(xiàn)象GTFP 量級和公開文獻對不上差出十倍SBM-GML 的測算結果高度依賴投入產出口徑。資本用固定資產凈值還是總資產、能源用披露值還是行業(yè)均值、污染產出選哪些指標都會讓結果發(fā)生數(shù)量級變化。這不算程序 bug而是口徑選擇問題。解決確定口徑后不要中途更換論文里完整披露指標定義。用公開數(shù)據(jù)集做排序校驗——絕對量不可比但各家公司的效率排序應大致一致。我一般會拿同一個行業(yè)里 5 家知名公司的人工排序和模型排序做 Spearman 相關系數(shù)低于 0.6 就說明測算有問題需要回頭檢查數(shù)據(jù)。5.5 現(xiàn)象面板錯位公司代碼和年份對不上年報文件名是「代碼_年份.pdf」但公司可能變更證券代碼年報披露日和報告期末也可能跨年——2023 年年報在 2024 年 4 月披露如果用公告日期定義年份面板時間維度會整體偏移。解決從 PDF 首頁解析報告期末日期用報告期年份作為面板的 year 字段。數(shù)據(jù)預處理最后加斷言每家公司年份序列連續(xù)無重復。# 面板完整性自檢發(fā)現(xiàn)錯位的第一道防線 def validate_panel(df): for code, grp in df.groupby(stock_code): years sorted(grp[year].unique()) assert years list(range(min(years), max(years) 1)), \ f{code} 年份不連續(xù): {years} assert len(grp) len(years), f{code} 存在重復年份 print(面板校驗通過所有公司年份連續(xù)無重復)這個斷言放在數(shù)據(jù)預處理最后一步比放在回歸之前更合適——回歸報錯時你還要回頭查是哪家公司出了問題前置斷言直接告訴你。跑批時把 year 字段統(tǒng)一成 int 類型別讓字符串和整數(shù)混著比大小這種隱晦的錯位最耗時間。6. 穩(wěn)健性檢驗與復現(xiàn)驗證換口徑、換樣本、換估計量6.1 核心解釋變量的替換與對照最直接的穩(wěn)健性檢驗是把 AI 應用打分替換成基于同一套檢索文本的關鍵詞加權頻次。對每家公司年報的「管理層討論與分析」部分統(tǒng)計「人工智能」「機器學習」「深度學習」「大模型」的標準化出現(xiàn)頻次重新跑基準回歸。如果核心系數(shù)符號和顯著性不變說明結論不是某一特定打分口徑的偶然產物。6.2 工具變量對內生性做一次正面回應人工智能應用程度和 GTFP 之間存在反向因果的可能——生產率高的企業(yè)更有余力部署 AI。常見做法是用行業(yè)平均 AI 應用程度作為工具變量或者用滯后一期的 AI 打分。# 兩階段最小二乘行業(yè)均值作為工具變量 from linearmodels.iv import IV2SLS def run_iv(df): df df.copy() df[const] 1.0 df[iv_industry_ai] df.groupby([industry, year])[ai_score].transform(mean) model IV2SLS( dependentdf[gtfp], exogdf[[const, size, leverage, growth, firm_age]], endogdf[[ai_score]], instrumentsdf[[iv_industry_ai]], ).fit() return model行業(yè)均值這個工具變量的邏輯是同行的 AI 應用會通過競爭和示范效應影響本公司但本公司的 GTFP 不會直接決定行業(yè)均值。跑完看第一階段 F 值大于 10 是底線小于 10 說明工具變量弱結果不可信。6.3 交付前的復現(xiàn)檢查清單項目收尾時我會把這張清單完整過一遍檢查項具體做法不過關的處理檢索命中質量抽 50 個 chunk 人工判斷召回文本是否貼合問題調整切分策略重新向量化打分可復現(xiàn)性同一份年報間隔一天跑兩次打分對比一致性確認 temperature0檢查 API 版本變化GTFP 排序校驗與公開文獻或人工排序做 Spearman 相關檢查投入產出口徑面板完整性運行 validate_panel 斷言回補缺失年報回歸敏感性換聚類層級、換控制變量集合對比核心系數(shù)在論文中如實報告敏感度這套清單的出發(fā)點不是「證明結果一定對」而是「換個合理設定結果不會反轉」。從那以后我每次跑類似項目都強制走一遍這個流程——先跑清單再寫結論寫完結論再抽檢原始文本。RAG 這條鏈路的黑匣子在于中間每個環(huán)節(jié)都有信息損耗你控制得越細審稿人問起來越不慌。希望幫到你。本文還有配套的精品資源點擊獲取