Files
PlantAI-rag/docs/history/PC_IMPLEMENTATION_PLAN.md
2026-07-10 17:59:59 +08:00

14 KiB
Executable File
Raw Permalink Blame History

PC 实现方案详细设计

PC 数据库特征分析

基于对 PC 数据库的观察,PC 的实现方式具有以下特征:

1. PC 的核心字段

- source: 源实体 (如 "32 SPL gene(s) (CiSPLs)")
- source type: 源实体类型 (如 "gene", "gene identifier")
- relationship: 关系 (如 "identified in")
- target: 目标实体 (如 "pecan genome")
- target type: 目标实体类型 (如 "organism")
- p_source: 章节来源 (如 "34966577_abstract")
- basis: 证据文本
- species: 物种

2. PC 的实体识别特点

复合实体识别:

  • PC 识别 "32 SPL gene(s) (CiSPLs)" 作为一个整体
  • PC 识别 "SPL gene(s) family" 作为群体实体
  • PC 识别 "nine segmentally duplicated gene(s) pairs" 作为复合概念

实体类型丰富:

  • gene: 单个基因
  • gene identifier: 基因标识符
  • phenotype: 表型
  • process: 过程
  • organism: 生物体
  • treatment: 处理条件

3. PC 的关系抽取特点

基于证据文本:

  • PC 的 basis 字段包含完整的证据句子
  • 关系从证据句子中抽取

章节溯源:

  • PC 记录关系来源的章节 (p_source)
  • 如 34966577_abstract, 34966577_results1

关系类型标准化:

  • PC 使用标准化的关系类型
  • 如 identified in, collinear with, involved in

最接近 PC 的详细实现方案

方案概述

采用 "复合实体识别 + 基于证据的关系抽取 + 章节溯源" 的三层架构

第一层:复合实体识别

1.1 实体类型扩展

ENTITY_TYPES = {
    # 基础实体
    'gene': r'\b[A-Z][a-z]{1,2}[A-Z]{1,4}\d{1,4}[a-z]?\b',
    
    # 复合实体 - 数量+基因
    'gene_group_count': r'(\d+)\s+([A-Z][a-z]{1,2}[A-Z]{1,4})\s+gene\(s\)',
    
    # 复合实体 - 数量+基因+别名
    'gene_group_with_alias': r'(\d+)\s+([A-Z][a-z]{1,2}[A-Z]{1,4})\s+gene\(s\)\s+\(\s*([A-Z][a-z]{1,2}[A-Z]{1,4})s\s*\)',
    
    # 复合实体 - 基因家族
    'gene_family': r'([A-Z][a-z]{1,2}[A-Z]{1,4})\s+gene\(s\)\s+family',
    
    # 复合实体 - 重复基因对
    'duplicated_gene_pairs': r'(\d+|\w+)\s+segmentally\s+duplicated\s+gene\(s\)?\s+pairs?',
    
    # 复合实体 - 共线性基因对
    'collinear_gene_pairs': r'(\d+)\s+collinear\s+gene\(s\)?\s+pairs?',
    
    # 转录因子群体
    'transcription_factor_group': r'([A-Z][a-z]{1,2}[A-Z]{1,4})\s+transcription\s+factors?',
}

1.2 实体识别流程

class CompositeEntityRecognizer:
    def recognize(self, text):
        entities = []
        
        # 步骤1: 识别基础基因实体
        for match in re.finditer(ENTITY_TYPES['gene'], text):
            entities.append({
                'name': match.group(),
                'type': 'gene',
                'start': match.start(),
                'end': match.end()
            })
        
        # 步骤2: 识别复合实体
        for match in re.finditer(ENTITY_TYPES['gene_group_with_alias'], text):
            entities.append({
                'name': f"{match.group(1)} {match.group(2)} gene(s) ({match.group(3)}s)",
                'type': 'gene_group',
                'start': match.start(),
                'end': match.end(),
                'count': match.group(1),
                'family': match.group(2),
                'alias': match.group(3)
            })
        
        # 步骤3: 实体链接和消歧
        # 将 "CiSPLs" 链接到 "32 SPL gene(s) (CiSPLs)"
        for entity in entities:
            if entity['type'] == 'gene_group':
                # 找到该群体中的所有单个基因
                member_genes = self._find_member_genes(text, entity)
                entity['member_genes'] = member_genes
        
        return entities
    
    def _find_member_genes(self, text, group_entity):
        """找到复合实体中的成员基因"""
        # 例如从 "32 SPL gene(s) (CiSPLs)" 中找到所有 CiSPL 基因
        alias = group_entity.get('alias', '')
        pattern = rf'{alias}\d+[a-z]?'
        return re.findall(pattern, text)

第二层:基于证据的关系抽取

2.1 证据句子提取

