金融领域知识建模:金融本体设计方法论
好,咱们进入第二章。这一章,我打算聊聊金融知识图谱最核心的部分——本体设计。
很多人一上来就急着写代码、搭图数据库,结果做着做着发现数据对不上、关系理不清。说白了,就是本体没设计好。我早期一个项目就吃过这个亏,吭哧吭哧搞了两个月,最后发现实体类型定义得太粗,根本没法支撑问答。嗯,从那以后,我养成了一个习惯:先花30%的时间做本体设计。
什么是金融本体?
本体,听起来玄乎。其实就是一套共享的概念模型。你想想看,咱们要建一个金融问答系统,总得先定义清楚:
- 有哪些东西?(实体类型)
- 它们之间怎么关联?(关系类型)
- 每个东西有什么特征?(属性定义)
这就是本体的三要素。我习惯用一句话概括:本体就是知识图谱的“骨架”。骨架搭好了,数据才能往上挂。
核心原则:本体设计要遵循“够用就好”原则。别想着一次把所有细节都定义完,迭代才是王道。
实体类型定义
金融领域,实体类型其实就四大类:公司、人物、事件、产品。我见过有人把“股票代码”也当成实体类型,这就过度设计了。代码只是属性,不是实体。
1. 公司(Company)
这是最核心的实体。包括上市公司、非上市公司、子公司、关联公司等。我建议用层级结构来组织:
- 上市公司:有股票代码、交易所信息
- 非上市公司:关注注册资本、股东结构
- 子公司:需要关联母公司
我的经验:公司实体一定要有唯一标识。我习惯用“统一社会信用代码”作为主键,股票代码作为辅助标识。别只用公司名,重名的情况太多了。
2. 人物(Person)
人物包括高管、股东、实控人、分析师等。这里要注意:
- 高管:关注任职时间、职位变动
- 股东:关注持股比例、增减持记录
- 实控人:这是监管重点关注的对象
我曾经踩过一个坑:把“董事长”和“总经理”当成同一个人处理。结果问答系统里问“谁是CEO”,返回了董事长。后来我加了个职位时间线属性,才解决这个问题。
3. 事件(Event)
金融事件是问答系统的高频需求。常见的有:
- 投资事件:谁投了谁、投了多少
- 收购事件:收购方、被收购方、交易金额
- 财报发布:营收、利润、增长率
- 监管处罚:处罚对象、原因、金额
事件实体有个特点:时间属性特别重要。我一般会给每个事件加一个“发生时间”和“披露时间”,因为金融数据经常有滞后。
4. 产品(Product)
产品包括基金、理财产品、保险、信托等。这里要区分:
- 基金产品:关注净值、规模、基金经理
- 理财产品:关注收益率、期限、风险等级
- 保险产品:关注保障范围、保费、赔付条件
注意:产品实体容易和“公司”实体混淆。比如“余额宝”是产品,但背后是天弘基金公司。千万别搞混了。
关系类型定义
关系是知识图谱的灵魂。我总结了三类核心关系:
| 关系类型 | 示例 | 说明 |
|---|---|---|
| 投资 | 公司A 投资 公司B | 包括股权投资、债权投资 |
| 收购 | 公司A 收购 公司B | 注意区分“控股”和“全资收购” |
| 任职 | 张三 任职于 公司A | 需要记录职位和任职时间 |
除了这三类,还有持股、担保、关联交易等关系。但我建议先从核心关系做起,后面再扩展。
属性定义与约束
属性就是实体的特征。我习惯把属性分成三类:
- 标识属性:唯一标识实体的,比如公司ID、股票代码
- 描述属性:描述实体特征的,比如公司名称、注册地址
- 数值属性:可量化的,比如注册资本、营收金额
属性约束也很重要。我常用的约束有:
- 非空约束:比如公司名称不能为空
- 唯一约束:比如股票代码必须唯一
- 范围约束:比如持股比例在0-100%之间
- 格式约束:比如日期格式必须是YYYY-MM-DD
避坑指南:我曾经在属性定义上犯过一个低级错误——把“注册资本”定义成字符串类型。结果查询“注册资本大于1亿的公司”时,字符串比较完全乱套。后来我改成数值类型,并加了单位属性“万元”。
知识体系结构图
下面这张图,是我做金融本体设计时常用的框架。它展示了实体、关系、属性之间的逻辑关系:
代码示例:本体定义的Python实现
理论说完了,咱们看看代码。我用Python的dataclass来定义本体,简洁又清晰:
from dataclasses import dataclass
from typing import List, Optional
from enum import Enum
class EntityType(Enum):
COMPANY = "公司"
PERSON = "人物"
EVENT = "事件"
PRODUCT = "产品"
class RelationType(Enum):
INVEST = "投资"
ACQUIRE = "收购"
EMPLOY = "任职"
HOLD = "持股"
@dataclass
class Entity:
"""实体基类"""
id: str
name: str
type: EntityType
@dataclass
class Company(Entity):
"""公司实体"""
stock_code: Optional[str] = None
registered_capital: Optional[float] = None
industry: Optional[str] = None
@dataclass
class Person(Entity):
"""人物实体"""
position: Optional[str] = None
start_date: Optional[str] = None
end_date: Optional[str] = None
@dataclass
class Relation:
"""关系定义"""
source_id: str
target_id: str
relation_type: RelationType
properties: dict = None
# 使用示例
company = Company(
id="C001",
name="腾讯控股",
type=EntityType.COMPANY,
stock_code="0700.HK",
registered_capital=1000000.0
)
person = Person(
id="P001",
name="马化腾",
type=EntityType.PERSON,
position="董事会主席"
)
relation = Relation(
source_id=person.id,
target_id=company.id,
relation_type=RelationType.EMPLOY,
properties={"start_date": "1998-11-01"}
)
我的习惯:本体定义一定要写单元测试。我每次改完本体,都会跑一遍测试,确保所有约束都生效。别嫌麻烦,后面能省很多事。
好了,这一章的内容就到这儿。本体设计是个需要反复打磨的过程,别指望一次就完美。记住:先搭骨架,再填血肉。下一章咱们聊聊数据采集和清洗,那又是另一番天地了。
公众号:蓝海资料掘金营,微信deep3321