Правильные теги POS для номеров, замененных на ## в пространстве

Набор данных gigaword - это огромный корпус, используемый для обучения абстрактных моделей реферирования. Он содержит такие резюме:

spain 's colonial posts #.## billion euro loss
taiwan shares close down #.## percent

Я хочу обработать эти сводки с помощью spacy и получить правильный тег pos для каждого токена. Проблема в том, что все числа в наборе данных были заменены знаками #, которые не классифицируются как числа (NUM), а как другие теги.

>>> import spacy
>>> from spacy.tokens import Doc
>>> nlp = spacy.load("en_core_web_sm")
>>> nlp.tokenizer = lambda raw: Doc(nlp.vocab, words=raw.split(' '))
>>> text = "spain 's colonial posts #.## billion euro loss"
>>> doc = nlp(text)
>>> [(token.text, token.pos_) for token in doc]
[('spain', 'PROPN'), ("'s", 'PART'), ('colonial', 'ADJ'), ('posts', 'NOUN'), ('#.##', 'PROPN'), ('billion', 'NUM'), ('euro', 'PROPN'), ('loss', 'NOUN')]

Есть ли способ настроить теггер POS так, чтобы он классифицировал все токены, которые состоят только из # -знака и точек, как числа?

Я знаю, что вы заменяете просторный POS-теггер своим собственным или настраиваете его для своего домена с дополнительными данными, но у меня нет тегированных обучающих данных, где все числа заменены на #, и я хотел бы как можно меньше менять теггер . Я бы предпочел иметь регулярное выражение или фиксированный список токенов, которые всегда распознаются как числа.


person Pyfisch    schedule 10.02.2020    source источник
comment
на случай, если вы используете деталь, чтобы вернуть #s: я исправил это (я забыл 0). Кстати: спасибо   -  person Walter Tross    schedule 17.02.2020


Ответы (1)


А как насчет замены # цифрой?

В первой версии этого ответа я выбрал цифру 9, потому что она напоминает мне форматы числовых полей COBOL, которые я использовал около 30 лет назад ... Но затем я взглянул на набор данных и понял, что для правильной обработки NLP один следует прояснить хотя бы пару вещей:

  • порядковые числа (1-я, 2-я, ...)
  • даты

Порядковые числа нуждаются в особой обработке для любого выбора цифры, но цифра 1 дает разумные даты, за исключением года (конечно, 1111 может или не может интерпретироваться как действительный год, но давайте не будем рисковать). 11/11/2020 явно лучше, чем _5 _...

Вот код:

import re

ic = re.IGNORECASE
subs = [
    (re.compile(r'\b1(nd)\b', flags=ic), r'2\1'),  # 1nd -> 2nd
    (re.compile(r'\b1(rd)\b', flags=ic), r'3\1'),  # 1rd -> 3rd
    (re.compile(r'\b1(th)\b', flags=ic), r'4\1'),  # 1th -> 4th
    (re.compile(r'11(st)\b', flags=ic), r'21\1'),  # ...11st -> ...21st
    (re.compile(r'11(nd)\b', flags=ic), r'22\1'),  # ...11nd -> ...22nd
    (re.compile(r'11(rd)\b', flags=ic), r'23\1'),  # ...11rd -> ...23rd
    (re.compile(r'\b1111\b'), '2020')              # 1111 -> 2020
]

text = '''spain 's colonial posts #.## billion euro loss
#nd, #rd, #th, ##st, ##nd, ##RD, ##TH, ###st, ###nd, ###rd, ###th.
ID=#nd#### year=#### OK'''

text = text.replace('#', '1')
for pattern, repl in subs:
    text = re.sub(pattern, repl, text)

print(text)
# spain 's colonial posts 1.11 billion euro loss
# 2nd, 3rd, 4th, 21st, 22nd, 23RD, 11TH, 121st, 122nd, 123rd, 111th.
# ID=1nd1111 year=2020 OK

Если предварительная обработка корпуса все равно преобразует любую цифру в #, вы не потеряете информацию с этим преобразованием. Некоторые «истинные» # превратились бы в 1, но это, вероятно, будет незначительной проблемой по сравнению с числами, которые не распознаются как таковые. Более того, при визуальном осмотре около 500000 строк набора данных я не смог найти ни одного кандидата на «истинное» #.

NB: \b в приведенных выше регулярных выражениях обозначает «границу слова», т. Е. Границу между символом \w (слово) и \W (не слово), где символ слова - это любой буквенно-цифровой символ (дополнительная информация здесь). \1 в замене означает первую группу, то есть первую пару круглых скобок (дополнительная информация здесь). При использовании \1 регистр всего текста сохраняется, что было бы невозможно с заменой строк, таких как 2nd. Позже я обнаружил, что ваш набор данных нормализован для всех нижних регистров, но я решил оставить его общим.

Если вам нужно вернуть текст с #s из частей речи, это просто

token.text.replace('0','#').replace('1','#').replace('2','#').replace('3','#').replace('4','#')
person Walter Tross    schedule 12.02.2020