名师讲堂|R 语言中使用 finbert-tone-chinese 模型对上市公司年报文本进行情感分析

在之前的 R 语言课程「R 语言文本分析」中,我介绍了使用词频统计进行情感分析的方法,也就是先对文本进行分词,根据情感词典统计积极、消极和中性词的词频进行分析。不过这种方法难以分析复杂的文本表述。

R 语言文本分析:https://rstata.duanshu.com/#/brief/course/bf37cf50eef04d38b43541cc52114c96

另外在课程「使用 R 语言调用百度大脑自然语言处理接口进行文本情感倾向分析」中,我又介绍了一种使用 API 接口进行文本情感分析的方法,不过这种方法需要付费,不够经济。

使用 R 语言调用百度大脑自然语言处理接口进行文本情感倾向分析: https://rstata.duanshu.com/#/brief/course/8c0ceabc989d4d2bb886862d256c7181

最近找到了一种新的方法,也就是借助一些预训练模型,这里我使用的是 finbert-tone-chinese 模型。

finbert-tone-chinese: https://hf-mirror.com/yiyanghkust/finbert-tone-chinese

该模型是基于 bert-base-chinese,使用大约 8000 份分析师报告文本进行训练得到,在测试集上的准确度为 0.88。

关于 bert-base-chinese 模型的使用,大家可以学习之前的课程:

名师讲堂|突破性创新与颠覆性创新:使用BERT和 SBERT 模型计算专利文本相似度(一):https://rstata.duanshu.com/#/brief/course/35f74d717a0b464ea103175adbb5d885

名师讲堂|突破性创新与颠覆性创新:使用BERT和 SBERT 模型计算专利文本相似度(二):https://rstata.duanshu.com/#/brief/course/b1e28818e8164cfda3430cc45fddaf91

类似上述课程,这里我们同样使用 R 语言运行这一模型。

附件中的“finbert-tone-chinese”文件夹就是该模型的文件。如果大家使用 Mac 电脑的话,可以使用下面的代码下载(注意是 shell 代码,不是 R 语言的。附件中有下载好的,不用再下载了):

brew 的安装在系列课程「R 语言数据科学」第一次课里面有介绍。

brew install git-lfs
git lfs install
git clone https://hf-mirror.com/yiyanghkust/finbert-tone-chinese

为了在 R 语言中调用,我们需要先使用 Python 编写一个函数:

# 下载模型(MAC 系统)
# brew install git-lfs
# git lfs install
# git clone https://hf-mirror.com/yiyanghkust/finbert-tone-chinese

from transformers import BertTokenizer, BertForSequenceClassification
import torch
import torch.nn.functional as F

def get_financial_sentiment_scores(text: str) -> dict:
"""
输入一段中文金融文本,输出 FinBERT-Tone-Chinese 模型预测的三种情感(正面/负面/中性)得分

参数:
text: str - 待分析的中文文本(建议为金融相关内容,如财报摘要、新闻标题、市场评论等)

返回:
dict - 包含 "neutral"(中性得分)、"positive"(正面得分)、"negative"(负面得分)的字典,
得分范围为 [0,1],三者总和为 1
"""
# 1. 定义模型名称(与 URL 对应)和情感标签映射
MODEL_NAME = "/Users/ac/Desktop/上市公司年报文本情感分析/finbert-tone-chinese/"
SENTIMENT_LABELS = ["neutral", "positive", "negative"] # 模型输出顺序固定

try:
# 2. 加载模型和分词器(首次调用会自动下载模型,后续复用本地文件)
tokenizer = BertTokenizer.from_pretrained(MODEL_NAME)
model = BertForSequenceClassification.from_pretrained(MODEL_NAME)

# 3. 文本预处理:将文本转换为模型可接受的张量格式
# truncation=True:超过模型最大长度(512)的文本截断
# padding=True:不足长度的文本补全
# return_tensors="pt":返回 PyTorch 张量
inputs = tokenizer(
text,
truncation=True,
padding=True,
max_length=512,
return_tensors="pt"
)

