Python 查找文本中单词标记的偏移量

Python find offsets of a word token in a text

我编写了这个函数 findTokenOffset 来查找给定单词在预标记文本中的偏移量(作为间隔单词列表或根据特定标记器)。

重新导入,json

def word_regex_ascii(word):
    return r"\b{}\b".format(re.escape(word))

def findTokenOffset(text,tokens):
  seen = {} # map if a token has been see already!
  items=[] # word tokens
  my_regex = word_regex_ascii
  # for each token word
  for index_word,word in enumerate(tokens):

      r = re.compile(my_regex(word), flags=re.I | re.X | re.UNICODE)

      item = {}
      # for each matched token in sentence
      for m in r.finditer(text):

          token=m.group()
          characterOffsetBegin=m.start()
          characterOffsetEnd=characterOffsetBegin+len(m.group()) - 1 # LP: star from 0
          
          found=-1
          if word in seen:
              found=seen[word]
          
          if characterOffsetBegin > found:
              # store last word has been seen
              seen[word] = characterOffsetEnd
              item['index']=index_word+1 #// word index starts from 1
              item['word']=token
              item['characterOffsetBegin'] = characterOffsetBegin
              item['characterOffsetEnd'] = characterOffsetEnd
              items.append(item)

              break
  return items

当标记是像

这样的单个单词时,此代码可以正常工作
text = "George Washington came to Washington"
tokens = text.split()
offsets = findTokenOffset(text,tokens)
print(json.dumps(offsets, indent=2)) 

但是,应该有像这里这样的多代币方式的代币:

text = "George Washington came to Washington"
tokens = ["George Washington", "Washington"]
offsets = findTokenOffset(text,tokens)
print(json.dumps(offsets, indent=2)) 

由于在不同标记中重复单词,偏移量无法正常工作:

[
  {
    "index": 1,
    "word": "George Washington",
    "characterOffsetBegin": 0,
    "characterOffsetEnd": 16
  },
  {
    "index": 2,
    "word": "Washington",
    "characterOffsetBegin": 7,
    "characterOffsetEnd": 16
  }
]

How to add support to multi-token and overlapped token regex matching(感谢评论中对这个确切问题名称的建议)?

如果您想查找 Washington,而不是 George Washington,您可以从初始字符串中删除找到的句子。因此,您可以按单词数量对 'tokens' 进行排序。这让你有机会先浏览句子,然后浏览单词。

如果不需要在结果输出中搜索phrase/word索引信息,可以使用以下方式:

import re,json
 
def findTokenOffset(text, pattern):
    items = []
    for m in pattern.finditer(text):
        item = {}
        item['word']=m.group()
        item['characterOffsetBegin'] = m.start()
        item['characterOffsetEnd'] = m.end()
        items.append(item)
    return items
 
text = "George Washington came to Washington Washington.com"
tokens = ["George Washington", "Washington"]
pattern = re.compile(fr'(?<!\w)(?:{"|".join(sorted(map(re.escape, tokens), key=len, reverse=True))})(?!\w)(?!\.\b)', re.I )
offsets = findTokenOffset(text,pattern)
print(json.dumps(offsets, indent=2)) 

Python demo的输出:

[
  {
    "word": "George Washington",
    "characterOffsetBegin": 0,
    "characterOffsetEnd": 17
  },
  {
    "word": "Washington",
    "characterOffsetBegin": 26,
    "characterOffsetEnd": 36
  }
]

主要部分是 pattern = re.compile(fr'(?<!\w)(?:{"|".join(sorted(map(re.escape, tokens), key=len, reverse=True))})\b(?!\.\b)', re.I ),它执行以下操作:

  • map(re.escape, tokens) - 转义 tokens 字符串中的特殊字符
  • sorted(..., key=len, reverse=True) - 按长度降序对转义 tokens 中的项目进行排序(以便 Washigton Post 可以早于 Washington 匹配)
  • "|".join(...) - 创建了 tokenstoken1|token2|etc
  • 的交替列表
  • (?<!\w)(?:...)(?!\w)(?!\.\b) - 是将 tokens 中的所有选项作为整个单词匹配的最终模式。 (?<!\w)(?!\w) 用于启用单词边界检测,即使 tokens start/end 带有特殊字符。

关于单词边界的注意事项

您应该检查您的 令牌边界 要求。我添加了 (?!\.\b) 因为你提到 Washington 不应该在 Washington.com 中匹配,所以我推断当它紧跟 . 和一个词时想要使任何词匹配失败边界。还有很多其他可能的解决方案,主要的解决方案是 空白边界 (?<!\S)(?!\S).

此外,参见