【问题标题】:Replace to entity tags to IOB format将实体标签替换为 IOB 格式
【发布时间】:2021-11-01 19:04:04
【问题描述】:

我正在尝试将非 IOB 标记转换为 conllu 文件中的 IOB。

文件的两个示例行是:

2 Ute Ute PROPN NE Case=Nom|Gender=Fem|Number=Sing 1 appos _ NE=PER_23|Morph=nsf

3 Wedemeier Wedemeier PROPN NE Case=Nom|Gender=Fem|Number=Sing 2 flat _ SpaceAfter=No|NE=PER_23|Morph=nsf

我想拥有

2 Ute Ute PROPN NE Case=Nom|Gender=Fem|Number=Sing 1 appos _ NE=B-PER|Morph=nsf

3 Wedemeier Wedemeier PROPN NE Case=Nom|Gender=Fem|Number=Sing 2 flat _ SpaceAfter=No|NE=I-PER|Morph=nsf

我现在想解析文件,将所有出现的“NE=NamedEntityTag_Number”更改为 IOB(类型并不重要,只需将每个“NE=field_type_number(在示例中为“NE=PER_23”)更改为(NE= B-PER 和 NE=I-PER)。PER 可以是 list_of_fields 中的任何字段。因此,我创建了一个包含所有命名实体标签的 list_of_fields。由于 conllu 文件保存为文本文件,因此我正在解析文本由于不是所有的行都包含命名实体标签,所以我首先检查,是否有命名实体标签在该行中,如果是,我检查下一行是否有相同的标签(包括相同的数字),以及该行这很重要:当下一行包含具有相同编号 id 的相同注释时,它属于同一实体,因此,第一行必须是 B-PER,而该行的后续必须是 I- PER。

我正在尝试使用fileinput,只是为了改变NE的部分。

希望有人能帮忙,谢谢!

`

import fileinput

import re

list_of_fields = ["PER", "ORG", "LOC", "GPE", "OTH"]

with fileinput.FileInput(file, inplace=True, backup=".bak") as file:
    for line in file:
        ne = [annotation for annotation in list_of_fields if (annotation in line)]
        if re.compile(r"^NE="+ne+"\_\d+$") in line:
            if re.compile(r"^NE="+ne+"\_\d+$") in next(line) == re.compile(r"^NE="+ne+"\_\d+$") in line:
                re.sub(r"^NE="+ne+"\_\d+$", r"NE=B-"+ne, line)
                re.sub(r"^NE="+ne+"\_\d+$", r"NE=I-"+ne, next(line))
            else:
                re.sub(r"^NE=" + ne + "\_\d+$", r"NE=B-" + ne, line)`

【问题讨论】:

  • 我对这种文件格式一无所知,但你不能在循环文件时使用next(),因为这会耗尽迭代器。此外,如果您可以提供一些输入和预期输出来证明您描述的问题,这将有所帮助。
  • 我调整了上面的描述。我只需要适当地切换所有“NE=...”
  • 每行是否只有您列出的一个字段?字段的值(例如 PER_23)是否总是出现在整个文件中按顺序更改的组中?
  • 是的,每一行要么一个 NE= 要么没有!不,数字不会按顺序变化。因此,两条线(FC Bayern München)可能是 ORG_74、ORG_74 和 ORG_74(因为它们构建一个单元,因此编号相同,因此表示为 ORG(组织))。但是,ORG 的下一次出现可能是 ORG_215,而不是 ORG_75。在新的注释样式中,第一个 ORG_74 (FC) 将是 B-ORG、Bayern I-ORG 和 München I-ORG。

标签: python-3.x regex text-parsing named-entity-recognition


【解决方案1】:

您必须保存最后一个字段和最后一个值才能跨多行进行比较。如果其中一个与下一个不同,则使用 B-<field> 替换,否则使用 I-<field>

import fileinput
import re

list_of_fields = ["PER", "ORG", "LOC", "GPE", "OTH"]
joined_fields = f'({"|".join(list_of_fields)})'
field_pattern = re.compile(f'NE={joined_fields}')
last_field = last_value = None

with fileinput.FileInput(file, inplace=True, backup=".bak") as in_file,
     open('output.txt', 'wt') as out_file:

    for line in in_file:
        matches = re.findall(field_pattern, line)
        if not matches:
            # keep input
            out_file.write(line)
            continue
        field = matches[0] # assuming only one field per line
        start_index = line.find(f'NE={field}')
        end_index = line.find('|', start_index)
        value = re.findall(rf'{field}_(\d+)', line[start_index:end_index])[0]
        if field != last_field or value != last_value:
            replacement = f'B-{field}'
        else:
            replacement = f'I-{field}'
        last_field = field
        last_value = value
        new_line = re.sub(rf'{field}_{value}(-{joined_fields}_\d+)*', replacement, line)
        out_file.write(new_line)

编辑:允许多个字段,仅使用第一个字段

【讨论】:

  • 感谢您的帮助!这几乎是正确的。然而,对于像“FC Bayern München”这样的词跨度,它现在被标记为 FC = B-ORG、Bayern = B-ORG、München = I-ORG。但是,应该是 FC = B-ORG,拜仁 = I-ORG_,München = I-ORG_。但真的很感谢你!
  • 哦,我知道错误在哪里了。在某些情况下,就像 FC Bayern München 一样,有两个标签,用破折号隔开。所以 FC = ORG_128, Bayern = ORG_128-ORG_129, München = ORG_128-ORG_129-GPE_130...我想我会坚持第一个出现的标签,所以 ORG_128,从而删除其余的标签。对不起!
  • 你能用那些棘手的输入来扩展你的问题吗?
  • 是的,在某些情况下,标签会扩展到第二个标签,看起来像“ORG_128-ORG_129-GPE_130”。对于这些情况,我只需要第一个标签,即这里的 I-ORG案例。
  • @darned7 我更新了我的答案以允许您描述的多个字段
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-29
  • 1970-01-01
  • 2015-03-26
  • 1970-01-01
相关资源
最近更新 更多