class EvidenceExtractor:
    def extract_evidence_sentences(self, text, entity):
        """提取包含实体的证据句子"""
        sentences = []
        
        # 分割句子
        sentence_pattern = r'[^.!?]+[.!?]+'
        all_sentences = re.findall(sentence_pattern, text)
        
        for sent in all_sentences:
            # 检查句子是否包含实体
            if entity['name'] in sent or self._entity_in_sentence(entity, sent):
                sentences.append({
                    'text': sent.strip(),
                    'entity': entity,
                    'confidence': self._calculate_confidence(entity, sent)
                })
        
        return sentences
    
    def _entity_in_sentence(self, entity, sentence):
        """检查实体是否在句子中(支持别名匹配)"""
        # 基础名称匹配
        if entity['name'] in sentence:
            return True
        
        # 别名匹配
        if entity.get('alias') and entity['alias'] in sentence:
            return True
        
        # 成员基因匹配
        if entity.get('member_genes'):
            for gene in entity['member_genes']:
                if gene in sentence:
                    return True
        
        return False

2.2 关系模式匹配(PC风格)

PC_RELATION_PATTERNS = [
    # identified in 模式
    {
        'relation': 'identified in',
        'patterns': [
            r'(\d+\s+[A-Z][a-z]+[A-Z]+\s+gene\(s\)\s+\([A-Z][a-z]+[A-Z]+s\))\s+were\s+identified\s+in\s+([^\.]+)',
            r'(\d+\s+[A-Z][a-z]+[A-Z]+\s+gene\(s\))\s+were\s+identified\s+in\s+([^\.]+)',
            r'([A-Z][a-z]+[A-Z]+\s+gene\(s\))\s+were\s+identified\s+in\s+([^\.]+)',
            r'([A-Z][a-z]+[A-Z]+\d+[a-z]?)\s+was\s+identified\s+in\s+([^\.]+)',
        ],
        'source_type': 'gene_group',
        'target_type': 'organism'
    },
    
    # collinear with 模式
    {
        'relation': 'collinear with',
        'patterns': [
            r'([A-Z][a-z]+[A-Z]+\d+[a-z]?)\s+(?:is|was)\s+collinear\s+with\s+([A-Z][a-z]+[A-Z]+\d+[a-z]?)',
            # 列表形式: "CiSPL3a/AtSPL3, CiSPL4b/AtSPL5"
            r'([A-Z][a-z]+[A-Z]+\d+[a-z]?)/([A-Z][a-z]+[A-Z]+\d+[a-z]?)',
        ],
        'source_type': 'gene',
        'target_type': 'gene'
    },
    
    # involved in 模式
    {
        'relation': 'involved in',
        'patterns': [
            r'([A-Z][a-z]+[A-Z]+\s+gene\(s\)\s+family)\s+(?:is|are)\s+involved\s+in\s+([^\.]+)',
            r'([A-Z][a-z]+[A-Z]+\s+gene\(s\))\s+(?:is|are)\s+involved\s+in\s+([^\.]+)',
            r'([A-Z][a-z]+[A-Z]+\d+[a-z]?)\s+(?:is|are)\s+involved\s+in\s+([^\.]+)',
            # 被动形式
            r'([A-Z][a-z]+[A-Z]+\d+[a-z]?)\s+.*?\s+involved\s+in\s+([^\.]+)',
        ],
        'source_type': 'gene',
        'target_type': 'process'
    },
    
    # contain 模式
    {
        'relation': 'contain',
        'patterns': [
            r'([A-Z][a-z]+[A-Z]+\s+gene\(s\))\s+contain\s+([^\.]+)',
            r'([A-Z][a-z]+[A-Z]+\d+[a-z]?)\s+contains?\s+([^\.]+)',
            r'([A-Z][a-z]+[A-Z]+\s+gene\(s\))\s+had\s+([^\.]+)',
        ],
        'source_type': 'gene',
        'target_type': 'attribute'
    },
]

第三层:章节溯源

class SectionTracker:
    def __init__(self):
        self.section_markers = {
            'abstract': r'ABSTRACT|Abstract',
            'intro': r'INTRODUCTION|Introduction',
            'methods': r'MATERIALS\s*&\s*METHODS|Methods',
            'results': r'RESULTS|Results',
            'discussion': r'DISCUSSION|Discussion',
            'conclusion': r'CONCLUSION|Conclusion',
            'references': r'REFERENCES|References',
        }
    
    def track_sections(self, text):
        """追踪文本中的章节"""
        sections = []
        current_pos = 0
        
        for section_name, pattern in self.section_markers.items():
            for match in re.finditer(pattern, text, re.IGNORECASE):
                sections.append({
                    'name': section_name,
                    'start': match.start(),
                    'end': len(text)  # 临时,后续会更新
                })
        
        # 排序并计算章节边界
        sections.sort(key=lambda x: x['start'])
        for i in range(len(sections) - 1):
            sections[i]['end'] = sections[i + 1]['start']
        
        return sections
    
    def get_section_for_position(self, position, sections):
        """获取指定位置所属的章节"""
        for section in sections:
            if section['start'] <= position < section['end']:
                return section['name']
        return 'unknown'


### 第四层:关系抽取流程

