初涉自然语言处理(三):分句

进度有点慢,先放个分句的算法上来吧,光是写这一大堆正则就让我头疼。

先说一下分句的目标,就是把文本分割成最小的句子。这任务听起来简单实际上很麻烦,因为网络上的文本格式参差不齐,有的有标点有的没标点,例如下图:

可以看到第一行的 Preface 是单独一句,但 11 ~ 12 行的 TO MY BEST FRIEND 应该也是一句,他们之间都没有标点,只有换行符。

这里主要用大小写来判断,英语单词的大小写分为3种:Upper CaseLower CaseInitial Caps.

规则是这样的:

My \r\n Friend 断开
My \r\n FRIEND 断开
MY \r\n Friend 断开
MY \r\n friend 断开
my \r\n FRIEND 断开

My \r\n friend 不断开
my \r\n friend 不断开
MY \r\n FRIEND 不断开
my \r\n Friend 不断开

当然不排除还是会有不按套路出牌的,但已经能对付大多数情况了。

这是转换后的样子,本来是没有换行符的,这里为了便于对比把每句都写到一行里。可以看到第一行没有了,最后一行被处理成一句话了。

另外由于这里要统计词频,所以人名、数字等都需要做特殊处理。可以看到图中一些名字被处理成了 <name>,而数字可以被表示成 <number>

其实应该还有 URL 和 Email 之类也需要处理的,不过我比较懒统一用非字母打断掉了,反正后期要和字典对比,这些会被忽略的。

以下是代码,前半部分和统计词频时的差不多,因为也需要对单词进行规范化。后面用了一大堆断言,看得我眼睛都花了。

class:FileCleaner
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
private string _clean(string raw)
{
string c = raw;
/* Word Processing
******************************************/
// convert hyphen
c = Regex.Replace(c, @"\-{2,}", ". ", RegexOptions.Compiled | RegexOptions.Multiline);
// convert whitespace
c = Regex.Replace(c, @"\xA0|(&nbsp;)|\-", " ", RegexOptions.Compiled | RegexOptions.Multiline);
// convert '
c = Regex.Replace(c, @"\u2019", "\u0027", RegexOptions.Compiled | RegexOptions.Multiline);
// convert “”
c = Regex.Replace(c, @"[\u201c\u201d]", "\u0022", RegexOptions.Compiled | RegexOptions.Multiline);
// stemming am
c = Regex.Replace(c, @"I'm", "I am", RegexOptions.Compiled | RegexOptions.Multiline);
// stemming are
c = Regex.Replace(c, @"([a-zA-Z]+)('re)", "$1 are", RegexOptions.Compiled | RegexOptions.Multiline);
// stemming will
c = Regex.Replace(c, @"([a-zA-Z]+)('ll)", "$1 will", RegexOptions.Compiled | RegexOptions.Multiline);
// stemming have
c = Regex.Replace(c, @"([a-zA-Z]+)('ve)", "$1 have", RegexOptions.Compiled | RegexOptions.Multiline);
// remove abbreviation
c = Regex.Replace(c, @"([a-zA-Z]+)(('s)|(s')|('d))", "$1", RegexOptions.Compiled | RegexOptions.Multiline);
// convert numbers
c = Regex.Replace(c, @"\d+(,\d+)*(\.\d+)?\.*", " <number> ", RegexOptions.Compiled | RegexOptions.Multiline);
// remove names
c = Regex.Replace(c, @"(((Mr)|(Mrs)|(Ms)|(Doc)|(Prof))\.\u0020*([A-Z][a-z]+)*)|([A-Z]\u0020*\.\u0020*)+", " <name> ", RegexOptions.Compiled | RegexOptions.Multiline);
// remove leading '
c = Regex.Replace(c, @"(\s|^)(\u0027+)([a-zA-Z])", " $3", RegexOptions.Compiled | RegexOptions.Multiline);
// remove tailing '
c = Regex.Replace(c, @"([a-zA-Z])(\u0027+)(\s|$)", "$1 ", RegexOptions.Compiled | RegexOptions.Multiline);
/* Segment Sentences
******************************************/
// constant EOLs
c = Regex.Replace(c, @"(?<!</s>)(\r\n){2,}", " </s>\r\n", RegexOptions.Compiled | RegexOptions.Multiline);
// between-sentence EOLs [My \r\n Friend] [My \r\n FRIEND]
c = Regex.Replace(c, @"(?<=[A-Z][a-z]+)\s*[\r\n](?=(([A-Z][a-z]+)|([A-Z]+)))", " </s>\r\n", RegexOptions.Compiled | RegexOptions.Multiline);
// between-sentence EOLs [MY \r\n Friend] [MY \r\n friend]
c = Regex.Replace(c, @"(?<=[A-Z]+)\s*[\r\n](?=(([A-Z][a-z]+)|[a-z]+))", " </s>\r\n", RegexOptions.Compiled | RegexOptions.Multiline);
// between-sentence EOLs [my \r\n FRIEND]
c = Regex.Replace(c, @"(?<=[a-z]+)\s*[\r\n](?=([A-Z]+))", " </s>\r\n", RegexOptions.Compiled | RegexOptions.Multiline);
// in-sentence EOLs [My \r\n friend] [my \r\n friend] [MY \r\n FRIEND]
c = Regex.Replace(c, @"(?<=\s[a-zA-Z]+)\s*[\r\n](?=\s*[a-zA-Z])", " ", RegexOptions.Compiled | RegexOptions.Multiline);
// convert punctuation
c = Regex.Replace(c, @"[\.\,\?\!\;\:\(\)\u0022]+\s*", " </s>\r\n", RegexOptions.Compiled | RegexOptions.Multiline);
// add EOS
c = Regex.Replace(c, @"(?<!</s>)\r\n", " </s>\r\n", RegexOptions.Compiled | RegexOptions.Multiline);
// add BOS
c = Regex.Replace(c.Trim(), @"^\u0020*(?!<s>)", "<s> ", RegexOptions.Compiled | RegexOptions.Multiline);
/* Process redundancies
******************************************/
// remove unreadable chars
c = Regex.Replace(c, @"[^a-zA-Z\<\>\u0027\r\n\u0020\/]|", "", RegexOptions.Compiled | RegexOptions.Multiline);
// remove empty sentences
c = Regex.Replace(c, @"<s>\s*\u0027*\s*</s>", "", RegexOptions.Compiled | RegexOptions.Multiline);
// remove empty lines
c = Regex.Replace(c, @"(?<=\</s\>)[\r\n]*(?=\<s\>)", "\r\n", RegexOptions.Compiled | RegexOptions.Multiline);
// remove extra whitespaces
c = Regex.Replace(c, @"\u0020{2,}", " ", RegexOptions.Compiled | RegexOptions.Multiline);
// remove EOLs
c = Regex.Replace(c, @"\r\n", " ", RegexOptions.Compiled | RegexOptions.Multiline);
// trim string
return c.Trim();
}