我正试图从几个包含人员条目的大型文本文件中提取数据。然而,问题是,我无法控制数据到达我手中的方式
它通常是这样的格式:
LASTNAME,名字Middlename(也许是昵称)为什么这里有这个文本2012年1月25日
名字姓氏2001我不在乎的一些文本
姓,名字等等。。。2012年1月25日。。。
目前,我使用的是一个巨大的正则表达式,它可以拆分所有kindaCamelcase
单词、所有末尾附加了月份名称的单词,以及许多名称的特殊情况。然后我使用更多的正则表达式来提取许多名称和日期的组合。
这似乎是次优。
有没有Python的机器学习库可以解析有点结构化的格式错误的数据?
我尝试过NLTK,但它无法处理我的脏数据。我现在正在修改Orange,我喜欢它的OOP风格,但我不确定我是否在浪费时间。
理想情况下,我想做这样的事情来训练解析器(具有许多输入/输出对):
training_data = (
'LASTNAME, Firstname Middlename (Maybe a Nickname)FooBarJanuary 25, 2012',
['LASTNAME', 'Firstname', 'Middlename', 'Maybe a Nickname', 'January 25, 2012']
)
这样的事情可能发生吗?还是我高估了机器学习?任何建议都将不胜感激,因为我想了解更多关于这个主题的信息。
我最终实现了一系列有点复杂的详尽正则表达式,这些正则表达式包含了使用基于文本的"过滤器"的每一个可能的用例,这些过滤器在加载解析器时被适当的正则表达式替换。
如果有人对代码感兴趣,我会把它编辑成这个答案。
这基本上是我用过的。为了用我的"语言"构造正则表达式,我必须制作替换类:
class Replacer(object):
def __call__(self, match):
group = match.group(0)
if group[1:].lower().endswith('_nm'):
return '(?:' + Matcher(group).regex[1:]
else:
return '(?P<' + group[1:] + '>' + Matcher(group).regex[1:]
然后,我创建了一个通用的Matcher
类,它为给定模式名称的特定模式构建了一个正则表达式:
class Matcher(object):
name_component = r"([A-Z][A-Za-z|'|-]+|[A-Z][a-z]{2,})"
name_component_upper = r"([A-Z][A-Z|'|-]+|[A-Z]{2,})"
year = r'(1[89][0-9]{2}|20[0-9]{2})'
year_upper = year
age = r'([1-9][0-9]|1[01][0-9])'
age_upper = age
ordinal = r'([1-9][0-9]|1[01][0-9])s*(?:th|rd|nd|st|TH|RD|ND|ST)'
ordinal_upper = ordinal
date = r'((?:{0}).? [0-9]{{1,2}}(?:th|rd|nd|st|TH|RD|ND|ST)?,? d{{2,4}}|[0-9]{{1,2}} (?:{0}),? d{{2,4}}|[0-9]{{1,2}}[-/.][0-9]{{1,2}}[-/.][0-9]{{2,4}})'.format('|'.join(months + months_short) + '|' + '|'.join(months + months_short).upper())
date_upper = date
matchers = [
'name_component',
'year',
'age',
'ordinal',
'date',
]
def __init__(self, match=''):
capitalized = '_upper' if match.isupper() else ''
match = match.lower()[1:]
if match.endswith('_instant'):
match = match[:-8]
if match in self.matchers:
self.regex = getattr(self, match + capitalized)
elif len(match) == 1:
elif 'year' in match:
self.regex = getattr(self, 'year')
else:
self.regex = getattr(self, 'name_component' + capitalized)
最后,还有一个通用的Pattern
对象:
class Pattern(object):
def __init__(self, text='', escape=None):
self.text = text
self.matchers = []
escape = not self.text.startswith('!') if escape is None else False
if escape:
self.regex = re.sub(r'([[].?+-()^\])', r'\1', self.text)
else:
self.regex = self.text[1:]
self.size = len(re.findall(r'($[A-Za-z0-9-_]+)', self.regex))
self.regex = re.sub(r'($[A-Za-z0-9-_]+)', Replacer(), self.regex)
self.regex = re.sub(r's+', r'\s+', self.regex)
def search(self, text):
return re.search(self.regex, text)
def findall(self, text, max_depth=1.0):
results = []
length = float(len(text))
for result in re.finditer(self.regex, text):
if result.start() / length < max_depth:
results.extend(result.groups())
return results
def match(self, text):
result = map(lambda x: (x.groupdict(), x.start()), re.finditer(self.regex, text))
if result:
return result
else:
return []
它变得相当复杂,但它起了作用。我不会发布所有的源代码,但这应该会让一些人开始。最后,它转换了这样一个文件:
$LASTNAME, $FirstName $I. said on $date
转换为具有命名捕获组的已编译正则表达式。
我也有类似的问题,主要是因为从Microsoft Office 2010导出数据的问题,结果是两个连续单词之间以一定的间隔连接。域区域是像拼写检查器一样的逻辑操作。你可以跳到机器学习解决方案,或者像我一样创建启发式解决方案。
简单的解决方案是假设新形成的单词是专有名称的组合(第一个字符大写)。
第二个额外的解决方案是拥有一个有效单词的字典,并尝试一组生成两个(或至少一个)有效单词的分区位置。当其中一个是专有名称时,可能会出现另一个问题,根据定义,该名称不在以前的词典中。也许我们可以使用单词长度统计的一种方法,它可以用来识别一个单词是一个错误形成的单词还是一个合法的单词。
在我的情况下,这是手动更正大型文本语料库的一部分(人工循环验证),但唯一可以自动化的是选择可能格式错误的单词及其更正的推荐。
关于连接的单词,您可以使用标记器来拆分它们:
OpenNLP令牌化器将输入字符序列分段为令牌。标记通常是单词、标点符号、数字等。
例如:
现年61岁的Pierre Vinken将于11月29日以非执行董事身份加入董事会。
标记为:
现年61岁的Pierre Vinken将于11月29日以非执行董事身份加入董事会
OpenNLP有一个"可学习的标记器",你可以训练它。如果不起作用,你可以尝试以下答案:从没有空格/组合词的文本中检测最有可能的单词。
拆分完成后,您可以删除标点符号并将其传递给NER系统,如CoreNLP:
Johnson John Doe也许是个昵称为什么这段文字在这里2012年1月25日
输出:
Tokens
Id Word Lemma Char begin Char end POS NER Normalized NER
1 Johnson Johnson 0 7 NNP PERSON
2 John John 8 12 NNP PERSON
3 Doe Doe 13 16 NNP PERSON
4 Maybe maybe 17 22 RB O
5 a a 23 24 DT O
6 Nickname nickname 25 33 NN MISC
7 Why why 34 37 WRB MISC
8 is be 38 40 VBZ O
9 this this 41 45 DT O
10 text text 46 50 NN O
11 here here 51 55 RB O
12 January January 56 63 NNP DATE 2012-01-25
13 25 25 64 66 CD DATE 2012-01-25
14 2012 2012 67 71 CD DATE 2012-01-25
问题的一部分:"所有末尾都加了月名的单词"
如果在字符串的末尾有一个格式为Monthname 1-or-2-digit-day-number, yyyy
的日期,那么应该先使用正则表达式将其删除。然后,对于输入字符串的剩余部分,现在有了一个简单得多的工作。
注意:否则,您可能会遇到给定名称的问题,这些名称也是月份名称,例如四月、五月、六月、八月。此外,March是一个可以用作"中间名"的姓氏,例如SMITH, John March
。
你对"最后/第一/中间"术语的使用是"有趣的"。如果您的数据中包含以下非盎格鲁名称,则存在潜在问题:
Mao Zedong
又名Mao Ze Dong
又名Mao Tse Tung
Sima Qian
又名Ssu-ma Ch'ien
Saddam Hussein Abd al-Majid al-Tikriti
Noda Yoshihiko
Kossuth Lajos
José Luis Rodríguez Zapatero
Pedro Manuel Mamede Passos Coelho
Sukarno
几个指针,让您开始:
- 对于日期解析,可以从几个正则表达式开始,然后使用chronic或jChronic
- 对于名称,这些OpenNlp模型应该可以工作
至于自己训练机器学习模型,这并不那么简单,尤其是关于训练数据(工作量)。。。