File size: 6,354 Bytes
53b81c8 d3f108f 53b81c8 d3f108f 53b81c8 d3f108f 53b81c8 d3f108f 53b81c8 d3f108f 53b81c8 d3f108f 53b81c8 d3f108f 53b81c8 d3f108f 53b81c8 d3f108f 53b81c8 d3f108f 53b81c8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | # -*- coding: utf-8 -*-
"""
FinSight 数据预处理:清洗 + 统一Alpaca格式
输入: data/raw/ 下载的原始数据(先跑 download_datasets.py)
输出: data/processed/finsight_train.jsonl / finsight_eval.jsonl
统一字段: instruction / input / output / task / agent
清洗规则(对应数据探索发现的噪声):
1. 网页杂质: 广告语(【立即开户,领取福利】)、"原标题:"、乱码问号串、多余空白
2. 情感标签归一: FINFE输出表述不统一("中性。"/"所属情感是中性。")→ 统一为"积极/消极/中性"
sentiment agent 只保留 FINFE 三分类;FINNSP(负面主体识别,NER抽取)归入 event agent
3. BAAI数据: 只保留中文、deita质量分>=7、取首轮对话转Alpaca格式
按key确定性切出约1/25(~1.5k)做qa评估集(BAAI是qa唯一来源,否则eval无qa)
4. 通用过滤: 输出为空、总长超4000字符的样本剔除;按(instruction+input)去重
5. 跨集去泄漏: 存eval前减去train已含的(instruction+input),避免train/eval污染
"""
import hashlib
import json
import os
import re
from collections import Counter
RAW = os.path.join(os.path.dirname(__file__), "raw")
OUT = os.path.join(os.path.dirname(__file__), "processed")
os.makedirs(OUT, exist_ok=True)
# FinCUGE任务 → FinSight Agent 的映射
TASK2AGENT = {
"FINQA": "event", # 事件抽取
"FINCQA": "event", # 因果事件抽取
"FINESE": "event", # 事件主体抽取
"FINFE": "sentiment", # 论坛情绪
"FINNSP": "event", # 负面消息及主体识别(NER抽取,非情感分类)
"FINNA": "summary", # 新闻摘要
"FINNL": "topic", # 新闻分类
"FINRE": "topic", # 关系抽取
}
# 含这些关键词的整句删除(券商广告变体太多,按关键词整句清除比枚举文案可靠)
AD_KEYWORDS = ["立即开户", "你还在等什么", "领取福利", "极速响应", "点击查看",
"股市震荡,需要注意什么", "跨年行情,应该如何布局"]
# 句子 = 到上一个句末标点/换行 为止、到下一个句末标点/换行 为止
AD_SENT_RE = re.compile(
r"[^。!?!?\n]*(?:" + "|".join(map(re.escape, AD_KEYWORDS)) + r")[^。!?!?\n]*[。!?!?]?"
)
AD_RE = re.compile(r"原标题[::]\s*|\?{3,}|?{3,}")
WS_RE = re.compile(r"[ \t ]+")
SENT_LABELS = ["积极", "消极", "中性"]
def clean_text(t):
t = AD_SENT_RE.sub("", t)
t = AD_RE.sub("", t)
t = WS_RE.sub(" ", t)
return t.strip()
def normalize_sentiment(out):
"""'所属情感是中性。' / '以上文本情感极性属于消极。' → '中性' / '消极'"""
hits = [lb for lb in SENT_LABELS if lb in out]
return hits[0] if len(hits) == 1 else None # 多标签/无标签视为噪声
def iter_fincuge(path):
with open(path, encoding="utf-8") as f:
for line in f:
r = json.loads(line)
task = r["task"]
ins = clean_text(r["instruction"])
inp = clean_text(r.get("input", ""))
out = clean_text(r["output"])
if task == "FINFE":
label = normalize_sentiment(out)
if label is None:
continue
out = label
yield {"instruction": ins, "input": inp, "output": out,
"task": task, "agent": TASK2AGENT[task]}
def iter_baai(path, min_score=7.0):
with open(path, encoding="utf-8") as f:
for line in f:
r = json.loads(line)
if r.get("lang") != "zh" or r.get("deita_score", 0) < min_score:
continue
conv = r.get("conversations", [])
if len(conv) < 2 or conv[0]["from"] != "human":
continue
ins = clean_text(conv[0]["value"])
out = clean_text(conv[1]["value"])
yield {"instruction": ins, "input": "", "output": out,
"task": "BAAI_QA", "agent": "qa"}
def row_key(r):
return hashlib.md5((r["instruction"] + r["input"]).encode()).hexdigest()
def dedup_and_filter(rows):
seen, kept = set(), []
stats = Counter()
for r in rows:
if not r["output"] or not r["instruction"]:
stats["空字段剔除"] += 1
continue
if len(r["instruction"]) + len(r["input"]) + len(r["output"]) > 4000:
stats["超长剔除"] += 1
continue
key = row_key(r)
if key in seen:
stats["重复剔除"] += 1
continue
seen.add(key)
kept.append(r)
return kept, stats
def split_baai_eval(rows, mod=25):
"""确定性切分: 约1/mod进qa评估集。基于key哈希,可复现、与train无交集"""
tr, ev = [], []
for r in rows:
(ev if int(row_key(r), 16) % mod == 0 else tr).append(r)
return tr, ev
def remove_overlap(ev_rows, train_rows):
"""存eval前剔除train已含的(instruction+input),消除跨集泄漏"""
tkeys = {row_key(r) for r in train_rows}
kept = [r for r in ev_rows if row_key(r) not in tkeys]
return kept, len(ev_rows) - len(kept)
def save(rows, name):
path = os.path.join(OUT, name)
with open(path, "w", encoding="utf-8") as f:
for r in rows:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
print(f" {name}: {len(rows):,} 条")
print(f" agent分布: {dict(Counter(r['agent'] for r in rows))}")
if __name__ == "__main__":
print("[1/2] 处理训练集 ...")
train = list(iter_fincuge(os.path.join(RAW, "fincuge_train.jsonl")))
n_fincuge = len(train)
baai = list(iter_baai(os.path.join(RAW, "baai_finance_all.jsonl")))
baai_train, baai_eval = split_baai_eval(baai)
train += baai_train
print(f" 原始: FinCUGE {n_fincuge:,} + BAAI筛后 {len(baai):,}"
f"(train {len(baai_train):,} / 切出qa评估 {len(baai_eval):,})")
train, stats = dedup_and_filter(train)
print(f" 清洗统计: {dict(stats)}")
save(train, "finsight_train.jsonl")
print("[2/2] 处理评估集 ...")
ev = list(iter_fincuge(os.path.join(RAW, "fincuge_eval.jsonl"))) + baai_eval
ev, stats = dedup_and_filter(ev)
ev, n_overlap = remove_overlap(ev, train)
print(f" 清洗统计: {dict(stats)} 跨集泄漏剔除: {n_overlap}")
save(ev, "finsight_eval.jsonl")
|