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

问题描述

我编写了这个函数 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,indent=2)) 

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

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

如何添加多令牌重叠令牌正则表达式匹配的支持(感谢评论中对该问题确切名称的建议)?

解决方法

如果您不需要结果输出中的搜索短语/单词索引信息,您可以使用以下方法:

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,reverse=True))})\b(?!\.\b)',re.I ),它执行以下操作:

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

词界注意事项

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

此外,请参阅Match a whole word in a string using dynamic regex

,

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