参考

参考文献和链接

jsr 385:

ref:https://jcp.org/en/jsr/detail?id=385 unit of measurement api 2.0

JSR 363:

ref:https://jcp.org/en/jsr/detail?id=363 unit of measurement api 1.0

物理量 纯文本表示方法

ref:UCUM ref:The Unified Code for Units of Measure 规范解说 作用:物理量的编写规范(在 unicode 领域),机器间物理量传输工具

名词

专业术语

英语汉语备注
document-level processing
data interdependencies数据相互依赖前后文本数据的相互关联性

文件解析

  • 解析工具

    • chemdataextractor.reader.*

eg:

1
2
3
4
5
6
7
>>> from chemdataextractor import Document
>>> f = open('paper.html', 'rb')
>>> doc = Document.from_file(f)

>>> from chemdataextractor.reader import RscHtmlReader
>>> f = open('rsc_article.html', 'rb')
>>> doc = Document.from_file(f, readers=[RscHtmlReader()])
  • 注意

    • 读取模式必须是 'rb'

解析结果 Document 对象

  • 解析结果存储在 Document 对象中
  • Document 属性

    • paragraphs
    • cems
    • abbreviation_​definitions
    • 注意

      • 没有的属性
      • tokens
      • sentences

段落

  • doc.elements

    • 一个列表
  • 段落获取 para = doc.elements[i]
  • 存储信息

    • para.sentences: List[Sentence]

      • 句子列表
    • para.tokens

      • token 列表
      • eg: [[Token('1,4-Dibromoanthracene', 0, 21), Token('was', 22, 25),]
    • para.cems

      • chemical 列表
      • Chemical Entity Mension(化学物质实体提到)
    • para.abbreviation_​definitions()

      • 缩写

解析器添加流程

  • 新建 Model 类

    • 用于添加到 Document.models: List[BaseModel]
  • model 中 添加 Parser

model

QuantityModel

用来解析物理量 (property, value, unit) 三元组

字段:

  • raw_​value
  • raw_​unites
  • value

    • 提取后的(可以直接使用的)值
  • units

    • 真正的值
  • specifier

    • Optional 可选
    • 与 autoparsers 配合使用

      • StringType(updatable=True) 时,

        • 通过 model.definitions, BaseModel.update 实现
        • 缩写代称等,更新 parse_​expression
        • 或运算,加入 specifier(缩写代称,如:希腊字母、Tc、Tn 等)
      • StringType(required=True), 过滤不合法匹配
      • StringType(parse_​expression), 需要 add_​action(join)

        • eg:

          1
          
          specifier_expr = ((I('Glass') + I('transition') + I('temperature')) | I('Tg')).add_action(join)
  • compound

    • 需要关联 化合物时

Dimension 类

  • 用途

    • 表示物理尺寸,physical dimension
  • 字段

    • constituent_​dimensions

      • 用来创建复合量纲

        • eg:

          1
          2
          
          class Speed(Dimension):
              constituent_dimensions = Length() / Time()
    • _​dimensions

      • 用来展示符合量纲
      • eg:

        1
        
        Speed._dimensions = {Length(): 1.0, Time(): -1.0}
      • 注意

        • 不用用户手动设置
    • units_​dict = {}

      • 用来提取这些复合量纲
      • 类型

        1
        
        Dict[chemdataextractor.parse.element, Union[chemdataextractor.model.units.unit.Unit, None]]
      • eg:

        1
        2
        3
        4
        5
        
        # 温度的提取
        units_dict = {R('°?(((K|k)elvin(s)?)|K)\.?', group=0): Kelvin,
              R('(°C|((C|c)elsius))\.?', group=0): Celsius,
              R('°?((F|f)ahrenheit|F)\.?', group=0): Fahrenheit,
              R('°|C', group=0): None}
        • 注意

          • 使用 regex 时,不要带 $, 避免停止后续匹配

安装 installation

chemdataextractor2

  • 日期: 2021.11

    • python 版本要求

      • python 3.7
    • 依赖安装

      • pip install /path/to/tableextractor
      • pip install /path/to/chemdataextractor2

组件

测试数据集

  • 开放 chemical ner

    • CHEMDNER

论文

chemdataextractor 1

论文名称:

ChemDataExtractor: A Toolkit for Automated Extraction of Chemical Information from the Scientific Literature

抽取对象

自动提取

  1. chemical entities
  2. associated properties
  3. measures
  4. procedures
  5. relations

特性

  1. extensible
  2. chemistry-aware
  3. natural language processing pipeline (nlp pipeline)
  4. 额外功能

    • table parser: 提取半结构化的信息

      a table parser for extracting information from
      semi-structured tabulated data
      
    • document-level 处理后算法(post-processing)

      document-level post-processing algorithms to resolve
      data interdependencies between information extracted from different parts of a document
      

      解析 interdepencies 信息,解决数据互依赖性

流程 pipeline

  1. tokennization
  2. part of speech tagging (POS tagging)

    • chemical aware POS
  3. named entity recognition (NER taggging)

    • combine CRF model and dictionary
  4. phrase parsing

    • rule-based grammar

chemical entity recognition (chemical NER)

  1. 使用 非监督学习 聚类(unsupervised word clustering)

    • 以提升机器学习性能

      to improve performance of machine learning methods through unsupervised training
      
  2. 基于大化学文献语料库 (based on a massive corpus chemistry articles)

Phrase Parsing and Information Extraction

实现原理:多种基于规则的语法分析

the novel use of multiple rule-based grammars that are tailored for
interpreting specific document domains such as textual paragraphs,
captions, tables.

即:对于不同的文本类型(document domains),段落、标题、表格等,编写特定的规则, 针对性解析处理

document level processing

用途:

  • resolve data interdepencies 解决数据的互依赖性

重要性:

  • 标题表格 常常包含化学识别符( chemical identifier )和引用
  • 化学标识符和引用常常在别的地方被定义

性能评估 performance evaluation

F1 scoreitem
93.4%chemical identifiers化学标识符
86.8%spectroscopic attributes光谱属性
91.5%chemical property attributes化学性质属性
Entity 识别评估

大语料 CHEMDNER 评估 —- 化学式结构式 NER 语料

材料基因项目

Materials Genome Initiative

large-scale data-mining for materials discovery 大规模数据挖掘和材料发现

Harvard Clean Energy Project

materials for organic photovoltaics 有机光伏材料

The Materials Project

论文:A Materials Genome Approach to Accelerating Materials Innovation focuses on battery materials

电池材料

项目类型

利用计算资源预测化学性质

  • 用到机器可理解的实验性质数据库
that would be well complemented by wider availability of machine-readable databases of
experimental properties

通用型材料数据抽取工具

通用的数据库制作和材料性质提取工具

a generic method that can automatically generate a database for any
type of material property

chemdataextractor 开发目的和软件项目类型

工具

LeadMine

在文本中,识别和标记 化学材料、靶蛋白、基因、命名反应、公司名称等等

NextMove Software's LeadMine product is a text mining tool
for the identification and annotation of
chemicals, protein targets, genes, diseases, species, named reactions,
company names, cell lines, etc.
in the text of documents.

v2.0 可以转换中文和日语的化学材料名称 2015 年论文:LeadMine: a grammar and dictionary driven approach to entity recognition

ChemicalTagger

抽取反应,给物质打上角色标记(chemical roles)

which parses experimental synthesis sections of documents to determine
chemical roles (e.g. reactant, solvent) and relationships with
experimental actions (e.g. heated, stirred)

可以与 LeadMine 一起使用抽取熔点 melting pints

使用工具:

  • ANTLR grammar

    • rule-based 文本解析
  • OSCAR

    • chemical NER
    • repo: OSCAR4
    • 底层使用 OSPIN

ChemEx

ChemicalTagger 的扩展版本

新功能:

  1. 识别 biomedical entity
  2. 2D 化学结构识别 + 底层使用 OSRA

论文:ChemEx: information extraction system for chemical data curation | BMC Bioinf…

model.units.dimension 包

Dimension 类

字段

constituent_​dimessions
  • type: Dimension
  • 用途

    • 新的复合量纲实例

      • 由子量纲运算得来
构建方法
  • dimension 实例对象间的运算
  • eg:

    • DimensionA / DimensionB

      1
      2
      
      class Speed(Dimension):
          constituent_dimensions = Length() / Time()
_dimensions
  • type: Dict[Dimension, float]
  • 用途

    • 存储 子量纲 之间的运算关系

model

字段

required

相对于上级 model(outer model)当前子 model 是否强制要求必须存在

  • type: bool

contextual

是否允许在文章的其他部分(相对于 outer model),获取当前 model 内容 (在 interdepency 解析过程中)

  • type: bool

updatable

引用文献

chemdataextractor

  • 项目论文

    • chemdataextractor2

      • ChemDataExtractor 2.0: Auto-Populated Ontologies for Materials Science
    • chemdataextractor1

      • ChemDataExtractor: A Toolkit for Automated Extraction of Chemical Information from the Scientific Literature
  • 应用

    • 本团队应用论文

      • A Design-to-Device Pipeline for Data-Driven Materials Discovery

grobid-quantity

  • 项目论文

    • Automatic Identification and Normalisation of Physical Measurements in Scientific Literature
  • 引用文献

    • Processing of quantitative expressions with units of measurement in scientific texts as applied to Belarusian and Russian text-to-speech synthesis

      • 白俄人写的论文

unit-api

  • 项目名称

    • units of measurements
  • 项目论文

    • How to Extract Unit of Measure in Scientific Documents?

      • 被 grobid-quantity 引用

解析流程分析

Document.from_file(f)

类: chemdataextractor.doc.document.Document

调用顺序:

  • Document.from_file(f)
  • Document.from_string(bytes)
  • Reader.detect

    • 被重写的方法
  • Reader.readstring(bytes)

    • bytes 是整个文档
  • Reader.parse(bytes)

    • 被重写的方法
    • 具体处理

      1. Reader._make_tree

        • 产生 root ElementTree
      2. Reader._css() 获取 root element

        • eg: <html> 标签
      3. Cleaner 调用

        • 清理和正规化
      4. 特殊类型,分类别抽取,结果类型 List[element]

        • titles
        • headings
        • figures
        • tables
        • citations

          • 参考文献
        • references

          • 链接
        • ignores
        • metadata
        • 结果存储

          • refs: List[parentElement, str]

            • 链接信息
          • specials: Dict[element, list]

            • element 可以 Hash
            • 链接以外的东西
      5. Reader._parse_element(root, specials, refs) –> Document

        • 从 root 开始解析
        • Reader._parse_element_r(elem, specials, refs, element_class)

正规化

  • Cleaner 类

SnowBall 算法

训练

  • file
  • Document object
  • Sentence object

规定

  • 句子长度 <= 300
  • 位置

    • chemdataextractor.relex

Entity

  • chemdataextractor.relex.entity.Entity
  • 用途

    • 关系抽取的基础实体
    • 实体容器
    • 实体的基本信息

      • 位置
      • 标签
      • 解析表达式 parse_expression
  • 属性

    • text

      • 文本内容
    • tag

      • 实体名称 or list
    • 位置

      • start: int
      • end: int
    • parse_expression
  • 特殊功能

    • tag

      • 嵌套 tag, __ 双下划线分割
      • eg:

        • normal_tag
        • root_tag__sub_tag
Cluster
  • chemdataextractor.relex.cluster.Cluster
  • 用途

    • 聚类
Pattern
  • chemdataextractor.relex.Pattern
  • 用途

    • snowball 算法中的 pattern 制作
  • 属性

    • snowball pattern

      • 各组成部分

        • self.elements: Dict[str, Dict]

          • {'prefix': {'tokens': [….], …}, …}
Phrase
  • chemdataextractor.relex.phrase.Phrase
  • 用途

    • 句子容器
    • 句子解析
    • 关系解析
    • 用于 snowball pattern 的聚类和抽取
    • 关系汇总工具
  • 属性

    • self.cluster_assignments: Set

      • 归属的 簇, 可以是多个
    • self.entities

      • 实体列表
    • self.tokens: List[str] | Any?
  • eg:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    class TestPhrase(unittest.TestCase):
    
        maxDiff = None
        training_corpus = 'tests/data/relex/curie_training_set/'
    
        def test_phrase_create(self):
            """Test that Candidate Relation objects are correctly created
            """
            tokens = ['this', 'is', 'a', 'test', 'phrase']
            entities = [
                Entity('this', 'who', I('this'), 0, 1),
                Entity('phrase', 'what', I('phrase'), 4, 5)]
            relations = [Relation(entities, confidence=1.0)]
            phrase = Phrase(tokens, relations, 1, 1)
            expected = '<Blank> (who) is a test (what) <Blank>'
            self.assertEqual(phrase.to_string(), expected)
Relation
  • chemdataextractor.relex.relation.Relation
  • 用途

    • 关系定义

      • 关系模式,容纳具体的关系例子
    • 关系的容器
    • Phrase 类中被调用
  • 属性

    • self.entities: List[Entity]

      • 关系中的各个实体
    • self.confidence: float

      • 这个关系的置信度
  • 方法

    • self.is_valid()

      • 同一个实体,拥有两个 tag,显然打 tag 出错,不合法

Snowball Element 解说

1
2
3
4
5
6
Cluster.dictionaries[element] = {'token dict': OrderedDict(), # 给定 element 各个 token 的 Dict[token_str, [frequeny, weight]] 参数统计
                             'unique words': [],  # Which words appear once
                             'total words': 0,    # counter 给定 element 的 所有 token 总数 sum(token_frequeny)
                             'total recurring words': 0}
#------------------
Phrase.elements['prefix'] = {'tokens': prefix_tokens} # prefix_tokens: ['That', 'is']

解释: 一个 element 就是 prefix, middle_n, suffix 之中的一个

组分:

  • 给定 Phrase 内部:

    • 相应的各个单词
    • 存储工具

      • Phrase.elements

        • 给定句子的多个 element
  • 给定 Cluster 内部

    • (cluster 包含多个 Phrase)
    • 存储工具

      • Cluster.dictionaries

        • Cluster.dictionaries[element]

          • 多个句子的 给定位置 element, 汇总
          • 统计所有给定 token 的权重 和 其他统计数据
          • eg:

             1
             2
             3
             4
             5
             6
             7
             8
             9
            10
            11
            12
            
            {
                "token_dict": {
                    "That": [               // token text
                        2,                  // freq
                        0.1                 // weight = current_freq / sum(all_freq)
                    ],
                    "is": [
                        3,
                        0.2
                    ]
                }
            }
      • Cluster.update_pattern 函数内部

        • local.vectors

          • 类型: Dict[List[List[float]]], Dict[element_name, List[Phrase_List[token_weight]]]
          • 元素:单个 element 的处理

            • self.dictionaries[element]['token_dict'] 包含了所有 Phrase 中的 token 信息
            • 建立 定长数组
            • 遍历所有 Phrase (self.phrases)
            • 给定 Phrase 中不包含 同样的 token, weight = 0
            • eg:

              1
              2
              3
              4
              5
              
              [
                  [0.1, 0.2, 0],              // 第一个 Phrase, token 顺序 That is
              
                  [0,  0.2, 0],               // 第二个 Phrase, token 顺序      is
              ]
              • 通过 self.dictionaries[element]['token_dict'].keys() token 保证遍历顺序

definitions

参见: chemdataextractor.doc.text.BaseText::definitions 行号: 690 行

样式:

1
2
3
4
5
6
7
[{
    'definition': first(definition.xpath('./phrase/text()')),
    'specifier': first(definition.xpath('./specifier/text()')),
    'tokens': tagged_tokens[start:end],
    'start': start,
    'end': end
}]

关联代码

  • Model.update(definitions) 方法

    • 更新 model 中的 updateable 字段

tagged_tokens

Sentence.tagged_tokens: List[Tuple[token, tag]]

tags: tag = pos_tag if ner_tag is None else ner_tag

句子文本处理流程

  1. Sentence(text) 对象
  2. token 化
  3. 打标签 pos_tag, ner_tag
  4. token 处理

    • definition 识别

      • 缩写
      • 代称
      • 化学名称 代称
    • 命名实体识别

      • 化学名称 cem
  5. model 匹配抽取 和 序列化(Sentence.records 方法实现)

    • model 匹配

      • 返回结果: List[Model]
    • records 处理

      • 序列化
      • 去重、去空值、去信息重叠

        • 普通 Model

          • 去重、空值
        • Compound Model

          • 去信息重叠
          • label 判断重复问题

打标签流程

在 chemdataextractor.doc.text.Sentence 中

  1. self.pos_tagger 打标签
  2. self.ner_togger 打标签
  3. 打完后

    • self.pos_tags, self.ner_tags
    • self.tagged_tokens
    • self.pos_tagged_tokens
    • self.ner_tagged_tokens

Model 抽取流程

使用 AutoSentenceParser 重组各个 parse_expression

AutoSentenceParser 继承关系:

  • BaseAutoParser(BaseParser).interpret(result, start, end)
  • BaseSentenceParser(BaseParser).parse_sentence(tokens)

抽取流程:

  1. AutoSentenceParser.root() 组建

    • 把各个字段的 parse_expression 组合在一起
    • 针对 self.model.dimensions (量纲) 特殊处理
    • 必须有 self.model.compound 字段
  2. BaseSentenceParser.parse_sentence(tokens)
  3. BaseAutoParser

    • BaseAutoParser.parse_

物理量(数值 + 单位处理流程)

粗文本处理

  • chemdataextractor.parse.quantity

    • extract_unit(text_str)

      • split_(text_str)
    • extract_value(text_str)

      • "150 to 160" –> [150, 160]

调用 extract_unit

parser.extract_unit:

  • <— parser.interpret(result, start, end) BaseParser :=> yield BaseModel

    • 粗结果 与 对应字段规则比较
    • 精准匹配抽取转换
  • <— parser.parse_sentence(tagged_tokens) BaseSentenceParser :=> yield BaseModel

    • parser.root.scan(tagged_tokens) BaseSentenceParser= :=> yield [etree.Element, start, end]

      • 数据匹配和粗抽取
      • xml 结构结果
    • model 精准结果
  • <— Sentence.records() Sentence :=> List[Model]

    • 结果清洗:去重、去空、合并、化合物去重特殊规则
    • model 的 遍历
    • Parser 的遍历
  • <— Text.records() Text :=> List[Model]

    • 其他非 Text 类,调用 Sentence.records 实现 model 抽取
  • <— Document.records Document :=> List[Model]

    • definitions 处理
    • interdepencies 互依赖的处理

互依赖性

  • 通过下面三个 BaseType 字段规定

    • required

      • 是否必须有
    • binding

      • 是否 在子 Model 中,该字段必须是相同的一个
    • contextual

      • 是否允许是上下文出现也可以

merge 问题

  • BaseModel::merge_all 引入
  • 相关函数

    • BaseModel::binding_properties
    • BaseModel::_binding_compatable(other) –> bool

      • self 和 other 类型不同 –> True
      • 绑定字段值 全相同 ,不冲突 –> True,可以合并
      • self 字段是 other 的子集(other 有不被 self 字段值包含的元素,即 self 字段值元素量小) –> False

        • BaseModel::is_superset() 和 BaseModel::is_subset()
    • BaseModel::_compatible(self, other)

      • self, other 类型一致
      • 字段值一致(相等),满足下列调节的字段

        1. not field.ignore_when_merging
        2. self[field_name] != None
      • 即,待比较 self 有的字段 other 字段不为 None,必须相等,否则 not compatible
    • BaseModel::binding_properties() –> Dict[name_str, value]

      • 找到的 field.binding = True 字段
      • 当前级别 level Model 中的字段
      • 不包含 sub model 中字段

例子:

参考:Getting Started — Step1: Defining a new property

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Pressure(QuantityModel):
    """ A boiling point property"""
    specifier = StringType(parse_expression=(I('Boiling') + I('Point')).add_action(join), required=True)
    compound = ModelType(Compound)

class BoilingPoint(TemperatureModel):
    """ A boiling point property"""
    specifier = StringType(parse_expression=(I('Boiling') + I('Point')).add_action(join), required=True)
    compound = ModelType(Compound, binding=True)
    pressure = ModelType(Pressure, contextual=True)

例子修改: 官方使用 Altitude 我认为是错误的

解说:

  • 两个 model 共同字段 compound
  • BoilingPoint::compound 字段

    • ModelType(Compound, binding=True)

      • binding

        父Model 和子Model 中给定字段的值必须相同
        
        这里 binding 规定了 当前 root_model(BoilingPoint) 中的 compound 字段
        和 sub_model(Pressure) 中的 compound 字段的值必须相同
        
  • BoilingPoint::pressure 字段

    • contextual

      • 允许在上下文中找该字段的值
  • BoilingPoint::pressure 字段

    • required

      • 是否必选

相关函数

chemdataextractor.model.base.BaseModel::binding_properties

绑定的属性

类型:Dict[name_str, value]

chemdataextractor.model.base.BaseModel::merge_all

parser 与 句法模式

句法组分

specifier 和 缩写

(缩写)

Optional(lbrct) + W('Tm') + Optional(rbrct)

归属词

Optional(lbrct + W('Tm') + rbrct) + Optional(W('=') | I('of') | I('was') | I('is') | I('at'))

=, of, was, is, at

范围

in the range of
about

api 接口

chemdataextractor.parse.elements

功能类

针对单个 expr

内部存储的一个解析规则 self.expr

Any

匹配任意 token

OneOrMore(ParseElementEnchenced)

单个 expr 第一个必须出现,后续出不出现都可

ZeroOrMore
针对多个 expr 组合

内部存储的时多个解析规则 self.exprs

And(ParseExpression)

多个 exprs 顺序出现,允许元素缺失,每个 expr 出不出现,根据对应 expr 的内部规则

  • 内部规则

    • 是否强制出现
    • 是否捕获 ParseException 异常
Or

多个 exprs 匹配最长的那一个

基类 BaseParserElement

BaseParserElement.scan(tagged_tokens)

返回类型:

  • Tuple[List[etree.Element], matched, next_index]
  • Tuple[etree.Element, matched, next_index]

逻辑:

  • 逐个 token 匹配

    • 匹配失败

      • 下一个 i += 1
    • 匹配成功,

      • 额外要求: next_i > i

        • 满足

          • 单个 Element

            • yield single_element, i, next_i
          • 多个 Element

            • yield list_element, i, next_i
          • 是否允许重叠 overlap?

            • True: i += 1
            • False: i = next_i
        • 不满足

          • i += 1
  • 遍历结束

任务(功能): 逐个 yield 匹配的 (token, i, next_i)

BaseParserElement.with_condition(condition_function)

作用: 接受一个回调函数

参数类型: condition_function: CallableTuple[List[Element], int, bool]

condition_function 的用途

  • 在 BaseParserElement.parse() 中被调用
  • 接收 BaseParserElement._parse_tokens 返回结果
  • 作用:判断 self._parse_tokens 结果 List[Element] 是否满足条件
BaseParserElement.parse(tagged_tokens, start_index)

异常: ParseException

返回值: (List[Element], next_index)

  • next_index = start_index + 1
BaseParserElement.actions 属性

设置:

  • self.add_actions(*actions)
  • self.set_actions(*actions)

单个 action

  • 类型: 回调函数, Callable[[tagged_tokens, index, List[etree.Element]]]
  • 作用 修饰 result (List[etree.Element])
BaseParserElement._parse_tokens(tagged_tokens, start_parse_index, actions=True)
  • 重载函数
  • 执行解析

结果: Tuple[List[Element], index]

chemdataextractor.parse.base

用于解析给定的 Document(Text) 文档,调用 parse_expression 遍历 tagged_tokens

chemdataextractor.parse.base.BaseParser

作用: 解析器的基类

属性:

  • self.trigger_phrase: BaseParserElement

    • 过滤器

      • 判断给定句子是否是要处理的
      • 提升解析速度
    • 类型

      • 单个匹配规则
  • self.root: BaseParserElement

    • 给定 Model 的所有规则处理后制作的匹配规则

方法:

self.interpret(result, start, end)

作用: 把 self.root.scan(tagged_tokens) 解析的结果 [List[etree.Element], start, end] 再解析成对应到 Model 的字段

特点:

  • 嵌套 Model 的处理,通过 self._get_data() 实现
  • ListType 字段处理,通过 self._get_data() 实现
  • self.model.dimenssions 处理

    • 量纲一处理
    • 包含量纲时处理
    • 无 self.model.dimensions 处理

返回: yield Model, 解析好的 Model 结果

self._get_data(field_name, field, result: etree.Element)

作用: 通过给定 field_name, field 从 etree.Element 中提取响应 字段的值,并加以检验

特点:

  • 嵌套模型处理(字段是模型)
  • required 字段处理
  • ListType 字段处理

chemdataextractor.parse.auto

AutoSentenceParser

功能: 处理句子 tagged_tokens

结果: 抽取到的 model

属性

  • self.model

    • 注意: 一个 parse 只能有一个关联 model
AutoSentenceParser.root(self)

作用:

  • 把 model 下的 BaseType 包裹的字段 遍历一遍

    • 获取 parse_expression
    • 给 parse_expression 添加名字

      • 通过 parse_expression(name) 实现

        • 背后逻辑 BaseParserElement.set_name(name) 间接调用
    • 通过遍历下面的字段实现

      • model.compound.labels
      • model.dimension
      • model.specifier
      • 其他字段

        • model.fields: dict
  • 制作 entites 列表

    • compound: labels
    • specifier: specifier
    • value_phase(物理量 值+单位), 嵌套的 parse_expression

      • raw_value
      • raw_units
      • 结构, And 运算

        • (raw_value + raw_unites)
    • 跳过的

      • value
      • error
      • 原因猜测

        • 它们是结果存储
        • 不是要直接解析的对象
  • 制作 combined_entity_list 列表
  • 通过 OneOrMore(…)('root_phase') 合在一起

注意:

  • 要求 self.model 必须有 compound 属性

chemdataextractor.model.base

chemdataextractor.model.base.ModelList

功能:

  • 存储结果 Model 对象列表

    • self.models: List[Model]
  • 序列化结果

    • json 形式

      • self.serialize()
    • dict 形式

      • self.to_json()
    • 实现方法

      • 通过 Model.serialize() 实现给定 Model 序列化
      • 通过 BaseType.serialize() 实现给定字段序列化

chemdataextractor.doc 包

chemdataextractor.doc.text.BaseText

功能:

  1. token 化

    • 指定分隔符,分割
  2. 缩写识别

    • 通过缩写词典
    • 通过括号等特殊符号
  3. 别称

    • self.definitions
  4. 打标签 tagger (pos 和 ner)
  5. 化学材料识别
  6. 物理量解析?

    • self.quantity_re
    • chemdataextractor.parse.quantity.construct_quantity_re 函数
  7. 规则抽取

    • 使用 各个 model 字段规则抽取数据
    • 序列化成 List[Model]

属性:

  • tagger

    • 属性方法:

      • self.pos_tags
      • self.ner_tags
    • 属性:

      • self.pos_tagger
      • self.ner_tagger
      • self.tags
  • 缩写

    • 属性方法

      • self.abbreviation_definitions
    • 属性

      • self.abbreviation_detector
  • 化学材料

    • self.cems()
  • 别称

    • self.definitions()
    • self.chemical_definitions()
  • 物理量 quantity

    • self.quantity_re()

      • 一个 model_list 的 dimensions.units_dict 规则叠加
  • 抽取结果

    • self.records()

      • 遍历 self._streamed_lined_models 中的 model
      • 遍历 model 下的 parsers
      • 得到多个 record
      • 多个 records 消减合并(结果非单个)

        • 空值删除
        • 重复值删除
        • Compound Model 问题

          • 删除重复: labels 集合判断 是否已经搜集过
          • 更新信息重叠: 新 record 与 旧 record, names 有交集 或者 labels 有交集

            • 把 新 record 的信息 (names, labels, roles) 更新(并集 union)到旧 record 中
      • 返回类型 List[Model]

        • 一个 Model 对应 一个 Record
chemdataextractor.doc.document.Document

属性:

  • self.elements: List[Text]
方法
Document.from_file

作用:

  • 注册 file 对象
  • 读取二进制内容
  • 传递给 Document.from_string

接受类型: Union[file_object, str]

Document.from_string

作用:

  • 准备 Reader,选择合适的 Reader, 通过 Reader.detect() 实现
  • 调用 Reader.readstring(bytes) 开始解析

接受类型: ByteString(bytes)

chemdataextractor.doc.text.Sentence

句子的文本容器和解析工具

字段:

  • self.definitions: List[Dict]

    1
    2
    3
    4
    5
    6
    
    [{
        'definition': first(definition.xpath('./phrase/text()')),
        'specifier': first(definition.xpath('./specifier/text()')),
        'tokens': tagged_tokens[start:end],
        'start': start,
        'end': end}, ...]

chemdataextractor.reader 包

继承关系

  • 抽象类

    • BaseReader –> LxmlReader
  • LxmlReader

    • 使用 css 选择器
具体类

需要实现的方法:

  • def detect(self, fstring, fname) –> bool

    • 判断扩展名等等
  • def _make_tree(self, fstring: bytes) –> lxml.etree.ElementTree

    • 产生 root
  • def parse(self, fstring: bytes) –> chemdataextractor.doc.Document

    • 执行解析
XmlReader
HtmlReader
chemdataextractor.reader.markup.LxmlReader

入口方法: self.parse(fstring) 完成功能:

  • xml 解析

    • 构建一个 etree Element root
    • 解析 etree.Element root, 制作 List[Text]
    • 使用 List[Text] 构建 Document 对象
  • 数据清洗

    • 丢弃空元素 Text('')
    • Text.text.strip()

      • strip 掉 空白符
方法
  • self._xpath(…) –> List[etree.Element]
  • self._css(…) –> List[etree.Element]
  • self._parse_element_r(etree.Element, …) –> List[Text]

    • Text, base class of Title, Paragraph, Heading, Sentence
  • self._parse_element(etee.Element, …) –> List[Text]
  • self._parse_text(etree.Element, …) –> List[Text]

    • return single Text object, [Text]
Reader._parse_element_r

代码

 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
from lxml import etree


def _parse_element_r(self, el, specials, refs, id=None, element_cls=Paragraph):
    """Recursively parse HTML/XML element and its children into a list of Document elements."""

    # 结果
    elements = []

    # drop 无效 Element
    if el.tag in {etree.Comment, etree.ProcessingInstruction}:
        return []
    # if el in refs:
    #     return [element_cls('', references=refs[el])]

    # 跳过特殊元素 specials: Dict[str, List[Text]]
    if el in specials:
        return specials[el]

    id = el.get('id', id)

    # 属于当前 `el`(element) 的 链接
    references = refs.get(el, [])

    # * 文本处理 ----------------------------------
    # 1) 全部文本 elem.text
    # 2) 构建 element_cls 类
    if el.text is not None:
        elements.append(element_cls(six.text_type(el.text), id=id, references=references))
    elif references:
        # 3) 不存在文本 elem.text
        elements.append(element_cls('', id=id, references=references))

    # 递归 子类型处理
    for child in el:
        # 换行标签 <br/> 处理
        # br is a special case - technically inline, but we want to split
        if child.tag not in {etree.Comment, etree.ProcessingInstruction} and child.tag.lower() == 'br':
            elements.append(element_cls(''))

        child_elements = self._parse_element_r(child, specials=specials, refs=refs, id=id, element_cls=element_cls)
        if (self._is_inline(child) and len(elements) > 0 and len(child_elements) > 0 and
                isinstance(elements[-1], (Text, Sentence)) and isinstance(child_elements[0], (Text, Sentence)) and
                type(elements[-1]) == type(child_elements[0])):
            elements[-1] += child_elements.pop(0)
        elements.extend(child_elements)
        if child.tail is not None:
            if self._is_inline(child) and len(elements) > 0 and isinstance(elements[-1], element_cls):
                elements[-1] += element_cls(six.text_type(child.tail), id=id)
            else:
                elements.append(element_cls(six.text_type(child.tail), id=id))

chemdataextractor.scrape 包

chemdataextractor.scrape.clean.Cleaner 类

作用:

  • 删除无用标签,eg: <script>, style, …
  • 用 text 替代整个标签

chemdataextractor.relex 包

chemdataextractor.relex.Snowball

方法
Snowball.candidates(tagged_tokens)

功能: 处理一个句子的 tagged_tokens 抽取关系,可以是多个

entities 制作

  • 类型: chemdataextractor.relex.Entity 类
  • AutoSentenceParser.root.scan(tagged_tokens) 扫描
  • self.retrieve_entities(…) 制作粗 entities 原料

    • (text, (model.__name__.lower(), tag), field.expression)

      • tag: field name in model
  • KnuthMorrisPratt 产生 开始位置等,index 参数
  • tag 转换

    • (model.__name__.lower(), tag) –> model_name__field_name
  • Entity 类对象创建
  • entity_dic: Dict[tag, List[Entity]] 创建
  • 重复删除: 多余删除,一个 (model_name, field_name) 多个 entity 选择性抛弃

    • 准则: 同一个 tag, index 位置交叠判断

      • start 相同时
      • to_pop.append([i, j][np.argmin([entities[i].end, entities[j].end])])

        • 舍弃长度短的
  • 组分不完整删除:

    • 针对 root_model
    • self.filter_incomplete(model, entities_dict)

      • 支持嵌套 model 处理
      • 给定 field 是 required, 并且 相应 model_name__field 不在 entities_dict.keys() 中

        • 出删除 该 model 所有 entities
    • 即:

      • 一个子 model (是否 required,未定)
      • 这个子 model 如果含有 required 字段 field, 没有获取到
      • 整体删除这个 子 model 的所有字段 start_with(model_name + '__')
    • 解说

      • 因为 entities 里面存储的是 model__name__field_name

        • 所以是遍历的各个 子model 的普通字段完整性 和 当前 root model 普通字段的完整行
      • 没有遍历 model 是 required 这种嵌套情况

        • 这种通过 self.required_fulfilled(model, entities_dict) 覆盖
  • 校验 嵌套 model, 检验 field 完整性

    • self.required_fulfilled(model, entities_dict) –> bool
    • 校验不通过,当前句子 直接失败, self.candidates(tagged_tokens) –> []
  • 排序(all_entities)

    • 给定 tag (model_name__field_name), 列表内, 按 List[Entity] 按 entity.start 排序
  • 笛卡尔积制作

    • product(*all_entities)
    • 展平后的笛卡尔积
    • 用处,model 解析结果列表形式,转换成 各字段都是单个的形式

      • 可以产生针对 多对多 潜在形式的枚举
    • 结果: List[List[Entity]]

      • 实体的笛卡尔积结果
      • 一个各个 tag 都存在的 实体列表
      • List[Candidate]
      • Candidate: List[Entity]
  • 关系构建

    • 单个 condidate 内部 实体排序
    • chemdataextractor.relex.Relation 类
    • 置信度初始化 confidence=0

      • r = Relation(candidate, confidence=0)
    • 关系有效性判断

      • 不同 tag(model_name__field_name), 相同 index 非法

        • Relation 内部实际上都是不同 tag,天然满足 tag 不同
      • e1.tag != e2.tag and e1.start == e2.start and e1.end == e2.end

        • index 重叠非法
Snowball.train_from_sentence(Sentence)
Snowball.parse_sentence(tokens)

功能:

  • 仿造 chemda

逻辑:

  • self.candidates(tagged_tokens) ==> List[Relation(entities, confidence=0)] cde 抽取结果

    • 未标注初始化 Relation.confidence = 0 不可信
  • unique_names

    • 不重复的化合物名称 compound name
    • 用来做阈值判断

      • num_candicates * num_of_unique_names <= self.max_candidate_combination
  • 制作 candiate 组合: all_combs
  • candidate_phrase 创建

    • 通过 单个 combination 和 not_tagged_tokens 一起创建
    • candidate_phrase = Phrase([t[0] for t in tokens], new_rels, self.prefix_length, self.suffix_length)
    • 作用:?

      • 通过化合物 compound 数量,来创建 Relation 组合
      • compound 数量决定 最大组合元素数量
  • 标注

    • 可以是多个,eg: "1,3,5"
    • 初始化: chosen_relation.condidence = 1.0

      • 置信度 修正
  • 给定句子 Snowball 关系更新

    • self.update(sent.raw_tokens, chosen_relations)

      • 聚类使用 self.cluster(Phase(sent.raw_tokens, Relation, self.prefix_length, self.suffix_length))
Snowball.update(sent_text_tokens, chosen_condidates)

功能:

  • new_phrase 创建:

    • 把当前句子解析到的 Relations 包装成 Phrase
  • 聚类:

    • 放入 self.cluster(new_phrase) 方法中聚类
Snowball.cluster(phrase)

功能:

  • 聚类
  • 把 新 phrase 添加如 self.clusters 属性内
Snowball.classify(phrase)

功能: 给 phrase 分类到对应的 cluster, 或这新建一个 cluster 逻辑:

  • 逐个 cluster 对比,获取相似度 similarity
  • 检验标准: if similarity >= self.minimum_cluster_similarity_score:
  • 比较结果

    • 相似: 添加到给定 cluster
    • 否则: 新建一个 cluster

chemdataextractor.relex.Phrase

功能:

  • suffix_tokens、middle_tokens、suffix_tokens 制作
  • 一个句子的多个 Relation 容器
  • 用于 cluster 和 抽取 pattern: <prefix> Tag_Entity <middle> Tag_Entity <suffix>

接口:

  • __init__(self, sent_raw_tokens, list_phases, prefix_length, suffix_length)

    • 句子信息, 解析的关系, 前缀长度限制,后缀长度限制

属性:

  • sent_raw_tokens
  • full_sentence 句子文本
  • self.relations

    • 一个句子的所有 chosen_relations
  • order

    • 排序的 tag
  • entities

    • 排序的 Entity
  • self.elements

    • 类型: Dict[str, List[{"tokens": List[text_token] }]]
    • 例子: {"prefix": [{"tokens": ["That", "is"]}]}

方法:

  • self.create()

    • 按 entity.tag(model_name__field_name) 计数
    • 所有 relations 展开到 combined_entity_list
    • combined_entity_list 排序
    • 排序的 tag : self.order
    • prefix_tokens 制作

      • 通过 sorted_entity_list[0].start 制作
      • 空 –> ["<Blank>"]
    • suffix_tokens 制作

      • 通过 sorted_entity_list[-1].end 制作
      • 空 –> ["<Blank>"]
    • middle_tokens

      • 便利 sorted_entity_list 制作
    • prefix_tokens、midlde_tokens、suffix_tokens 存储

      • self.elements: Dict[fix_name_str, Dict['tokens', List[str_token]]]

        • fix_name_str: eg: "prefix", "suffix", "middle_1", "middle_2", …
        • Dict['tokens', List[str_token]]:

          • {"tokens": ["<BLANK>"]}
          • {"tokens": ["that", "is"]}

chemdataextractor.relex.Relation

功能:

  • 一个句子的 不同 tag 实体的结合
  • 即,给定 model 的不同 field 的抽取结果容器

接口: Relation(entities: List[Entity], confidence: float)

方法:

  • is_valid

    • 不允许两个 entity 重叠
    • if e1.tag != e2.tag and e1.start == e2.start and e1.end == e2.end:

chemdataextractor.relex.Cluster

功能:

  • 单个聚类的簇
  • 相似 Phrase 收集容器
属性:
self.dictionaries
  • 存储给定 (prefix, middle_1, middle_2, suffix) 的 token 信息

    • 指的是 element
  • 类型:

    1
    2
    3
    4
    
    self.dictionaries[element] = {'token dict': OrderedDict(), # 给定 element 各个 token 的 Dict[token_str, [frequeny, weight]] 参数统计
                                  'unique words': [],  # Which words appear once
                                  'total words': 0,    # counter 给定 element 的 所有 token 总数 sum(token_frequeny)
                                  'total recurring words': 0}  # counter
    • frequeny: int
    • weight: float 分数

      • frequeny / total_words
  • 添加 和 更新 tokens 参数

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    @staticmethod
    def add_tokens(dictionary, tokens):
        for token in tokens:
            if token not in dictionary['token dict'].keys():
                dictionary['total words'] += 1
                dictionary['token dict'][token] = [1.0, 0]  # [frequeny, weight]
            else:
                dictionary['total words'] += 1
                dictionary['token dict'][token][0] += 1
        return
方法:
add_phrase()
  • 把 phrase 放入到 self.dictionaries
  • 更新计算 element 对应的 token 的 频数和权重 frequeny, weight
update_pattern()
  • 制作 vectors: List[{element_key: List[List[token_weight]]}]

    • 作用: 统计给定 element 的 各个 token 的权重 weight
    • 针对 self.phrases 制作 vectors

      • 遍历 self.phrases
      • 遍历 phrase.elemens.keys()
      • 遍历 self.dictionaries[element], 内部 token_weight
    • 单个 element, 多个 phrase, 多个 tokens

      • 因此 vectors[element] 对应的是
      • 给定 element, 不同 phrase, 不同 token 内部的 weight 二维阵列(2d array)

        • 行: phrase
        • 列: token 的 weight 权重
  • 形心(centroid)查找

    • 单个形心:

      • 给定 vector[element]: 2d array
      • 频率最高的行计算 element_mode = mode_rows(2d_array)
      • KDtree.query 查找 形心 Phrase(即 row_id)
      • 提取 element, self.phrases[medoid_idx].elements[element]
    • 存储

      • 结果形式: 找到给定 phrase

        • pattern_elements[element] = self.phrases[medoid_idx].elements[element]
        • 单个形心 List[token_str]
        • 整体 Dict[element_name, List[token_str]]
    • 结果存放

      • pattern_elements
      • self.pattern = Pattern(pattern_elements, self.entities, ....)
update_pattern_confidence()
  • 功能:

    • 计算 形心 置信度 confidence
    • new_confidence = len(centroid_found_relations) / sum(len(all_phrases.relations))

      • (遍历所有 phrases, 形心发现的 relation 总和) / (原来所有 phrases 已经发现的 relations 总和)
    • 置信度更新,与 self.learnning_rate 有关

      1
      
      self.pattern.confidence = self.learning_rate*new_pattern_confidence + (1.0 - self.learning_rate)*self.old_pattern_confidence
get_relations(sent_tagged_tokens)
  • 遍历给定 Pattern.parse_expression.scan(tokens) 抽取结果

    • 理论上,应当是只有一个
    • 给定 match, 遍历 Pattern.relations

      • 给定 relation, 遍历 Entities

        • 把 Entity 放入 entity_type_indexes: Dict[str, List[Entity]], Dict['entity_tag', List[Entity]]

          • 作用:

            • 多个 Relation 的 Entity 存在一起
            • Entity.tag 分类, 保证了 同一个句子, Entity.tag 一致,List[Entity] 内部顺序就是 Pattern.relation 顺序
        • 使用 Entity.tag 做 xpath, 在 match(etree 类型) 中查找匹配结果

          • 注意:多结果性
        • 因为 一个句子(Pattern) 允许多个 Relation, 相同的 Entity.tag 可能存在多个结果 在 match 中
        • 使用 Pattern 中 Entity 的 index 在 Phrase(即 res[0]) 的 match_xpath_found_results 中 查找中 对应 Entity

          • Pattern entity 查找位置
          • Phrase 匹配 xpath 结果 对应 真实的 entity
      • 找到 relation 中 Entity 对应的实际 句子中 Entity(重头构建) –> found_entities

        • 原因

          1. Pattern.relations.relation.entity ,因为 多个 Relation 可能有共享的 Entity
  • 最终结果

    • 给定 self.Pattern.relations 重新查找的 Relation 对应的 Entity
    • 这里 self.Pattern 是形心,不是原来的 Phrase 对应的句子了

chemdataextractor.relex.Pattern

属性:

  • self.parse_expression

    • 解释: 形心的 Element tokens 和 Entity parse_expression 合在一起创建

方法:

  • generate_cde_parse_expression

    • 逻辑

      • 把 prefix, middle_1, middle_2, …, suffix token
      • 逐个使用 IWord(token_str) 包起来

        • 跳过 "<BLANK>"
      • 注意:

        • 中间 穿插 Entity.parse_expression

          • 通过 Model.field.parse_expression 传递过来

            • 在 Snowball.candiate
            • 在 Entity 初始化时, Entity.parse_expression name 被重新设置, 参见 Entity
      • 使用 And 连接
      • 设定名称 "phrase", 完成处理

句法相似度 chemdataextractor.relex.utils

作用:

  • 当前句法模式 Phrase 和一个给定句法簇 cluster 的相似度
  • 比较 Phrase 和 cluster 所有子 Phrase 相似的总体情况
向量化 chemdataextractor.relex.utils.vectorise()

逻辑:

  • 按 element 统计

    • 获取所有 token 的频数:

      • cluster 中给定 element 所有 token 的频数 frequeny
      • 加上当前对应 phrase 对应的 element 的 token 的 频数
    • 计算 phrase 和 pattern 的 token 正规化向量

      • 频数向量

        • 按 整个 cluster 中的 所有 token 为元素数量和排序基准
        • 分别统计 phrase 和 pattern 的 对应 token 频数
      • frequency_vector / np.linalg.norm(frequency_vector)
      • 即: 频率向量 = 频数向量 / 二阶范数(频数向量)
  • 得到结果

    • Dict[element_name, List[token_frequeny]]
    • List[token_frequeny]

      • 这是一个稀疏向量
相似度 chemdataextractor.relex.utils.match_score()

逻辑:

  • 按 element 分组

    • suffix 一组,prefix 一组,middle 一组
  • 给定 element

    • token 频率向量做 点积训练
    • 特殊情况

      • 长度问题

        • 都为零 –> 1
        • 单个为零 –> 0
        • 正常 –> 点积运算
  • 按分组乘上对应比重后加和

Snowball.retrieve_entities(model, sent_parser.scan_result)

返回 generator: yield –> List[(text, (model_name, field_name), field.parse_expression)]

entities: List[(text, (model_name, field_name), field.parse_expression), index, index + token_count]

  • text: 空格分割的多个单词(token)
  • index: KnuthMorrisPratt 算法差生产生的开始位置

单位解析

工具汇总

  • chemdataextractor.parse.auto.construct_unit_element

    • 作用: 创建 (单位) 规则匹配工具(root 中用到的 parse_expression)
    • 注意: 这里 只是单位规则 ,不包含数值规则
    • 函数接口: construct_unit_element(dimensions: Dimension)
  • chemdataextractor.parse.quantity.value_element_plain

    • 作用: 创建 纯数值 匹配规则
  • chemdataextractor.parse.quantity.value_element

    • 作用: 创建 (数值 + 单位) 匹配规则
    • 函数接口: value_element(units: BaseParserElement) -> BaseParserElement
  • chemdataextractor.parse.quantity.construct_quantity_re

    • 作用: 在 句子 token 化中使用

      • 线索:

        • Sentence::quantity_re
        • Sentence.word_tokenizer = ChemWordTokenizer
        • ChemWordTokenizer.get_additional_regex(sentence)
    • 函数接口:

      • construct_quantity_re(*models)
      • models: List[BaseModel]
      • 有用数据: List[QuantityModel]

单位匹配规则 construct_unit_element

参考: chemdataextractor.parse.auto

 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
def construct_unit_element(dimensions):
    """
    Construct an element for detecting units for the dimensions given.
    Any magnitude modifiers (e.g. kilo) will be automatically handled.

    :param Dimension dimensions: The dimensions that the element produced will look for.
    :returns: An Element to look for units of given dimensions. If None or Dimensionless are passed in, returns None.
    :rtype: BaseParserElement or None
    """
    if not dimensions or not dimensions.units_dict:
        return None

    #------------------------- 单个 单位的处理 -----------------------
    # Handle all the magnitudes
    # 数量关系(kilo)等规则
    units_regex = '^(('
    for element in magnitudes_dict.keys():
        units_regex += '(' + element.pattern + ')|'
        units_regex = units_regex[:-1]
        units_regex += ')?'
        units_regex += '('
        # Case where we have a token that's just brackets

        # 多余 =括号= 和 =-= 处理
    units_regex += r'((\(|\[))|((\)|\]))|\-|'

    # Handle all the units
    # 单位符号的加入
    for element in dimensions.units_dict:
        units_regex += '(' + element.pattern + ')|'
        units_regex += r'(\/)'
        # Case when we have powers, or one or more units

    # 符号的 幂运算 规则 <--- 完成的
    # ---> kilo meter, /, +3.2, meter, m
    units_regex2 = units_regex + r'|([\+\-–−]?\d+(\.\d+)?)'
    units_regex2 += '))+$'

    # 不包括 数字 规则  <---- 完成的, kilo meter, meter, /
    units_regex += '))+'

    # ~(不包含数字) + (包含数字)*~ ,即 (前后挨在一起的情况), eg: m / s , m, m 2
    units_regex += (units_regex2[1:-2] + '*')
    units_regex += '$'
    return (R(pattern=units_regex) + ZeroOrMore(R(pattern=units_regex) | R(pattern=units_regex2))).add_action(merge)

解析 regex2

  • 总体
1
^(((c(enti)?)|(k(ilo)?)|(M(ega)?)|(G(iga)?)|(T(era)?)|(m(illi)?)|(µ|(micro)|(mu))|(n(ano)?)|(p(ico)?))?(((\(|\[))|((\)|\]))|\-|([Mm](ol)(e(s)?)?)|(m[Mm](ol)(e(s)?)?)|(\/)|([\+\-–−]?\d+(\.\d+)?)))+$
  • 匹配

    mmol
    c-mol
    centimol
    centimmol
    mol/3
    mol/3.1
    molmolmol
    
    molmol
    mol3.1mol3.2
    mol-3
    centi-mol
    milli-mol1.2
    
    )mol
    
    (mol
    (mol3
    (mol3)
    mol/centi-mol
    
    replace. Tab to end.
    
数量级部分
1
2
3
4
5
6
((c(enti)?)|(k(ilo)?)|(M(ega)?)|(G(iga)?)|(T(era)?)|(m(illi)?)|(µ|(micro)|(mu))|(n(ano)?)|(p(ico)?))?

centi
killo
Mega
等等
单位和指数部分

括号(),[]; -; 单位本身; 分数 /; 指数 -\d\.\d+

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(((\(|\[))|((\)|\]))|\-|([Mm](ol)(e(s)?)?)|(m[Mm](ol)(e(s)?)?)|(\/)|([\+\-–−]?\d+(\.\d+)?))


((\(|\[))|((\)|\]))|\-|([Mm](ol)(e(s)?)?)|(m[Mm](ol)(e(s)?)?)|(\/)|([\+\-–−]?\d+(\.\d+)?)

((\(|\[))|((\)|\])) : 括号
\- : -
([Mm](ol)(e(s)?)?) : mmol
(m[Mm](ol)(e(s)?)?) : mol
(\/) : /
([\+\-–−]?\d+(\.\d+)?) : 数字

规则分立

  • 分别说明,括号、单位、分数、指数等
  • 使用 | 连接
  • 各个部分作为组建
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
mmol
-mol
mol
mmol
mol/3
mol/3.1
molmolmol

molmol
mol3.1mol3.2
mol-3
-mol
-mol1.2

)mol

(mol
(mol3
(mol3)
mol/-mol

数值解析 value_element_plain

参考: chemdataextractor.parse.quantity

  • 不包含单位
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@memoize
def value_element_plain():
    """
    Returns an element similar to value_element but without any units.

    :returns: An Element to look for values.
    :rtype: BaseParserElement
    """
    fraction = R('^[\+\-–−]?\d+/\d+$') | (R('^[\+\-–−]?\d+$') + R('^/$') + R('^\d+$')).add_action(merge)
    pure_number = R('^[\+\-–−]?\d+(([\.・,\d])+)?$')
    number = fraction | pure_number
    joined_range = R('^[\+\-–−]?\d+(([\.・,\d])+)?[\-–−~∼˜]\d+(([\.・,\d])+)?$')('raw_value').add_action(merge)
    spaced_range = (number + R('^[\-–−~∼˜]$') + number)('raw_value').add_action(merge)
    to_range = (number + I('to') + number)('raw_value').add_action(join)
    plusminus_range = (number + R('±') + number)('raw_value').add_action(join)
    bracket_range = R(pure_number.pattern[:-1] + '\(\d+\)' + '$')('raw_value')
    between_range = (I('between').hide() + number + I('and') + number).add_action(join)
    value_range = (Optional(R('^[\-–−]$')) + (plusminus_range | joined_range | spaced_range | to_range | between_range | bracket_range))('raw_value').add_action(merge)
    value_single = (Optional(R('^[~∼˜\<\>]$')) + Optional(R('^[\-–−]$')) + number)('raw_value').add_action(merge)
    value = Optional(lbrct).hide() + (value_range | value_single)('raw_value') + Optional(rbrct).hide()
    return value

number 数字匹配

1
2
3
4
5
6
7
8
number = R('^[\+\-–−]?\d+(([\.・,\d])+)?$')

fraction = R('^[\+\-–−]?\d+/\d+$') | (R('^[\+\-–−]?\d+$') + R('^/$') + R('^\d+$')).add_action(merge)
pure_number = R('^[\+\-–−]?\d+(([\.・,\d])+)?$')
number = fraction | pure_number

value_single = (Optional(R('^[~∼˜\<\>]$')) + Optional(R('^[\-–−]$')) + number)('raw_value').add_action(merge)
# 加入支持 - 13.2, > 13.2 > - 13.2 等
pure_number 纯数字
1
pure_number = R('^[\+\-–−]?\d+(([\.・,\d])+)?$')
+12.3
-12,3
12·3
fraction 分数
  • 不允许空格版本

    1
    
    R('^[\+\-–−]?\d+/\d+$')
    • 分子分母都是整数
    +13/21
    -12/3
    
  • 允许空格版本

    1
    
    (R('^[\+\-–−]?\d+$') + R('^/$') + R('^\d+$')).add_action(merge)
    • 分子分母都是整数
    • *注意*: 正负号不允许与分子分开
    +12 / 13
    -12 / 32
    12 / 33
    
value_single 完整的单个数表示
1
value_single = (Optional(R('^[~∼˜\<\>]$')) + Optional(R('^[\-–−]$')) + number)('raw_value').add_action(merge)
1
2
3
4
5
12.3
-12.3
- 12.3
> -12.3
> - 12.3

range 范围

joined_range 范围匹配不允许空格
1
joined_range = R('^[\+\-–−]?\d+(([\.・,\d])+)?[\-–−~∼˜]\d+(([\.・,\d])+)?$')('raw_value').add_action(merge)
  • 横线等分割
+12.3-12.3
-12,3~13.2
spaced_range 范围允许空格
1
    spaced_range = (number + R('^[\-–−~∼˜]$') + number)('raw_value').add_action(merge)
1
+12.3 ~ 13.6
  • 不许有 ~ 等范围分隔符
to_range 通过 "to" 间隔表示范围
1
to_range = (number + I('to') + number)('raw_value').add_action(join)
12.3 to 13.5
plusminus_range 通过误差限 "±" 表示范围
1
plusminus_range = (number + R('±') + number)('raw_value').add_action(join)
12.3 ± 13.2
bracket_range 通过括号 "()" 表示范围
1
2
3
bracket_range = R(pure_number.pattern[:-1] + '\(\d+\)' + '$')('raw_value')
# -->
R('^[\+\-–−]?\d+(([\.・,\d])+)?' + '\(\d+\)' + '$')('raw_value')
+12.3 (23)
+12.3 (2)
between_range 通过 "between … and …" 表示范围
1
between_range = (I('between').hide() + number + I('and') + number).add_action(join)
between 12.3 and 13.6

(数值 + 单位)匹配规则 value_element

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def value_element(units=(OneOrMore(T('NN')) | OneOrMore(T('NNP')) | OneOrMore(T('NNPS')) | OneOrMore(T('NNS')))('raw_units').add_action(merge)):
    """
    Returns an Element for values with given units. By default, uses tags to guess that a unit exists.

    :param BaseParserElement units: (Optional) A parser element for the units that are to be looked for. Default option looks for nouns.
    :returns: An Element to look for values and units.
    :rtype: BaseParserElement
    """
    number = R('^[\+\-–−]?\d+(([\.・,\d])+)?$')
    joined_range = R('^[\+\-–−]?\d+(([\.・,\d])+)?[\-–−~∼˜]\d+(([\.・,\d])+)?$')('raw_value').add_action(merge)
    spaced_range = (number + Optional(units).hide() + (R('^[\-–−~∼˜]$') + number | number))('raw_value').add_action(merge)
    to_range = (number + Optional(units).hide() + I('to') + number)('raw_value').add_action(join)
    plusminus_range = (number + R('±') + number)('raw_value').add_action(join)
    bracket_range = R(number.pattern[:-1] + '\(\d+\)' + '$')('raw_value')
    between_range = (I('between').hide() + number + I('and') + number).add_action(join)
    value_range = (Optional(R('^[\-–−]$')) + (bracket_range | plusminus_range | joined_range | spaced_range | to_range | between_range))('raw_value').add_action(merge)
    value_single = (Optional(R('^[~∼˜\<\>]$')) + Optional(R('^[\-–−]$')) + number)('raw_value').add_action(merge)
    value = Optional(lbrct).hide() + (value_range | value_single)('raw_value') + Optional(rbrct).hide()
    return value + units

number

1
number = R('^[\+\-–−]?\d+(([\.・,\d])+)?$')
1
2
3
12.3
12
+12

spaced_range

1
spaced_range = (number + Optional(units).hide() + (R('^[\-–−~∼˜]$') + number | number))('raw_value').add_action(merge)
  • 空格分割(分词分隔符)
  • 注: 允许单位
# 设单位 kg
+12.3 kg - 13.1
12.3 kg ~ 13.2
12.3 kg 13.2  # ~ 忽略

construct_quantity_re

相比于 construct_unit_element 多出的正则匹配规则

  • ^((?P<split>[\+\-–−]?\d+([\.\-\−]?\d+)?)|((?P<split2>.*)(\(|\/|\[)))

    • 匹配举例:

      • +3: 正数
      • -2: 负数
      • 3.5: 小数
      • 2.2-1.3: 范围, 通过 \-\− 连接的两个数
      • 开括号:

        • (
        • /
        • [

Snowball 代码问题记录

chemdataextractor.relex.Cluster::update_pattern()

149 行:

  • 传入的 phrase.relations 问题

    • phrase 并不是选定的,而是 self.phrases[-1]
    • 是否有影响?

      • self.pattern.relations 用途 提供 Phrase.Relation.Entity.tag 用于 Entitiy 排序
      • 聚类中 Entity.parse_expression 用于解析新句子

        • Entity.parse_expression 来源于 model.field.parse_expression
      • 只要 Relation 全即可
      • 因此 Entity.parse_expression 对 Relation 无影响, 对 phrase 是谁无影响
    • 结论

      • 无影响

Snowball 算法概念

相似度

使用余弦相似度 cosine similarity 公式:

\begin{equation} sim(x, y) = \frac{x \cdot y}{ \lVert x \rVert \cdot \lVert y \rVert} \end{equation}

Single Pass Clustering (SPC) 算法

优点:

  • 不需要设定 cluster 数量

    • 但是,要指定 similarity 阈值

BaseType 类型特点

  • ModelType 类型

    • 特殊字段 self.model_class
  • ListType 和 SetType

    • self.field

Model 字段问题

字段类型

BaseType 和 衍生类规定

字段值

容器: BaseModel._values: Dict[name, value]

  • BaseModel._values[self.name] = value

    • self.name 是字段的名称, 通过 ModelMeta 元类自动设置

多个 model 问题

单个句子 多个 model

self.models = [A, B, …]

嵌套 model + 多个 model 问题

self.models = [MolModel] 和 self.models = [Compound, MolModel] 结果不一致

1
2
3
4
5
6
# self.models = [MolModel] -->
records: [{'MolModel': {'raw_value': '0.2', 'raw_units': 'mmol', 'value': [0.2], 'units': 'MilliMol^(1.0)', 'specifier': 'substance amount', 'compound': {'Compound': {'names': ['PbX2']}}, 'fake_field': 'of'}}]

# self.models = [Compound, MolModel] -->
self.models = [Compound, MolModel] 结果不一致
records: [{'Compound': {'names': ['PbX2']}}, {'Compound': {'names': ['Br']}}, {'Compound': {'names': ['CH3NH3X']}}, {'Compound': {'labels': ['I']}}, {'MolModel': {'raw_value': '0.2', 'raw_units': 'mmol', 'value': [0.2], 'units': 'MilliMol^(1.0)', 'specifier': 'substance amount', 'compound': {'Compound': {'names': ['PbX2']}}, 'fake_field': 'of'}}]

官方通用 parser

chemdataextractor.parse.auto.AutoSentenceParser

构建逻辑:

1
2
3
4
5
6
7
8
if model is QuantityModel and model is Dimensionless:
    ...
elif ...:
    ...

combined_entity_list = entities_1 | entities_2 | ....

root_phrase = OneOrMore(combined_entities + Optional(SkipTo(combined_entities)))('root_phrase')

句子和 token 切分

ChemWordTokenizer

执行切分:

  • Sentence::tokens()
  • 调用: ChemWordTokenizer::get_word_tokens(self, sentence, additional_regex=None)
  • 调用: ChemWordTokenizer::span_tokenize(sentence.text, additional_regex)

    1. 通过空格切分 '\s+'

      • re.finditer(regex, sentence.text, re.U)
    2. 二次切分

      • self._subspan(s, spans[i], spans[i + 1] if i + 1 < len(spans) else None, additional_regex)

        • 跳过特殊词

          • self.SPLIT: —, <—-> 等
          • self.SPLIT_END_WORD: 's 're 等
          • self.SPLIT_START_WORD: "''" 等
          • self.NO_SPLIT: mm-hm, mm-mm 等不可分割词
        • 跳过 url

          • http:// 开头
          • ftp:// 开头
          • www. 开头