# 4. 模型推理(关闭梯度计算,提高效率)
model.eval() # 切换为评估模式(避免训练时的随机 dropout)
with torch.no_grad():
outputs = model(**inputs) # 传入预处理后的文本
logits = outputs.logits # 获取模型原始输出(未归一化的得分)
probabilities = F.softmax(logits, dim=1) # 归一化,得到概率得分(总和为 1)

# 5. 结果转换:从张量提取数值,与情感标签对应
prob_values = probabilities.squeeze().tolist() # 去除多余维度,转为列表
sentiment_scores = dict(zip(SENTIMENT_LABELS, prob_values))

# 6. 保留 4 位小数(可选,提升可读性)
sentiment_scores = {k: round(v, 4) for k, v in sentiment_scores.items()}

return sentiment_scores

except Exception as e:
raise RuntimeError(f"情感分析失败:{str(e)}")

这个函数会输入一段中文金融文本,输出 FinBERT-Tone-Chinese 模型预测的三种情感(正面/负面/中性)得分。

然后回到 R 语言里面。首先加载相关 R 包,创建虚拟环境:

library(tidyverse)
library(reticulate)

# 1. 创建虚拟环境
# 安装 python3.10
conda_create(envname = "bert2", python_version = "3.10")

#> [1] "/opt/anaconda3/envs/bert2/bin/python"

# 2. 激活环境
use_condaenv("bert2")

# 3. 安装必要的库
# 1. 读取requirements.txt文件
requirements <- readLines("requirements.txt")

# 2. 清理空行和注释
requirements <- grep("^[^#]", requirements, value = TRUE) # 移除注释行
requirements <- trimws(requirements) # 去除两端空格
requirements <- requirements[requirements != ""] # 移除空行

requirements

#> [1] "accelerate==0.20.3" "certifi==2025.6.15"
#> [3] "charset-normalizer==3.4.2" "click==8.2.1"
#> [5] "filelock==3.18.0" "fsspec==2025.5.1"
#> [7] "hf-xet==1.1.5" "huggingface-hub==0.19.4"
#> [9] "idna==3.10" "Jinja2==3.1.6"
#> [11] "joblib==1.5.1" "MarkupSafe==3.0.2"
#> [13] "mpmath==1.3.0" "networkx==3.4.2"
#> [15] "nltk==3.9.1" "numpy==1.26.4"
#> [17] "packaging==25.0" "pillow==11.2.1"
#> [19] "psutil==7.0.0" "PyYAML==6.0.2"
#> [21] "regex==2024.11.6" "requests==2.32.4"
#> [23] "safetensors==0.5.3" "scikit-learn==1.7.0"
#> [25] "scipy==1.15.3" "sentence-transformers==2.2.2"
#> [27] "sentencepiece==0.2.0" "sympy==1.14.0"
#> [29] "threadpoolctl==3.6.0" "tokenizers==0.13.3"
#> [31] "torch==2.0.1" "torchaudio==2.0.2"
#> [33] "torchvision==0.15.2" "tqdm==4.67.1"
#> [35] "transformers==4.30.2" "typing_extensions==4.14.0"
#> [37] "urllib3==2.5.0"

# 3. 安装所有依赖
conda_install(packages = requirements, envname = "bert2",
python_version = "3.10", pip = T,
pip_options = "--index-url https://pypi.tuna.tsinghua.edu.cn/simple")

