如果查看源代码更容易,请随意跳过我冗长的解释!
所以我写了一个函数来标记文本字符串。在最简单的情况下,它需要一个像It's a beautiful morning
这样的字符串并返回一个令牌列表。对于前面的示例,输出将为['It', "'", 's', ' ', 'a', ' ', 'beautiful', ' ', 'morning']
。
这是通过函数的前两行实现的:
separators = dict.fromkeys(whitespace + punctuation, True)
tokens = [''.join(g) for _, g in groupby(phrase, separators.get)]
这里要注意的是It's
被分成["It", "'", "s"]
。在大多数情况下,这不是问题,但有时也是如此。出于这个原因,我添加了stop_words
kwarg,它采用了一组“非标记化”的字符串。例如:
>>> tokenize("It's a beautiful morning", stop_words=set("It's"))
>>> ["It's", , ' ', 'a', ' ', 'beautiful', ' ', 'morning']
这种“非标记化”通过在标记列表中移动的滑动窗口起作用。考虑下面的架构。该窗口显示为[]
Iteration 1: ['It', "'",] 's', ' ', 'a', ' ', 'beautiful', ' ', 'morning'
Iteration 2: 'It', ["'", 's',] ' ', 'a', ' ', 'beautiful', ' ', 'morning'
Iteration 3: 'It', "'", ['s', ' ',] 'a', ' ', 'beautiful', ' ', 'morning'
在每次迭代时,窗口中包含的字符串将被连接并根据stop_words
的内容进行检查。如果窗口到达令牌列表的末尾并且未找到匹配项,则窗口的大小增加1.因此:
Iteration 9: ['It', "'", 's',] ' ', 'a', ' ', 'beautiful', ' ', 'morning'
这里我们有一个匹配,所以整个窗口被替换为一个元素:它的内容,加入。因此,在迭代9结束时,我们得到:
"It's", ' ', 'a', ' ', 'beautiful', ' ', 'morning'
现在,我们必须重新开始,以防这个新令牌在它的邻居组合时形成一个停用词。该算法将窗口大小设置为2并继续。 整个过程在迭代结束时停止,其中窗口大小等于令牌列表的长度。
这种递归是我算法效率低下的根源。对于几乎没有未标记的小字符串,它的工作速度非常快。但是,计算时间似乎随着未标记的数量和原始字符串的总长度呈指数增长。
以下是该函数的完整源代码:
from itertools import groupby, tee, izip
from string import punctuation, whitespace
def tokenize(phrase, stop_words=None):
separators = dict.fromkeys(whitespace + punctuation, True)
tokens = [''.join(g) for _, g in groupby(phrase, separators.get)]
if stop_words:
assert isinstance(stop_words, set), 'stop_words must be a set'
window = 2 # Iterating over single tokens is useless
while window <= len(tokens):
# "sliding window" over token list
iters = tee(tokens, window)
for i, offset in izip(iters, xrange(window)):
for _ in xrange(offset):
next(i, None)
# Join each window and check if it's in `stop_words`
for offset, tkgrp in enumerate(izip(*iters)):
tk = ''.join(tkgrp)
if tk in stop_words:
pre = tokens[0: offset]
post = tokens[offset + window + 1::]
tokens = pre + [tk] + post
window = 1 # will be incremented after breaking from loop
break
window += 1
return tokens
这里有一些难以理解的数字(无论如何我能做的最好)。
>>> import cProfile
>>> strn = "it's a beautiful morning."
>>> ignore = set(["they're", "we'll", "she'll", "it's", "we're", "i'm"])
>>> cProfile.run('tokenize(strn * 100, ignore=ignore)')
cProfile.run('tokenize(strn * 100, ignore=ignore)')
57534203 function calls in 15.737 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 10.405 10.405 15.737 15.737 <ipython-input-140-6ef74347708e>:1(tokenize)
1 0.000 0.000 15.737 15.737 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 {built-in method fromkeys}
899 0.037 0.000 0.037 0.000 {itertools.tee}
900 0.000 0.000 0.000 0.000 {len}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
365450 1.459 0.000 1.459 0.000 {method 'join' of 'str' objects}
57166950 3.836 0.000 3.836 0.000 {next}
由此我收集到大部分执行时间都发生在我的函数范围内。如上所述,我怀疑 window
的不断重置是导致效率低下的原因,但我不确定如何进一步诊断这一点。
我的问题如下:
window
?非常感谢!
答案 0 :(得分:1)
我可能误解了这个问题,但似乎只是在拆分之前搜索被忽略的单词才能解决问题:
def tokenize(phrase, stop_words=()):
stop_words = '|'.join(re.escape(x) + r'\b' for x in stop_words)
other = '\s+|\w+|[^\s\w]+'
regex = stop_words + '|' + other if stop_words else other
return re.findall(regex, phrase)
正如Michael Anderson所指出的,你应该添加\b
以避免匹配单词的部分
编辑:新的正则表达式将空格与标点符号分开。
答案 1 :(得分:0)
我投票支持正则表达式!
如果您不关心从标记列表中排除标点符号,则可以执行
import re
text = '''It's a beautiful morning'''
tokens = re.split(text, ' ')
给你
["It's", 'a', 'beautiful', 'morning']
如果你想要点击所有标点符号,你可以
tokens = re.split(r'\W+', text)
回来
['它','s','a','美丽','早上']
tokens = re.findall(r"\w+(?:[-']\w+)*|'|[-.(]+|\S\w*", text)