```python
class PCKnowledgeExtractor:
    """PC 风格的知识抽取器"""
    
    def __init__(self):
        self.entity_recognizer = CompositeEntityRecognizer()
        self.evidence_extractor = EvidenceExtractor()
        self.section_tracker = SectionTracker()
    
    def extract(self, text, pmid):
        """主抽取流程"""
        relations = []
        
        # 步骤1: 章节追踪
        sections = self.section_tracker.track_sections(text)
        
        # 步骤2: 实体识别
        entities = self.entity_recognizer.recognize(text)
        
        # 步骤3: 对每个实体抽取关系
        for entity in entities:
            # 提取证据句子
            evidence_sentences = self.evidence_extractor.extract_evidence_sentences(text, entity)
            
            for evidence in evidence_sentences:
                # 匹配关系模式
                for pattern_def in PC_RELATION_PATTERNS:
                    for pattern in pattern_def['patterns']:
                        matches = re.finditer(pattern, evidence['text'], re.IGNORECASE)
                        
                        for match in matches:
                            # 确定源和目标
                            if entity['name'] in match.group(1) or match.group(1) in entity['name']:
                                source = entity['name']
                                target = match.group(2).strip()
                            else:
                                continue
                            
                            # 确定章节
                            section = self.section_tracker.get_section_for_position(
                                evidence['text'].index(match.group(0)),
                                sections
                            )
                            
                            # 构建关系
                            relation = {
                                'source': source,
                                'source_type': pattern_def['source_type'],
                                'relation': pattern_def['relation'],
                                'target': target,
                                'target_type': pattern_def['target_type'],
                                'evidence': evidence['text'],
                                'section': f"{pmid}_{section}",
                                'pmid': pmid,
                                'confidence': evidence['confidence']
                            }
                            
                            relations.append(relation)
        
        return relations

关键改进点

1. 复合实体识别

PC 做法: 识别 "32 SPL gene(s) (CiSPLs)" 作为一个整体实体

V13 改进:

# 添加复合实体模式
r'(\d+)\s+([A-Z][a-z]+[A-Z]+)\s+gene\(s\)\s+\(\s*([A-Z][a-z]+[A-Z]+)s\s*\)'

# 识别结果
{
    'name': '32 SPL gene(s) (CiSPLs)',
    'type': 'gene_group',
    'count': '32',
    'family': 'SPL',
    'alias': 'CiSPL',
    'member_genes': ['CiSPL2a', 'CiSPL2b', ...]  # 自动提取成员
}

2. 实体链接

PC 做法: 将 "CiSPLs" 链接到 "32 SPL gene(s) (CiSPLs)"

V13 改进:

# 建立实体链接
for entity in entities:
    if entity['type'] == 'gene_group':
        # 找到所有别名引用
        alias_pattern = rf"\b{entity['alias']}s?\b"
        for match in re.finditer(alias_pattern, text):
            # 链接到复合实体
            entity['references'].append({
                'text': match.group(),
                'position': match.start()
            })

3. 列表形式关系识别

PC 做法: 识别 "CiSPL3a/AtSPL3, CiSPL4b/AtSPL5" 中的共线性关系

V13 改进:

# 列表形式模式
r'([A-Z][a-z]+[A-Z]+\d+[a-z]?)/([A-Z][a-z]+[A-Z]+\d+[a-z]?)'

# 从列表中提取多个关系
pairs = re.findall(pattern, "CiSPL3a/AtSPL3, CiSPL4b/AtSPL5, CiSPL6a/AtSPL6")
for source, target in pairs:
    relations.append({
        'source': source,
        'relation': 'collinear_with',
        'target': target
    })

4. 灵活的模式匹配

PC 做法: 支持多种表达方式

  • "CiSPL gene(s) contain..."
  • "CiSPL genes contained..."
  • "The CiSPL genes had..."

V13 改进:

# 使用词干提取或模糊匹配
from nltk.stem import PorterStemmer

stemmer = PorterStemmer()

# 匹配 "contain", "contained", "contains", "had" 等
contain_stems = ['contain', 'contained', 'contains', 'had', 'have']

实施优先级

高优先级(立即实施)

  1. 复合实体识别: 识别 "32 SPL gene(s) (CiSPLs)" 等
  2. 列表形式关系: 识别 "CiSPL3a/AtSPL3" 共线性
  3. 实体链接: 将别名链接到复合实体

中优先级(后续实施)

  1. 章节溯源: 记录关系来源章节
  2. 证据句子提取: 提取完整证据文本
  3. 词形还原: 支持时态/词形变化

低优先级(可选)

  1. 人工校验接口: 支持人工确认关系
  2. 机器学习优化: 使用 NER 模型提升识别

预期效果

实施该方案后,预计:

指标 当前 预期
覆盖率 19.9% 35-40%
identified in 0 15-18
collinear with 0 12-14
involved in 0 10-12
contain 0 5-6

关键改进: 能够识别复合实体和列表形式的关系,大幅提升 PC 关键关系的覆盖率。