#> [1] "'accelerate==0.20.3'" "'certifi==2025.6.15'"
#> [3] "'charset-normalizer==3.4.2'" "'click==8.2.1'"
#> [5] "'filelock==3.18.0'" "'fsspec==2025.5.1'"
#> [7] "'hf-xet==1.1.5'" "'huggingface-hub==0.19.4'"
#> [9] "'idna==3.10'" "'Jinja2==3.1.6'"
#> [11] "'joblib==1.5.1'" "'MarkupSafe==3.0.2'"
#> [13] "'mpmath==1.3.0'" "'networkx==3.4.2'"
#> [15] "'nltk==3.9.1'" "'numpy==1.26.4'"
#> [17] "'packaging==25.0'" "'pillow==11.2.1'"
#> [19] "'psutil==7.0.0'" "'PyYAML==6.0.2'"
#> [21] "'regex==2024.11.6'" "'requests==2.32.4'"
#> [23] "'safetensors==0.5.3'" "'scikit-learn==1.7.0'"
#> [25] "'scipy==1.15.3'" "'sentence-transformers==2.2.2'"
#> [27] "'sentencepiece==0.2.0'" "'sympy==1.14.0'"
#> [29] "'threadpoolctl==3.6.0'" "'tokenizers==0.13.3'"
#> [31] "'torch==2.0.1'" "'torchaudio==2.0.2'"
#> [33] "'torchvision==0.15.2'" "'tqdm==4.67.1'"
#> [35] "'transformers==4.30.2'" "'typing_extensions==4.14.0'"
#> [37] "'urllib3==2.5.0'"

然后就可以把 Python 的函数 source 成 R 语言的函数了:

source_python("main.py")

使用示例:

get_financial_sentiment_scores(text = "公司2024年净利润同比增长50%,核心业务市场份额进一步扩大")

#> $neutral
#> [1] 0.0005
#>
#> $positive
#> [1] 0.9992
#>
#> $negative
#> [1] 0.0003

get_financial_sentiment_scores(text = "此外宁德时代上半年实现出口约2GWh,同比增加200%+。")

#> $neutral
#> [1] 0.0007
#>
#> $positive
#> [1] 0.9989
#>
#> $negative
#> [1] 0.0004

get_financial_sentiment_scores("公司因违规操作被监管处罚,预计本年度营收将减少20%")

#> $neutral
#> [1] 0.0007
#>
#> $positive
#> [1] 0.0005
#>
#> $negative
#> [1] 0.9988

get_financial_sentiment_scores("央行今日发布2024年第二季度货币政策执行报告,详细阐述当前经济形势")

#> $neutral
#> [1] 0.9934
#>
#> $positive
#> [1] 0.0048
#>
#> $negative
#> [1] 0.0018

如果是数据框,可以使用如此代码计算每段文本的情感得分:

# 对于数据框
tibble(text = c(
"公司2024年净利润同比增长50%,核心业务市场份额进一步扩大",
"此外宁德时代上半年实现出口约2GWh,同比增加200%+。",
"公司因违规操作被监管处罚,预计本年度营收将减少20%",
"央行今日发布2024年第二季度货币政策执行报告,详细阐述当前经济形势"
)) -> df

df

#> # A tibble: 4 × 1
#> text
#> <chr>
#> 1 公司2024年净利润同比增长50%,核心业务市场份额进一步扩大
#> 2 此外宁德时代上半年实现出口约2GWh,同比增加200%+。
#> 3 公司因违规操作被监管处罚,预计本年度营收将减少20%
#> 4 央行今日发布2024年第二季度货币政策执行报告,详细阐述当前经济形势

df %>%
mutate(res = map(text, get_financial_sentiment_scores)) -> df1

df1 %>%
mutate(res = map(res, ~tibble(class = c("neutral", "positive", "negative"), value = unlist(.x)))) %>%
unnest(res) %>%
spread(class, value)

#> # A tibble: 4 × 4
#> text negative neutral positive
#> <chr> <dbl> <dbl> <dbl>
#> 1 公司2024年净利润同比增长50%,核心业务市场份额进一步扩大…… 0.0003 0.0005 0.999
#> 2 公司因违规操作被监管处罚,预计本年度营收将减少20% 0.999 0.0007 0.0005
#> 3 央行今日发布2024年第二季度货币政策执行报告,详细阐述当前经济形势…… 0.0018 0.993 0.0048
#> 4 此外宁德时代上半年实现出口约2GWh,同比增加200%+。 0.0004 0.0007 0.999

然后就可以进行其他分析了。

点击这里跳转到 RStata 短书平台获取附件:名师讲堂|R 语言中使用 finbert-tone-chinese 模型对上市公司年报文本进行情感分析

评论