3. 金融文本命名实体识别

金融文本命名实体识别:金额与百分比 学习卡片

任务与边界

从财报/新闻句子中抽取三类数值实体:货币金额(如 1.2亿元、$3.5 million)、百分比(如 12.5%、百分之八)、增长率(如 同比增长15%、环比下降3.2个百分点)。抽取后统一为结构化字段:类型、原始文本、数值、单位、方向(增长/下降/无)。

实现方式:正则规则 + 中文数字转换,不依赖外部模型,纯 Python 标准库即可运行。

处理流程

输入句子 金额正则匹配 百分比/增长率匹配 中文数字→阿拉伯 统一格式输出

完整代码

import re

CN_NUM = {'零':0,'一':1,'二':2,'两':2,'三':3,'四':4,'五':5,'六':6,'七':7,'八':8,'九':9}
CN_UNIT = {'十':10,'百':100,'千':1000,'万':10000,'亿':100000000}

def cn_to_num(s):
    """中文数字转阿拉伯数字,支持'百分之八'中的'八'、'一点二'等"""
    if not s:
        return None
    # 处理小数:一点二
    if '点' in s:
        int_part, dec_part = s.split('点', 1)
        int_val = cn_to_num(int_part) or 0
        dec_str = ''.join(str(CN_NUM.get(c, 0)) for c in dec_part)
        return float(f"{int_val}.{dec_str}")
    total, section, num = 0, 0, 0
    for ch in s:
        if ch in CN_NUM:
            num = CN_NUM[ch]
        elif ch in CN_UNIT:
            unit = CN_UNIT[ch]
            if unit >= 10000:
                section = (section + num) * unit
                total += section
                section, num = 0, 0
            else:
                section += (num if num else 1) * unit
                num = 0
    return total + section + num

# 金额:数字+单位(亿/万/元/美元等),或 $/¥ 前缀
MONEY_RE = re.compile(
    r'(?P<cur>[$¥¥])?\s*(?P<num>\d+(?:\.\d+)?)\s*(?P<unit>亿元|万元|亿|万|元|美元|million|billion)?'
)
# 百分比:数字% 或 百分之中文
PCT_RE = re.compile(r'(?P<num>\d+(?:\.\d+)?)\s*%|百分之(?P<cn>[零一二两三四五六七八九十点]+)')
# 增长率:方向词 + 百分比
GROW_RE = re.compile(r'(?P<dir>同比增长|环比增长|增长|同比增长率|环比下降|下降|减少|上升)\s*(?P<pct>\d+(?:\.\d+)?)\s*%')

UNIT_MAP = {'亿元':'亿元','万元':'万元','亿':'亿元','万':'万元','元':'元','美元':'美元',
            'million':'百万美元','billion':'十亿美元'}

def extract(text):
    results = []
    # 先抽增长率(优先级高,避免被百分比重复匹配)
    grow_spans = []
    for m in GROW_RE.finditer(text):
        grow_spans.append(m.span())
        direction = '下降' if ('下降' in m.group('dir') or '减少' in m.group('dir')) else '增长'
        results.append({
            'type': 'growth_rate', 'text': m.group(0),
            'value': float(m.group('pct')), 'unit': '%', 'direction': direction
        })
    # 金额
    for m in MONEY_RE.finditer(text):
        if not m.group('num'):
            continue
        if any(s <= m.start() < e for s, e in grow_spans):
            continue
        unit = UNIT_MAP.get(m.group('unit') or '', m.group('unit') or '')
        cur = m.group('cur') or ''
        results.append({
            'type': 'money', 'text': m.group(0).strip(),
            'value': float(m.group('num')), 'unit': cur + unit if cur else unit,
            'direction': None
        })
    # 百分比
    for m in PCT_RE.finditer(text):
        if any(s <= m.start() < e for s, e in grow_spans):
            continue
        if m.group('num'):
            val = float(m.group('num'))
        else:
            val = cn_to_num(m.group('cn'))
        results.append({
            'type': 'percentage', 'text': m.group(0),
            'value': val, 'unit': '%', 'direction': None
        })
    return results

if __name__ == '__main__':
    samples = [
        "公司2023年营收1.2亿元,同比增长15%,净利润率为百分之八。",
        "Q2 revenue reached $3.5 million, down 3.2% quarter over quarter.",
        "总资产达500万元,环比下降2个百分点。"
    ]
    for s in samples:
        print("输入:", s)
        for r in extract(s):
            print("  ", r)
        print()

运行与验证

运行:python extract.py。观察输出中每条实体是否包含 type / text / value / unit / direction 五个字段。

通过条件:

  • 第一句应识别出 1.2亿元(money)、15%(growth_rate, 增长)、百分之八(percentage, value=8)。
  • 第二句应识别出 $3.5 million(money, unit=百万美元)、3.2%(growth_rate, 下降)。
  • 第三句应识别出 500万元(money)、2%(growth_rate, 下降)。

若某条缺失或类型错误,检查对应正则是否被增长率跨度过滤误伤。