nltk.TweetTokenizer 中的 Tokenize() 通过拆分返回整数
Tokenize() in nltk.TweetTokenizer returning integers by splitting
Tokenize() in nltk.TweetTokenizer
通过将 32 位整数分成数字返回 32 位整数。它只发生在某些数字上,我看不出有什么原因?
>>> from nltk.tokenize import TweetTokenizer
>>> tw = TweetTokenizer()
>>> tw.tokenize('the 23135851162 of 3151942776...')
[u'the', u'2313585116', u'2', u'of', u'3151942776', u'...']
输入 23135851162
已拆分为 [u'2313585116', u'2']
有意思的是,好像把所有的数字都分割成了10位数字
>>> tw.tokenize('the 231358511621231245 of 3151942776...')
[u'the', u'2313585116', u'2123124', u'5', u'of', u'3151942776', u'...']
>>> tw.tokenize('the 231123123358511621231245 of 3151942776...')
[u'the', u'2311231233', u'5851162123', u'1245', u'of', u'3151942776', u'...']
数字标记的长度影响标记化:
>>> s = 'the 1234567890 of'
>>> tw.tokenize(s)
[u'the', u'12345678', u'90', u'of']
>>> s = 'the 123456789 of'
>>> tw.tokenize(s)
[u'the', u'12345678', u'9', u'of']
>>> s = 'the 12345678 of'
>>> tw.tokenize(s)
[u'the', u'12345678', u'of']
>>> s = 'the 1234567 of'
>>> tw.tokenize(s)
[u'the', u'1234567', u'of']
>>> s = 'the 123456 of'
>>> tw.tokenize(s)
[u'the', u'123456', u'of']
>>> s = 'the 12345 of'
>>> tw.tokenize(s)
[u'the', u'12345', u'of']
>>> s = 'the 1234 of'
>>> tw.tokenize(s)
[u'the', u'1234', u'of']
>>> s = 'the 123 of'
>>> tw.tokenize(s)
[u'the', u'123', u'of']
>>> s = 'the 12 of'
>>> tw.tokenize(s)
[u'the', u'12', u'of']
>>> s = 'the 1 of'
>>> tw.tokenize(s)
[u'the', u'1', u'of']
如果连续数字 + 空格超出长度 10:
>>> s = 'the 123 456 78901234 of'
>>> tw.tokenize(s)
[u'the', u'123 456 7890', u'1234', u'of']
TL;DR
它似乎是 TweetTokenizer()
的 bug/feature,我们不确定是什么激发了它。
继续阅读以找出 bug/feature 发生的位置...
中龙
查看 TweetTokenizer 中的 tokenize()
函数,在实际分词之前,分词器会进行一些预处理:
首先,它通过 _replace_html_entities()
函数
将实体转换为相应的 unicode 字符,从而从文本中删除实体
可选地,它使用 remove_handles()
函数删除用户名句柄。
可选,通过reduce_lengthening函数对字长进行归一化
然后,使用 HANG_RE
正则表达式
缩短 有问题的字符序列
最后,实际的标记化是通过 WORD_RE
正则表达式
进行的
在 WORD_RE
正则表达式之后,
- 在小写标记化输出之前可选择保留表情符号的大小写
代码中:
def tokenize(self, text):
"""
:param text: str
:rtype: list(str)
:return: a tokenized list of strings; concatenating this list returns\
the original string if `preserve_case=False`
"""
# Fix HTML character entities:
text = _replace_html_entities(text)
# Remove username handles
if self.strip_handles:
text = remove_handles(text)
# Normalize word lengthening
if self.reduce_len:
text = reduce_lengthening(text)
# Shorten problematic sequences of characters
safe_text = HANG_RE.sub(r'', text)
# Tokenize:
words = WORD_RE.findall(safe_text)
# Possibly alter the case, but avoid changing emoticons like :D into :d:
if not self.preserve_case:
words = list(map((lambda x : x if EMOTICON_RE.search(x) else
x.lower()), words))
return words
默认情况下,除非用户指定,否则句柄剥离和长度减少不会启动。
class TweetTokenizer:
r"""
Tokenizer for tweets.
>>> from nltk.tokenize import TweetTokenizer
>>> tknzr = TweetTokenizer()
>>> s0 = "This is a cooool #dummysmiley: :-) :-P <3 and some arrows < > -> <--"
>>> tknzr.tokenize(s0)
['This', 'is', 'a', 'cooool', '#dummysmiley', ':', ':-)', ':-P', '<3', 'and', 'some', 'arrows', '<', '>', '->', '<--']
Examples using `strip_handles` and `reduce_len parameters`:
>>> tknzr = TweetTokenizer(strip_handles=True, reduce_len=True)
>>> s1 = '@remy: This is waaaaayyyy too much for you!!!!!!'
>>> tknzr.tokenize(s1)
[':', 'This', 'is', 'waaayyy', 'too', 'much', 'for', 'you', '!', '!', '!']
"""
def __init__(self, preserve_case=True, reduce_len=False, strip_handles=False):
self.preserve_case = preserve_case
self.reduce_len = reduce_len
self.strip_handles = strip_handles
让我们来看看这些步骤和正则表达式:
>>> from nltk.tokenize.casual import _replace_html_entities
>>> s = 'the 231358523423423421162 of 3151942776...'
>>> _replace_html_entities(s)
u'the 231358523423423421162 of 3151942776...'
已检查,_replace_html_entities()
不是罪魁祸首。
默认情况下,remove_handles()
和 reduce_lengthening()
被跳过,但为了理智起见,让我们看看:
>>> from nltk.tokenize.casual import _replace_html_entities
>>> s = 'the 231358523423423421162 of 3151942776...'
>>> _replace_html_entities(s)
u'the 231358523423423421162 of 3151942776...'
>>> from nltk.tokenize.casual import remove_handles, reduce_lengthening
>>> remove_handles(_replace_html_entities(s))
u'the 231358523423423421162 of 3151942776...'
>>> reduce_lengthening(remove_handles(_replace_html_entities(s)))
u'the 231358523423423421162 of 3151942776...'
也检查过,两个可选函数都没有表现不佳
>>> import re
>>> s = 'the 231358523423423421162 of 3151942776...'
>>> HANG_RE = re.compile(r'([^a-zA-Z0-9]){3,}')
>>> HANG_RE.sub(r'', s)
'the 231358523423423421162 of 3151942776...'
克拉! HANG_RE
的名字也被清除了
>>> import re
>>> from nltk.tokenize.casual import REGEXPS
>>> WORD_RE = re.compile(r"""(%s)""" % "|".join(REGEXPS), re.VERBOSE | re.I | re.UNICODE)
>>> WORD_RE.findall(s)
['the', '2313585234', '2342342116', '2', 'of', '3151942776', '...']
来了!这就是分裂出现的地方!
现在让我们更深入地了解 WORD_RE
,它是一个正则表达式元组。
第一个是来自 https://gist.github.com/winzig/8894715
的大量 URL 模式正则表达式
让我们一一浏览:
>>> from nltk.tokenize.casual import REGEXPS
>>> patt = re.compile(r"""(%s)""" % "|".join(REGEXPS), re.VERBOSE | re.I | re.UNICODE)
>>> s = 'the 231358523423423421162 of 3151942776...'
>>> patt.findall(s)
['the', '2313585234', '2342342116', '2', 'of', '3151942776', '...']
>>> patt = re.compile(r"""(%s)""" % "|".join(REGEXPS[:1]), re.VERBOSE | re.I | re.UNICODE)
>>> patt.findall(s)
[]
>>> patt = re.compile(r"""(%s)""" % "|".join(REGEXPS[:2]), re.VERBOSE | re.I | re.UNICODE)
>>> patt.findall(s)
['2313585234', '2342342116', '3151942776']
>>> patt = re.compile(r"""(%s)""" % "|".join(REGEXPS[1:2]), re.VERBOSE | re.I | re.UNICODE)
>>> patt.findall(s)
['2313585234', '2342342116', '3151942776']
啊哈!似乎 REGEXPS
的第二个正则表达式导致了问题!!
如果我们看https://github.com/alvations/nltk/blob/develop/nltk/tokenize/casual.py#L122:
# The components of the tokenizer:
REGEXPS = (
URLS,
# Phone numbers:
r"""
(?:
(?: # (international)
\+?[01]
[\-\s.]*
)?
(?: # (area code)
[\(]?
\d{3}
[\-\s.\)]*
)?
\d{3} # exchange
[\-\s.]*
\d{4} # base
)"""
,
# ASCII Emoticons
EMOTICONS
,
# HTML tags:
r"""<[^>\s]+>"""
,
# ASCII Arrows
r"""[\-]+>|<[\-]+"""
,
# Twitter username:
r"""(?:@[\w_]+)"""
,
# Twitter hashtags:
r"""(?:\#+[\w_]+[\w\'_\-]*[\w_]+)"""
,
# email addresses
r"""[\w.+-]+@[\w-]+\.(?:[\w-]\.?)+[\w-]"""
,
# Remaining word types:
r"""
(?:[^\W\d_](?:[^\W\d_]|['\-_])+[^\W\d_]) # Words with apostrophes or dashes.
|
(?:[+\-]?\d+[,/.:-]\d+[+\-]?) # Numbers, including fractions, decimals.
|
(?:[\w_]+) # Words without apostrophes or dashes.
|
(?:\.(?:\s*\.){1,}) # Ellipsis dots.
|
(?:\S) # Everything else that isn't whitespace.
"""
)
来自 REGEXP 的第二个正则表达式尝试将数字解析为 phone-numbers:
# Phone numbers:
r"""
(?:
(?: # (international)
\+?[01]
[\-\s.]*
)?
(?: # (area code)
[\(]?
\d{3}
[\-\s.\)]*
)?
\d{3} # exchange
[\-\s.]*
\d{4} # base
)"""
该模式尝试识别
- 可选,首位数字将匹配为国际代码。
- 后3位为区号
- 可以选择后跟破折号
- 再多3位是(电信)交换码
- 另一个可选破折号
- 最后 4 位基数 phone。
有关详细说明,请参阅 https://regex101.com/r/BQpnsg/1。
这就是为什么它试图将连续的数字分成 10 个数字块!
但请注意这些怪癖,因为 phone 数字正则表达式是硬编码的,所以可以在 \d{3}-d{3}-\d{4}
或 \d{10}
模式中捕获真实的 phone 数字,但如果破折号是其他顺序,它将不起作用:
>>> from nltk.tokenize.casual import REGEXPS
>>> patt = re.compile(r"""(%s)""" % "|".join(REGEXPS[1:2]), re.VERBOSE | re.I | re.UNICODE)
>>> s = '231-358-523423423421162'
>>> patt.findall(s)
['231-358-5234', '2342342116']
>>> s = '2313-58-523423423421162'
>>> patt.findall(s)
['5234234234']
我们可以修好吗?
TweetTokenizer
正则表达式中有一部分可以识别各种格式的 phone 数字(搜索 # Phone 数字: 在本文档中:http://www.nltk.org/_modules/nltk/tokenize/casual.html#TweetTokenizer)。一些 10 位数字恰好看起来像 10 位 phone 数字。这就是为什么它们被转换成单独的令牌。
Tokenize() in nltk.TweetTokenizer
通过将 32 位整数分成数字返回 32 位整数。它只发生在某些数字上,我看不出有什么原因?
>>> from nltk.tokenize import TweetTokenizer
>>> tw = TweetTokenizer()
>>> tw.tokenize('the 23135851162 of 3151942776...')
[u'the', u'2313585116', u'2', u'of', u'3151942776', u'...']
输入 23135851162
已拆分为 [u'2313585116', u'2']
有意思的是,好像把所有的数字都分割成了10位数字
>>> tw.tokenize('the 231358511621231245 of 3151942776...')
[u'the', u'2313585116', u'2123124', u'5', u'of', u'3151942776', u'...']
>>> tw.tokenize('the 231123123358511621231245 of 3151942776...')
[u'the', u'2311231233', u'5851162123', u'1245', u'of', u'3151942776', u'...']
数字标记的长度影响标记化:
>>> s = 'the 1234567890 of'
>>> tw.tokenize(s)
[u'the', u'12345678', u'90', u'of']
>>> s = 'the 123456789 of'
>>> tw.tokenize(s)
[u'the', u'12345678', u'9', u'of']
>>> s = 'the 12345678 of'
>>> tw.tokenize(s)
[u'the', u'12345678', u'of']
>>> s = 'the 1234567 of'
>>> tw.tokenize(s)
[u'the', u'1234567', u'of']
>>> s = 'the 123456 of'
>>> tw.tokenize(s)
[u'the', u'123456', u'of']
>>> s = 'the 12345 of'
>>> tw.tokenize(s)
[u'the', u'12345', u'of']
>>> s = 'the 1234 of'
>>> tw.tokenize(s)
[u'the', u'1234', u'of']
>>> s = 'the 123 of'
>>> tw.tokenize(s)
[u'the', u'123', u'of']
>>> s = 'the 12 of'
>>> tw.tokenize(s)
[u'the', u'12', u'of']
>>> s = 'the 1 of'
>>> tw.tokenize(s)
[u'the', u'1', u'of']
如果连续数字 + 空格超出长度 10:
>>> s = 'the 123 456 78901234 of'
>>> tw.tokenize(s)
[u'the', u'123 456 7890', u'1234', u'of']
TL;DR
它似乎是 TweetTokenizer()
的 bug/feature,我们不确定是什么激发了它。
继续阅读以找出 bug/feature 发生的位置...
中龙
查看 TweetTokenizer 中的 tokenize()
函数,在实际分词之前,分词器会进行一些预处理:
首先,它通过
_replace_html_entities()
函数 将实体转换为相应的 unicode 字符,从而从文本中删除实体
可选地,它使用
remove_handles()
函数删除用户名句柄。可选,通过reduce_lengthening函数对字长进行归一化
然后,使用
HANG_RE
正则表达式 缩短 有问题的字符序列
最后,实际的标记化是通过
WORD_RE
正则表达式 进行的
在 WORD_RE
正则表达式之后,
- 在小写标记化输出之前可选择保留表情符号的大小写
代码中:
def tokenize(self, text):
"""
:param text: str
:rtype: list(str)
:return: a tokenized list of strings; concatenating this list returns\
the original string if `preserve_case=False`
"""
# Fix HTML character entities:
text = _replace_html_entities(text)
# Remove username handles
if self.strip_handles:
text = remove_handles(text)
# Normalize word lengthening
if self.reduce_len:
text = reduce_lengthening(text)
# Shorten problematic sequences of characters
safe_text = HANG_RE.sub(r'', text)
# Tokenize:
words = WORD_RE.findall(safe_text)
# Possibly alter the case, but avoid changing emoticons like :D into :d:
if not self.preserve_case:
words = list(map((lambda x : x if EMOTICON_RE.search(x) else
x.lower()), words))
return words
默认情况下,除非用户指定,否则句柄剥离和长度减少不会启动。
class TweetTokenizer:
r"""
Tokenizer for tweets.
>>> from nltk.tokenize import TweetTokenizer
>>> tknzr = TweetTokenizer()
>>> s0 = "This is a cooool #dummysmiley: :-) :-P <3 and some arrows < > -> <--"
>>> tknzr.tokenize(s0)
['This', 'is', 'a', 'cooool', '#dummysmiley', ':', ':-)', ':-P', '<3', 'and', 'some', 'arrows', '<', '>', '->', '<--']
Examples using `strip_handles` and `reduce_len parameters`:
>>> tknzr = TweetTokenizer(strip_handles=True, reduce_len=True)
>>> s1 = '@remy: This is waaaaayyyy too much for you!!!!!!'
>>> tknzr.tokenize(s1)
[':', 'This', 'is', 'waaayyy', 'too', 'much', 'for', 'you', '!', '!', '!']
"""
def __init__(self, preserve_case=True, reduce_len=False, strip_handles=False):
self.preserve_case = preserve_case
self.reduce_len = reduce_len
self.strip_handles = strip_handles
让我们来看看这些步骤和正则表达式:
>>> from nltk.tokenize.casual import _replace_html_entities
>>> s = 'the 231358523423423421162 of 3151942776...'
>>> _replace_html_entities(s)
u'the 231358523423423421162 of 3151942776...'
已检查,_replace_html_entities()
不是罪魁祸首。
默认情况下,remove_handles()
和 reduce_lengthening()
被跳过,但为了理智起见,让我们看看:
>>> from nltk.tokenize.casual import _replace_html_entities
>>> s = 'the 231358523423423421162 of 3151942776...'
>>> _replace_html_entities(s)
u'the 231358523423423421162 of 3151942776...'
>>> from nltk.tokenize.casual import remove_handles, reduce_lengthening
>>> remove_handles(_replace_html_entities(s))
u'the 231358523423423421162 of 3151942776...'
>>> reduce_lengthening(remove_handles(_replace_html_entities(s)))
u'the 231358523423423421162 of 3151942776...'
也检查过,两个可选函数都没有表现不佳
>>> import re
>>> s = 'the 231358523423423421162 of 3151942776...'
>>> HANG_RE = re.compile(r'([^a-zA-Z0-9]){3,}')
>>> HANG_RE.sub(r'', s)
'the 231358523423423421162 of 3151942776...'
克拉! HANG_RE
的名字也被清除了
>>> import re
>>> from nltk.tokenize.casual import REGEXPS
>>> WORD_RE = re.compile(r"""(%s)""" % "|".join(REGEXPS), re.VERBOSE | re.I | re.UNICODE)
>>> WORD_RE.findall(s)
['the', '2313585234', '2342342116', '2', 'of', '3151942776', '...']
来了!这就是分裂出现的地方!
现在让我们更深入地了解 WORD_RE
,它是一个正则表达式元组。
第一个是来自 https://gist.github.com/winzig/8894715
的大量 URL 模式正则表达式让我们一一浏览:
>>> from nltk.tokenize.casual import REGEXPS
>>> patt = re.compile(r"""(%s)""" % "|".join(REGEXPS), re.VERBOSE | re.I | re.UNICODE)
>>> s = 'the 231358523423423421162 of 3151942776...'
>>> patt.findall(s)
['the', '2313585234', '2342342116', '2', 'of', '3151942776', '...']
>>> patt = re.compile(r"""(%s)""" % "|".join(REGEXPS[:1]), re.VERBOSE | re.I | re.UNICODE)
>>> patt.findall(s)
[]
>>> patt = re.compile(r"""(%s)""" % "|".join(REGEXPS[:2]), re.VERBOSE | re.I | re.UNICODE)
>>> patt.findall(s)
['2313585234', '2342342116', '3151942776']
>>> patt = re.compile(r"""(%s)""" % "|".join(REGEXPS[1:2]), re.VERBOSE | re.I | re.UNICODE)
>>> patt.findall(s)
['2313585234', '2342342116', '3151942776']
啊哈!似乎 REGEXPS
的第二个正则表达式导致了问题!!
如果我们看https://github.com/alvations/nltk/blob/develop/nltk/tokenize/casual.py#L122:
# The components of the tokenizer:
REGEXPS = (
URLS,
# Phone numbers:
r"""
(?:
(?: # (international)
\+?[01]
[\-\s.]*
)?
(?: # (area code)
[\(]?
\d{3}
[\-\s.\)]*
)?
\d{3} # exchange
[\-\s.]*
\d{4} # base
)"""
,
# ASCII Emoticons
EMOTICONS
,
# HTML tags:
r"""<[^>\s]+>"""
,
# ASCII Arrows
r"""[\-]+>|<[\-]+"""
,
# Twitter username:
r"""(?:@[\w_]+)"""
,
# Twitter hashtags:
r"""(?:\#+[\w_]+[\w\'_\-]*[\w_]+)"""
,
# email addresses
r"""[\w.+-]+@[\w-]+\.(?:[\w-]\.?)+[\w-]"""
,
# Remaining word types:
r"""
(?:[^\W\d_](?:[^\W\d_]|['\-_])+[^\W\d_]) # Words with apostrophes or dashes.
|
(?:[+\-]?\d+[,/.:-]\d+[+\-]?) # Numbers, including fractions, decimals.
|
(?:[\w_]+) # Words without apostrophes or dashes.
|
(?:\.(?:\s*\.){1,}) # Ellipsis dots.
|
(?:\S) # Everything else that isn't whitespace.
"""
)
来自 REGEXP 的第二个正则表达式尝试将数字解析为 phone-numbers:
# Phone numbers:
r"""
(?:
(?: # (international)
\+?[01]
[\-\s.]*
)?
(?: # (area code)
[\(]?
\d{3}
[\-\s.\)]*
)?
\d{3} # exchange
[\-\s.]*
\d{4} # base
)"""
该模式尝试识别
- 可选,首位数字将匹配为国际代码。
- 后3位为区号
- 可以选择后跟破折号
- 再多3位是(电信)交换码
- 另一个可选破折号
- 最后 4 位基数 phone。
有关详细说明,请参阅 https://regex101.com/r/BQpnsg/1。
这就是为什么它试图将连续的数字分成 10 个数字块!
但请注意这些怪癖,因为 phone 数字正则表达式是硬编码的,所以可以在 \d{3}-d{3}-\d{4}
或 \d{10}
模式中捕获真实的 phone 数字,但如果破折号是其他顺序,它将不起作用:
>>> from nltk.tokenize.casual import REGEXPS
>>> patt = re.compile(r"""(%s)""" % "|".join(REGEXPS[1:2]), re.VERBOSE | re.I | re.UNICODE)
>>> s = '231-358-523423423421162'
>>> patt.findall(s)
['231-358-5234', '2342342116']
>>> s = '2313-58-523423423421162'
>>> patt.findall(s)
['5234234234']
我们可以修好吗?
TweetTokenizer
正则表达式中有一部分可以识别各种格式的 phone 数字(搜索 # Phone 数字: 在本文档中:http://www.nltk.org/_modules/nltk/tokenize/casual.html#TweetTokenizer)。一些 10 位数字恰好看起来像 10 位 phone 数字。这就是为什么它们被转换成单独的令牌。