资讯详情

资讯详情

基于RAG与LLM的上市公司年报绿色全要素生产率抽取与实证分析

简介本资源面向经济学、统计学与数据科学方向的研究者及高年级学生提供一套将RAG技术与大语言模型应用于A股上市公司年报分析的完整统计建模方案核心议题是评估人工智能对企业绿色全要素生产率GTFP的影响并纳入企业融资约束的异质性分析与稳健性检验。压缩包共20个文件约2.29MB以7个py脚本和7个ipynb笔记本为主体覆盖年报爬取、文本转换、embedding生成、GPT打分与LightGBM建模等环节另含2个txt说明、2个json配置、1个docx附赠材料与1个md说明便于按流程复现。已有67人学习下载。读者可据此掌握从年报数据采集、RAG检索增强到计量建模与结果可视化的完整链路理解融资约束异质性处理与稳健性检验的实现思路并获得可复用的脚本与配置模板适合作为实证研究或课程项目的参考起点。1. 年报里挖出真问题RAG 加 LLM 怎么把绿色全要素生产率算明白A股上市公司年报动辄两三百页里面藏着能源消耗、环保投入、碳排放口径、研发人员结构这些关键字段但格式千差万别PDF 表格跨页、脚注藏在页脚、管理层讨论里还夹着定性描述。想用这些数据评估人工智能对企业绿色全要素生产率的影响靠人工摘录一个季度都跑不完一个行业。RAG 技术与大语言模型在这里的价值不是让模型“读一遍就懂”而是把非结构化年报切块、向量化、按问题召回再让模型在受控上下文里做字段抽取和口径对齐。这套统计建模项目适合两类人一类是做 ESG 或绿色金融实证的研究者手里有面板数据但缺文本变量另一类是做企业数字化转型的从业者想量化 AI 投入对绿色效率的传导路径。核心难点不在模型调用而在“召回什么、怎么对齐口径、怎么让融资约束异质性分析站得住”。2. 从年报 PDF 到可回归面板RAG 抽取管线的四个关键环节2.1 为什么不能直接全文塞进大语言模型主流大语言模型的上下文窗口虽然已经到 128K 甚至更长但年报里真正和绿色全要素生产率相关的段落可能只占 5%。全文塞进去有三个后果一是 token 成本随公司数量线性膨胀几百家公司跑一轮就不可控二是长上下文里模型对中间段落的注意力衰减明显关键数字容易被忽略三是无法做字段级校验模型输出一段话你没法定位它到底从哪一页抄来的。RAG 的思路是把年报先切成语义块用嵌入模型建索引查询时只召回最相关的 top-k 块再让模型在这些块里做抽取。这样每个字段都能回溯到原文页码后续稳健性检验时也能人工抽检。常见做法是先用 PDF 解析库把年报转成带页码的文本再按标题层级和段落长度切块块大小控制在 400 到 800 字之间重叠 80 到 120 字。切得太碎会丢上下文切得太大会引入噪声。嵌入模型选中文语义强的比如 BGE 系列或 M3E不要用纯英文模型硬套。2.2 年报解析与切块把 PDF 变成带页码的语义块下面这段代码用 Python 做 PDF 解析和切块核心是把每一页文本保留页码元数据再按段落合并成块。实际跑的时候扫描版年报需要先走 OCR这里假设是文本层可提取的 PDF。import fitz # PyMuPDF from langchain.text_splitter import RecursiveCharacterTextSplitter def parse_annual_report(pdf_path): doc fitz.open(pdf_path) pages [] for page_num in range(len(doc)): page doc[page_num] text page.get_text(text) # 保留页码后续召回要回溯 pages.append({page: page_num 1, text: text}) return pages def chunk_with_page_meta(pages, chunk_size600, overlap100): splitter RecursiveCharacterTextSplitter( chunk_sizechunk_size, chunk_overlapoverlap, separators[\n\n, \n, 。, , , ] ) chunks [] for p in pages: for seg in splitter.split_text(p[text]): # 每个块都带上页码方便溯源 chunks.append({page: p[page], content: seg}) return chunks pages parse_annual_report(sample_annual_report.pdf) chunks chunk_with_page_meta(pages) print(f总块数: {len(chunks)}首页块示例: {chunks[0][page]})逻辑说明parse_annual_report逐页提取文本并保留页码chunk_with_page_meta用递归切分器按中文标点优先切保证语义完整。参数上chunk_size600是经验值年报里一段管理层讨论通常在这个量级overlap100防止关键句被切断。如果年报表格多建议额外用pdfplumber抽表格单独存成结构化字段不要指望文本切块能还原表格。2.3 向量索引与召回让模型只看到该看的段落建索引时嵌入模型的选择直接决定召回质量。中文年报里“绿色全要素生产率”可能被写成“绿色效率”“环境绩效”“碳强度”所以查询侧要做同义扩展。下面用 FAISS 建索引并做召回。from sentence_transformers import SentenceTransformer import faiss import numpy as np # 用中文语义模型不要用英文模型 model SentenceTransformer(BAAI/bge-base-zh-v1.5) texts [c[content] for c in chunks] embeddings model.encode(texts, normalize_embeddingsTrue) dim embeddings.shape[1] index faiss.IndexFlatIP(dim) # 内积相似度配合归一化等于余弦 index.add(np.array(embeddings, dtypefloat32)) def retrieve(query, top_k5): q_emb model.encode([query], normalize_embeddingsTrue) scores, idxs index.search(np.array(q_emb, dtypefloat32), top_k) return [(chunks[i][page], chunks[i][content], float(s)) for s, i in zip(scores[0], idxs[0])] results retrieve(公司人工智能技术应用与绿色全要素生产率的关系) for page, content, score in results: print(f页码 {page} 相似度 {score:.3f}: {content[:80]}...)逻辑说明normalize_embeddingsTrue让向量归一化内积等价于余弦相似度。top_k5是召回块数太少会漏字段太多会引入噪声。实际调参时建议对 20 份年报做人工标注看 top-5 里包含目标字段的比例低于 80% 就要换嵌入模型或调整切块策略。查询语句不要只写一个词要把研究问题拆成“AI 投入”“绿色专利”“能耗强度”等多个查询分别召回后合并去重。2.4 字段抽取与口径对齐让 LLM 输出结构化 JSON召回之后用大语言模型做字段抽取。关键是给模型一个严格的输出格式并要求它标注来源页码。下面用提示词模板加 JSON 解析。import json EXTRACT_PROMPT 你是一个年报字段抽取助手。根据以下上下文抽取指定字段。 上下文 {context} 请输出 JSON字段包括 - ai_investment: 人工智能相关投入描述没有则填 null - green_patent_count: 绿色专利数量没有则填 null - energy_intensity: 能耗强度或单位产值能耗没有则填 null - source_page: 上述信息所在页码 只输出 JSON不要解释。 def extract_fields(query, top_k5): retrieved retrieve(query, top_k) context \n.join([f[第{r[0]}页] {r[1]} for r in retrieved]) prompt EXTRACT_PROMPT.format(contextcontext) # 这里调用你本地或API的大语言模型 response call_llm(prompt) try: return json.loads(response) except json.JSONDecodeError: return {error: 解析失败, raw: response} result extract_fields(人工智能投入 绿色专利 能耗强度) print(json.dumps(result, ensure_asciiFalse, indent2))逻辑说明提示词里明确字段名和 null 规则避免模型编造。source_page强制模型回溯后续人工抽检时直接翻页核对。call_llm是占位函数实际用哪个模型不影响流程。参数上温度设 0 到 0.2保证输出稳定如果模型返回带 markdown 代码块要先剥离再解析。抽取结果要落库每个公司一行字段缺失用 null不要用 0 填充否则回归时会把缺失当真实值。3. 绿色全要素生产率的测算与 AI 变量的构造别让指标打架3.1 绿色全要素生产率的两种测算路径绿色全要素生产率不是年报里直接披露的数字需要自己算。常见做法有两类一是参数法用随机前沿分析把能源投入和碳排放作为非期望产出纳入生产函数二是非参数法用包含非期望产出的方向性距离函数加 Malmquist 指数。前者对函数形式敏感后者对异常值敏感。我一般会两种都跑一遍看结论是否一致这就是稳健性检验的一部分。数据来源上投入端需要员工人数、固定资产、能源消耗产出端需要营业收入和碳排放。碳排放如果年报没披露可以用能耗折算但要在论文里写清折算系数来源。AI 变量则来自 RAG 抽取结果比如 AI 专利数量、AI 相关研发投入占比、年报中 AI 词频。词频要做行业标准化否则金融业和制造业不可比。3.2 融资约束异质性分析的分组逻辑融资约束异质性分析的核心是分组回归。常见分组变量有 SA 指数、KZ 指数、WW 指数或者用产权性质、企业规模做代理。SA 指数只用企业规模和年龄外生性较好我一般优先用。分组后做组间系数差异检验不要只看两组显著性要用费舍尔组合检验或自抽样法看系数差异是否显著。这里有个坑AI 变量和融资约束可能互为因果。AI 投入大的企业更容易拿到绿色信贷融资约束反而低。所以回归时要滞后一期 AI 变量或者用工具变量。常见工具变量有同行业同地区其他企业的 AI 投入均值但排他性要论证清楚。3.3 控制变量与固定效应的选择控制变量至少包括企业规模、资产负债率、净资产收益率、企业年龄、股权集中度、董事会规模。固定效应建议同时控制个体和时间行业随时间趋势也要考虑。如果样本是 2015 到 2023 年时间固定效应能吸收宏观政策冲击。标准误要聚类到企业层面否则 t 值虚高。下面是一个基准回归的代码框架用 statsmodels 做面板固定效应。import pandas as pd import statsmodels.formula.api as smf df pd.read_csv(panel_data.csv) # 滞后一期AI变量缓解互为因果 df[ai_lag] df.groupby(firm_id)[ai_intensity].shift(1) df df.dropna(subset[ai_lag, gtfp, size, leverage, roe]) model smf.ols( gtfp ~ ai_lag size leverage roe age top1 board_size C(firm_id) C(year), datadf ).fit(cov_typecluster, cov_kwds{groups: df[firm_id]}) print(model.summary())逻辑说明C(firm_id)和C(year)是个体和时间固定效应cov_typecluster聚类到企业。ai_lag是滞后一期 AI 强度。参数上如果样本量小个体固定效应会吃掉很多自由度可以改用行业固定效应加个体随机效应但要做豪斯曼检验。回归前一定要检查缺失值和异常值绿色全要素生产率算出来可能有极端值缩尾 1% 到 99% 是常规操作。4. 稳健性检验与 RAG 抽取质量排查翻车点都在细节里4.1 替换核心变量AI 变量换三种口径稳健性检验第一招是换 AI 变量口径。比如主回归用 AI 专利数量稳健性检验换成 AI 研发投入占比、年报 AI 词频、AI 技术人员占比。如果三种口径下系数符号和显著性一致结论才可信。RAG 抽取时AI 词频要区分“人工智能”“机器学习”“深度学习”“智能算法”等词做同义词合并。词频要除以年报总字数否则长年报天然词频高。4.2 更换样本区间与剔除特殊年份样本区间换法把 2020 年剔除因为疫情冲击可能同时影响 AI 投入和绿色生产。或者把直辖市企业剔除看结论是否由特定地区驱动。RAG 抽取时如果某年 PDF 解析质量差字段缺失率高要单独标记不要硬填。4.3 内生性处理工具变量与倾向得分匹配工具变量常用同行业同地区 AI 投入均值但要注意排他性。倾向得分匹配按 AI 投入高低分组匹配规模、行业、年份后看绿色全要素生产率差异。匹配后样本量会减少要报告匹配前后平衡性检验。4.4 RAG 抽取质量的排查清单排查 RAG 抽取质量我一般按下面五条走现象某公司 AI 专利数量抽取为 null但年报里明明有。原因召回 top-5 里没有包含专利表格的块。解决增加查询词“专利”“知识产权”并把表格单独抽成结构化数据。现象能耗强度抽出来单位不一致有的吨标煤/万元有的千瓦时/万元。原因模型没做单位归一。解决提示词里要求输出原始单位和数值后处理统一换算。现象同一字段在不同查询下结果不同。原因召回块不同模型看到上下文不同。解决固定查询模板对同一字段用相同查询词并做多次召回取交集。现象模型编造页码。原因提示词没强制要求只从上下文取页码。解决要求模型输出原文片段后处理用字符串匹配定位页码。现象扫描版年报 OCR 错字导致字段抽取失败。原因OCR 把“能耗”识别成“能牦”。解决对 OCR 结果做领域词典纠错或换更高精度 OCR 引擎。注意RAG 抽取结果一定要人工抽检至少 10% 的样本抽检不通过就回去调切块和召回不要直接进回归。5. 把整条管线跑成可复现的脚本从年报文件夹到回归表格5.1 目录结构与配置管理建议目录这样组织data/raw_pdf/放年报 PDFdata/chunks/放切块结果data/extracted/放抽取字段data/panel/放面板数据scripts/放各阶段脚本。配置文件用 YAML把嵌入模型名、top_k、chunk_size、回归变量列表都写进去换数据集时只改配置。# config.yaml embedding_model: BAAI/bge-base-zh-v1.5 chunk_size: 600 chunk_overlap: 100 top_k: 5 llm_temperature: 0.1 winsorize: [0.01, 0.99] cluster_var: firm_id5.2 批量抽取与断点续跑几百份年报跑抽取最怕中途报错全量重跑。做法是每处理完一份就写一个 JSON 文件文件名用公司代码加年份下次跑先检查文件是否存在存在就跳过。下面是一个批量抽取的骨架。import os import json from pathlib import Path def batch_extract(pdf_dir, out_dir, config): Path(out_dir).mkdir(parentsTrue, exist_okTrue) for pdf_file in Path(pdf_dir).glob(*.pdf): firm_year pdf_file.stem # 假设文件名是 公司代码_年份 out_path Path(out_dir) / f{firm_year}.json if out_path.exists(): continue # 断点续跑 try: pages parse_annual_report(str(pdf_file)) chunks chunk_with_page_meta(pages, config[chunk_size], config[chunk_overlap]) # 建索引、召回、抽取 result extract_fields(人工智能投入 绿色专利 能耗强度, config[top_k]) with open(out_path, w, encodingutf-8) as f: json.dump(result, f, ensure_asciiFalse, indent2) except Exception as e: print(f{firm_year} 处理失败: {e}) continue逻辑说明out_path.exists()实现断点续跑try/except保证单份失败不影响整体。参数上firm_year命名要统一后续合并面板时按这个键关联。如果 PDF 文件名不规范先用脚本重命名。5.3 从抽取结果到面板数据抽取完成后把所有 JSON 合并成一张宽表再和财务数据按公司代码和年份合并。合并时注意年报披露的是上一财年数据时间对齐要滞后一年。比如 2023 年年报披露的是 2022 年数据面板里应该把 AI 变量对齐到 2022 年。import pandas as pd import glob records [] for f in glob.glob(data/extracted/*.json): with open(f, encodingutf-8) as fp: d json.load(fp) firm, year f.split(/)[-1].replace(.json, ).split(_) d[firm_id] firm d[report_year] int(year) d[data_year] int(year) - 1 # 年报披露上一财年 records.append(d) df_ai pd.DataFrame(records) df_fin pd.read_csv(data/panel/financial.csv) panel pd.merge(df_fin, df_ai, on[firm_id, data_year], howleft) panel.to_csv(data/panel/merged_panel.csv, indexFalse)逻辑说明data_year做滞后对齐howleft保留财务数据全部样本AI 缺失的填 null。合并后要检查匹配率低于 70% 就要查公司代码格式是否一致。5.4 回归结果输出与表格整理回归跑完后用stargazer或手动整理成三线表。报告系数、标准误、显著性星号、样本量、R 方。异质性分析分两组报告稳健性检验放附录。所有代码和配置打包换一台机器只要改路径就能复现。我自己的习惯是每跑完一轮回归先把结果存成 CSV再用脚本生成表格不手动抄数。这样改样本或改变量时表格自动更新避免版本混乱。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →