文本分析与叙事冲击
Text Analysis & Narrative Shocks
把央行沟通、新闻发布会、货币政策执行报告当成"数据"——用 LDA 主题模型、词典法与监督学习,从语料里提取货币政策叙事与情绪指数,构造可与利率、产出回归的高频冲击序列。
gensim、scikit-learn、jieba、transformers);R 4.2+(stm、topicmodels)。01 为什么用文本数据:信息增量从哪里来
传统宏观实证依赖国民账户、价格、利率、就业等结构化统计指标,这些数据往往低频(季度/月度)、滞后发布,且是对"已发生事实"的回顾。但市场参与者的预期——对下一步政策、增长、通胀路径的判断——在很大程度上并不体现在事后统计里,而是先于事实、藏在央行的一句话、一份会议纪要、一条头条新闻里。文本数据(unstructured text data)正是把这种"先于事实的信息"变成可量化变量的桥梁。
在宏观经济学里,文本作为数据主要有三类来源:
- 央行沟通(central bank communication):FOMC 声明与会议纪要(minutes)、新闻发布会 transcript、货币政策执行报告(中国人民银行每季度发布)、行长讲话。这些文本直接透露决策者对经济的判断和政策倾向,是"政策叙事"的载体。
- 新闻媒体文本:彭博、路透、新华社的宏观新闻头条,反映媒体框架下的经济情绪(economic sentiment)与风险叙事。
- 社交媒体与企业文本:Twitter/X 情绪、上市公司财报电话会议(earnings call)、年报 MD&A,用于测度企业信心与不确定性(如 Baker–Bloom–Davis 的政策不确定性指数 EPU)。
同一句话在不同政策周期里含义不同:"我们关注通胀"在 2021 年和 2023 年传递的政策信号天差地别。文本计量的任务,就是把"措辞的相对变化"提炼成一列时间序列变量,再把它喂进 VAR / 回归 / 事件研究里。它不是让模型"读懂"语言,而是让词频的协方差结构承担经济含义。
Gentzkow, Kelly & Taddy (2019) 把这一范式称为 Text as Data:文本不再是定性叙述,而是和收入、价格一样的高维离散计数数据,可以回归、可以降维、可以做因果识别。
文本相对传统宏观数据的三条信息渠道
为什么审稿人愿意接受"把一句话变成一列数据"?核心是文本在三个维度上补足了传统宏观指标的短板:
- 高频性:FOMC 一年开 8 次会、中国人民银行《货币政策执行报告》一年 4 期、新闻每天都有。传统 CPI/GDP 一个季度才更新一次,文本把"政策预期"从季度频率提升到会议频率甚至日频。
- 前向性(forward-looking):央行文本不是对过去的总结,而是对未来路径的判断。企业财报电话会议里管理层对下一季度的指引,比当期销售额更早反映景气拐点。
- 情绪与不确定性维度:同样是"利率上调",措辞从"必要的"变成"被迫的",传递的政策意图完全不同。这种 nuance 在结构化利率数据里是看不到的,只能从文字里读出来。
反过来也要清醒:文本数据不是免费午餐。它是非结构化、有噪声、有内生性的,下文的错误清单会反复回到这三点。
02 文本预处理流水线 Preprocessing
原始文本是一串字符,模型无法直接消费。标准流水线如下(中文需额外分词):
jieba、pkuseg 或 LAC 做中文分词。央行语料里大量政策术语("降准"" MLF""结构性货币政策")必须加入自定义词典,否则会被切碎。Porter)或词形还原(lemmatization),把 walking/walked/walk 归并为 walk。预处理阶段最容易被忽略的是领域词典:通用停用词表会把"稳定""合理"这些在央行文本里极有信息量的词一并删掉。做央行语料时务必手工审查停用词表。
03 主题模型 LDA:Latent Dirichlet Allocation
LDA(Blei, Ng & Jordan 2003)是一种无监督贝叶斯模型。它假设每篇文档由若干个"主题"混合而成,每个主题又是词上的一个概率分布。两篇文档即使没有共同词,只要共享相似的主题混合比例,LDA 就能把它们聚在一起。
记 $K$ 个潜在主题,$V$ 个词。模型有两组核心参数:
- $\theta_d$:文档-主题分布,$K$ 维向量,$\sum_k \theta_{d,k}=1$。它告诉你"第 $d$ 份央行纪要里,政策展望、通胀展望、金融稳定、汇率……各占多大权重"。
- $\phi_k$:主题-词分布,$V$ 维向量,$\sum_v \phi_{k,v}=1$。它告诉你"第 $k$ 个主题最常说哪些词"。
- $z_{d,n}$:第 $d$ 篇文档第 $n$ 个词的隐主题指派;$w_{d,n}$:观测到的词。
先验 $\alpha$、$\beta$ 是 Dirichlet 超参数:$\alpha$ 越大,文档越倾向于混合多个主题;$\beta$ 越小,主题越集中在少数词上。后验推断通常用 Gibbs 采样:随机初始化每个词的主题指派,然后按条件概率 $p(z_{d,n}=k\mid w_{d,n},\cdot)\propto \dfrac{n_{d,k}+\alpha}{n_d+K\alpha}\cdot\dfrac{n_{k,w}+\beta}{n_k+V\beta}$ 反复重抽样,直到收敛。收敛后统计每个词属于各主题的次数,即可估计 $\theta_d$ 与 $\phi_k$。
不要靠肉眼拍脑袋。报告:(1) 困惑度 perplexity / held-out likelihood;(2) 主题一致性 coherence score(c_v),衡量主题 top-N 词是否在真实语料中共现;(3) 经济学可解释性——每个主题能不能用一句话讲清楚("通胀""就业""金融稳定")。三者折中,宏观文献常用 $K\in[5,20]$。
04 文本情绪测度:词典法与监督学习
LDA 给你"在说什么",情绪(sentiment / tone)给你"用什么语气说"。主流两条路线:
词典法(dictionary / lexicon-based)
预先定义一组"正面词 / 负面词",统计文档里两类词的相对频率:
金融领域最常用的是 Loughran–McDonald (2011) 金融情感词典——它是从上市公司 10-K 里反向构造的,专门解决一个问题:普通英语里的"负面词"(如 liability、tax、cost)在财务文本里其实是中性的。宏观/央行场景下,Ter Ellen et al. 与国内学者多用 自建央行沟通词典(宽松/紧缩、鸽/鹰词表),因为 LM 词典并不直接刻画货币政策立场。
监督学习法(supervised / transformer)
当有人工标注(如某段话被标为"鹰派/鸽派")时,可以训练分类器。近年主流做法是微调 BERT/RoBERTa:把央行纪要逐句送入预训练语言模型,输出"紧缩倾向"概率。相比词典法,BERT 能捕捉否定、转折("虽然通胀偏高,但……")和领域语境,但需要标注数据,且结果难以向审稿人解释。
没有标注 → 词典法起步,结果透明可复现;有几百条人工标注 → 用 BERT 微调并在留出集上报告 F1;顶刊近年趋势是"词典法做基准 + BERT 做稳健性",两者相互印证。
文本测度的外部效度:用市场反应做"校准"
无论词典法还是 BERT,得到的 tone 序列本身没有"绝对刻度"。审稿人最常问的一句话是:你测出来的 tone 真的是市场理解的 tone 吗?标准应对是用高频市场反应做外部校准:在央行声明/纪要发布前后 30 分钟内,看利率期货、股指期货的跳跃方向。如果文本 tone 与这些资产价格跳跃同向且显著相关,说明你的测度"对得上市场";如果不相关,就要回去检查分词、词典、切片。这一步是从"自说自话的词频"走向"有经济含义的政策叙事"的关键一跃。
05 叙事型货币政策冲击识别
传统货币政策冲击识别有两条路:一是 Romer & Romer (2004) 从 FOMC 货币政策报告里手动叙述性提取;二是 high-frequency identification(Gürkaynak, Sack & Swanson 2005)用声明前后几分钟利率期货的变动。叙事文本方法是第三条路:用 NLP 自动化前者,从会议纪要/新闻发布会文本里直接读出"这次决策相对以往是偏鹰还是偏鸽",并剔除央行对经济状态的内生反应。
识别逻辑可以写成两步回归:
第一阶段把"文本语气指数"对央行的内生反应函数(滞后通胀、产出、利率)回归,残差 $\hat\xi_t$ 就是剔除了"按规则应有的反应"之后、由叙事措辞变化带来的外生意外。这与 Romer–Romer 的"剔除央行对实时预测的反应"在精神上完全一致,只是用文本代替了人工阅读。Ter Ellen, Lakdawala & Marshall 正是用这套思路提取 FOMC 的语言冲击,并发现它对产出和通胀有显著而持久的宏观效应。
中文研究常以《货币政策执行报告》、货币政策委员会例会新闻稿、央行官网发布的政策问答为语料。郑挺国、陈琳艳、范馨月(2025,《经济研究》)系统构建了中国央行沟通的叙事测度,区分"政策立场叙事"与"经济展望叙事",识别出央行沟通叙事冲击对债券收益率、股票市场和实体经济的影响。做中文项目时,务必用中文财经分词 + 自建鸽鹰词表,不要直接套用英文 LM 词典。
06 完整 Python 代码:模拟央行文本 → LDA → 情绪 → 回归
下面这段代码完整可运行:先模拟一批"央行纪要"文本(演示用,真实研究时替换为你抓取的 PDF/TXT),用 gensim 跑 LDA,用词典法算情绪指数,最后把情绪指数与政策利率做回归。
# ==============================================================
# 文本分析与叙事冲击:完整可运行演示
# 依赖: pip install gensim scikit-learn jieba pandas numpy statsmodels
# ==============================================================
import re, numpy as np, pandas as pd
from gensim import corpora
from gensim.models import LdaModel
from sklearn.feature_extraction.text import TfidfVectorizer
import statsmodels.api as sm
# ---------- 0. 模拟央行会议纪要语料(真实研究替换为抓取文本) ----------
# 每条 = 一次会议纪要。我们故意混入"宽松派"与"紧缩派"措辞
np.random.seed(7)
dovish_words = ["ease","accommodative","support","liquidity","cut","growth","downside","stimulus"]
hawkish_words = ["tighten","inflation","overheating","rates","hawkish","anchor","overvalued","risk"]
neutral_words = ["monitor","stable","expect","policy","committee","meeting"," outlook","data"]
def make_doc(tilt):
# tilt=+1 偏鹰, -1 偏鸽, 0 中性
bag = []
for _ in range(60):
u = np.random.rand()
if tilt > 0 and u < 0.6:
bag.append(np.random.choice(hawkish_words))
elif tilt < 0 and u < 0.6:
bag.append(np.random.choice(dovish_words))
else:
bag.append(np.random.choice(neutral_words))
return " ".join(bag)
docs = [make_doc(np.random.choice([-1,0,1])) for _ in range(400)]
# ---------- 1. 预处理:分词 + 去停用词 ----------
stopwords = set(["the","a","an","of","to","and","is","in","for","on","our","we"])
def tokenize(text):
toks = re.findall(r"[a-z]+", text.lower())
return [t for t in toks if t not in stopwords and len(t) > 2]
tokenized = [tokenize(d) for d in docs]
dictionary = corpora.Dictionary(tokenized)
dictionary.filter_extremes(no_below=5, no_above=0.5) # 过滤极端词
corpus_bow = [dictionary.doc2bow(t) for t in tokenized]
# ---------- 2. TF-IDF 加权 ----------
tfidf = TfidfVectorizer(tokenizer=lambda x: x, lowercase=False,
token_pattern=None)
tfidf_matrix = tfidf.fit_transform([" ".join(t) for t in tokenized])
print("文档-词矩阵维度:", tfidf_matrix.shape)
# ---------- 3. LDA 主题模型(gensim) ----------
K = 3 # 主题数;实际用 coherence 选择
lda = LdaModel(corpus=corpus_bow, id2word=dictionary,
num_topics=K, random_state=42,
alpha='auto', eta='auto',
passes=20, iterations=200)
print("\n=== 各主题 top-8 词 ===")
for k in range(K):
top = lda.show_topic(k, topn=8)
print(f"主题{k}: " + ", ".join([w for w,_ in top]))
# 提取每篇文档的文档-主题分布 theta_d(后验推断)
theta = np.array([[p for _,p in lda.get_document_topics(bow, minimum_probability=0.0)]
for bow in corpus_bow])
print("\n文档-主题分布 theta 维度:", theta.shape) # (D, K)
# ---------- 4. 词典法情绪指数(鸽 vs 鹰) ----------
hawk_set = set(hawkish_words)
dove_set = set(dovish_words)
def sentiment(tokens):
n_pos = sum(t in hawk_set for t in tokens)
n_neg = sum(t in dove_set for t in tokens)
return (n_pos - n_neg) / max(len(tokens),1)
tone = np.array([sentiment(t) for t in tokenized]) # >0 偏鹰, <0 偏鸽
print("\n情绪指数 tone 描述: mean=%.3f std=%.3f" % (tone.mean(), tone.std()))
# ---------- 5. 把文本指数与利率做回归 ----------
# 模拟政策利率(真实研究用 shibor/DR007/Fed Funds)
policy_rate = 0.02 + 0.30*tone + 0.1*np.random.randn(len(tone))*0.001
df = pd.DataFrame({"tone":tone, "rate":policy_rate,
"theta_k0":theta[:,0], "theta_k1":theta[:,1]})
X = sm.add_constant(df[["tone","theta_k0","theta_k1"]])
ols = sm.OLS(df["rate"], X).fit()
print("\n=== 文本语气对政策利率的回归 ===")
print(ols.summary().tables[1])
# 解读: tone 系数应显著为正——偏鹰的纪要对应更高政策利率
把 docs 换成你从央行官网抓取的真实纪要文本(PDF → pdfplumber → 分会议切片),把 hawk_set/dove_set 换成自建中文鸽鹰词典,把 policy_rate 换成 DR007 或国债收益率,就是一篇完整工作论文的实证骨架。
07 完整 R 代码:stm / topicmodels 做 LDA
R 生态里 stm(Structural Topic Model)是当前最受顶刊欢迎的工具,它允许把文档协变量(如会议时间、主席任期)放进主题先验,直接估计"主题占比如何随时间/政策体制变化"。下面是最小可运行流程:
# ==============================================================
# R: 用 stm 包估计央行纪要 LDA / 结构主题模型
# install.packages(c("stm","topicmodels","quanteda"))
# ==============================================================
library(quanteda); library(stm); library(topicmodels)
# ---------- 0. 读入语料:一行一篇会议纪要 ----------
# df <- read.csv("pboc_minutes.csv", stringsAsFactors=FALSE)
# df$date <- as.Date(df$date)
# 这里用内置数据集演示
data(gadarian) # quanteda 内置:妇女参政论者传单文本
docs <- gadarian$documents
meta <- gadarian[ , c("treatment","pid","year","age","female","ideology")]
# ---------- 1. 构造文档-词矩阵(quanteda) ----------
corp <- corpus(docs)
toks <- tokens(corp, remove_punct=TRUE, remove_numbers=TRUE)
toks <- tokens_tolower(toks)
toks <- tokens_remove(toks, stopwords("en"))
toks <- tokens_wordstem(toks, language="english")
dfm_ <- dfm(toks)
dfm_ <- dfm_trim(dfm_, min_termfreq = 5, docfreq_max = 0.5)
# ---------- 2. stm:把协变量放进主题先验 ----------
# prevalence = ~ treatment + ideology + s(age)
# 表示"主题占比"受 treatment、意识形态、年龄影响
stm_fit <- stm(documents = dfm_,
K = 5,
prevalence = ~ treatment + ideology,
data = meta,
seed = 2024)
# 看每个主题的代表词
labelTopics(stm_fit, n = 8)
# ---------- 3. 主题占比随协变量变化(宏观研究常用) ----------
# estimateEffect: 在控制其他变量后, treatment 对各主题占比的影响
eff <- estimateEffect(1:5 ~ treatment + ideology,
stm_fit, meta, uncertainty = "Global")
plot(eff, "treatment", cov.value1 = "treatment", cov.value2 = "control")
# ---------- 4. 输出文档-主题分布 theta 供后续回归 ----------
theta_hat <- stm_fit$theta # D x K 矩阵
write.csv(theta_hat, "doc_topic_proportions.csv")
# ---------- 5. 备选:经典 LDA(topicmodels 包) ----------
# dtm <- convert(dfm_, to = "topicmodels")
# lda_fit <- LDA(dtm, k = 5, method = "Gibbs",
# control = list(seed=123, burnin=1000, thin=100, iter=5000))
# posterior(lda_fit)$terms # phi_k
# posterior(lda_fit)$topics # theta_d
08 文本作为因子 / 工具变量:进 VAR 与 IV 回归
文本指数本质上是一列高频(每次会议一条)的时间序列。把它纳入宏观模型有三种标准做法:
- 作为外生冲击进 VAR:把叙事情绪指数 $\hat\xi_t$ 排在 VAR 系统最前面(Cholesky 顺序第一位),识别"文本冲击"对产出、通胀、利率的脉冲响应。这是 Ter Ellen et al. 的做法——文本冲击不 contempraneously 反应宏观变量,因此排在 VAR 第一位。
- 作为工具变量 IV:当你想估计"央行沟通对利率的影响"但利率又反过来影响沟通时,可以用其他央行/其他时段的文本情绪(如美联储纪要对中国资产价格的外溢)作为工具,满足相关性 + 外生性。
- 作为因子因子分析:把上百篇纪要的 $\theta_{d,k}$ 按月平均,得到 $K$ 个低频"文本因子",再做 FAVAR(factor-augmented VAR),控制"文本里没说但数据里有"的信息。
09 论文案例精读
10 常见错误与进阶
LM 词典是从上市公司 10-K 年报构造的,"liability""risk"在财报里是负面词,在央行文本里却是中性甚至必要的风险提示。直接套用会得到几乎无信息量的"情绪指数"。正确做法:基于你的语料自建鸽鹰/宽松紧缩词典,或用少量人工标注训练分类器。
把 K 调到 50、100,发现每个主题都"很有意思",于是往里讲故事。LDA 是无监督模型,K 越大越能在样本内"解释"一切,但留出对数似然/ coherence 会先升后降。务必报告 coherence 曲线、困惑度,并做不同 K 的稳健性。
央行"说什么"取决于经济"现在怎么样"。如果你直接把 tone 对 GDP 回归,OLS 系数把"反应函数"和"冲击"混在一起。必须先做 Eq. 5.1 的第一阶段,用残差进回归/VAR,否则你的"叙事冲击"其实只是滞后宏观变量的影子。
不调用 jieba 直接把整段中文当一个长字符串,词袋矩阵全是单字噪声;把一整年《货币政策执行报告》当成一篇文档,则丢失了会议频率。正确做法:按"每次会议/每期报告"切片,中文单独分词,加入金融自定义词典。
用 BERT 微调得到一个 tone 序列,却不报告标注协议、留出集 F1、与词典法的相关性。审稿人无法复现就会直接拒。必须人工抽查 50–100 条分类结果,并与词典法做对照。
进阶资料与代码资源
- gensim LDA 官方教程:radimrehurek.com/gensim — 本页 Python 代码所用库。
- R
stm包:Roberts, Stewart & Tingley (2014), "stm: R Package for Structural Topic Models", JSS。 - Blei, Ng & Jordan (2003) — "Latent Dirichlet Allocation", JMLR。
- Loughran–McDonald 词典官网:sraf.nd.edu/textual-analysis — 免费下载六大情感词表。
- 郑挺国等(2025)中文论文:《经济研究》官网下载,参考其中文分词与央行语料切片流程。