-
Notifications
You must be signed in to change notification settings - Fork 18
/
6.html
650 lines (567 loc) · 99.4 KB
/
6.html
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title></title>
<link href="Styles/ebook.css" type="text/css" rel="stylesheet"/>
<link href="Styles/style.css" type="text/css" rel="stylesheet"/>
</head>
<body>
<div class="document" id="learning-to-classify-text"><h1 class="title"><font id="1">6. </font><font id="2">学习分类文本</font></h1>
<p><font id="3">模式识别是自然语言处理的一个核心部分。</font><font id="4">以<span class="example">-ed</span>结尾的词往往是过去时态动词(<a class="reference external" href="./ch05.html#chap-tag">5.</a>)。</font><font id="5">频繁使用<span class="example">will</span>是新闻文本的暗示(<a class="reference external" href="./ch03.html#chap-words">3</a>)。</font><font id="6">这些可观察到的模式——词的结构和词频——恰好与特定方面的含义关联,如时态和主题。</font><font id="7">但我们怎么知道从哪里开始寻找,形式的哪一方面关联含义的哪一方面?</font></p>
<p><font id="8">本章的目的是要回答下列问题:</font></p>
<ol class="arabic simple"><li><font id="9">我们怎样才能识别语言数据中能明显用于对其分类的特征?</font></li>
<li><font id="10">我们怎样才能构建语言模型,用于自动执行语言处理任务?</font></li>
<li><font id="11">从这些模型中我们可以学到哪些关于语言的知识?</font></li>
</ol>
<p><font id="12">一路上,我们将研究一些重要的机器学习技术,包括决策树、朴素贝叶斯分类器和最大熵分类。</font><font id="13">我们会掩盖这些技术的数学和统计的基础,集中关注如何以及何时使用它们(更多的技术背景知识见进一步阅读一节)。</font><font id="14">在看这些方法之前,我们首先需要知道这个主题的范围十分广泛。</font></p>
<div class="section" id="supervised-classification"><h2 class="sigil_not_in_toc"><font id="15">1 有监督分类</font></h2>
<p><font id="16"><span class="termdef">分类</span>是为给定的输入选择正确的<span class="termdef">类标签</span>的任务。</font><font id="17">在基本的分类任务中,每个输入被认为是与所有其它输入隔离的,并且标签集是预先定义的。</font><font id="18">这里是分类任务的一些例子:</font></p>
<ul class="simple"><li><font id="19">判断一封电子邮件是否是垃圾邮件。</font></li>
<li><font id="20">从一个固定的主题领域列表中,如“体育”、“技术”和“政治”,决定新闻报道的主题是什么。</font></li>
<li><font id="21">决定词<span class="example">bank</span>给定的出现是用来指河的坡岸、一个金融机构、向一边倾斜的动作还是在金融机构里的存储行为。</font></li>
</ul>
<p><font id="22">基本的分类任务有许多有趣的变种。</font><font id="23">例如,在多类分类中,每个实例可以分配多个标签;在开放性分类中,标签集是事先没有定义的;在序列分类中,一个输入列表作为一个整体分类。</font></p>
<p><font id="24">一个分类称为<span class="termdef">有监督的</span>,如果它的建立基于训练语料的每个输入包含正确标签。</font><font id="25">有监督分类使用的框架图如<a class="reference internal" href="./ch06.html#fig-supervised-classification">1.1</a>所示。</font></p>
<div class="figure" id="fig-supervised-classification"><img alt="Images/supervised-classification.png" src="Images/fb1a02fe3607a0deb452086296fd6f69.jpg" style="width: 456.5px; height: 235.5px;"/><p class="caption"><font id="26"><span class="caption-label">图 1.1</span>:有监督分类。</font><font id="27">(a)在训练过程中,特征提取器用来将每一个输入值转换为特征集。</font><font id="28">这些特征集捕捉每个输入中应被用于对其分类的基本信息,我们将在下一节中讨论它。</font><font id="29">特征集与标签的配对被送入机器学习算法,生成模型。</font><font id="30">(b)在预测过程中,相同的特征提取器被用来将未见过的输入转换为特征集。</font><font id="31">之后,这些特征集被送入模型产生预测标签。</font></p>
</div>
<p><font id="32">在本节的其余部分,我们将着眼于分类器如何能够解决各种各样的任务。</font><font id="33">我们讨论的目的不是要范围全面,而是给出在文本分类器的帮助下执行的任务的一个代表性的例子。</font></p>
<div class="section" id="gender-identification"><h2 class="sigil_not_in_toc"><font id="34">1.1 性别鉴定</font></h2>
<p><font id="35">在<a class="reference external" href="./ch02.html#sec-lexical-resources">4</a>中,我们看到,男性和女性的名字有一些鲜明的特点。</font><font id="36">以<span class="example">a</span>,<span class="example">e</span>和<span class="example">i</span>结尾的很可能是女性,而以<span class="example">k</span>,<span class="example">o</span>,<span class="example">r</span>,<span class="example">s</span>和<span class="example">t</span>结尾的很可能是男性。</font><font id="37">让我们建立一个分类器更精确地模拟这些差异。</font></p>
<p><font id="38">创建一个分类器的第一步是决定输入的什么样的<span class="termdef">特征</span>是相关的,以及如何为那些特征<span class="termdef">编码</span>。</font><font id="39">在这个例子中,我们一开始只是寻找一个给定的名称的最后一个字母。</font><font id="40">以下<span class="termdef">特征提取器</span>函数建立一个字典,包含有关给定名称的相关信息:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">def</span> <span class="pysrc-defname">gender_features</span>(word):
<span class="pysrc-more">... </span> return {<span class="pysrc-string">'last_letter'</span>: word[-1]}
<span class="pysrc-prompt">>>> </span>gender_features(<span class="pysrc-string">'Shrek'</span>)
<span class="pysrc-output">{'last_letter': 'k'}</span></pre>
<p><font id="41">这个函数返回的字典被称为<span class="termdef">特征集</span>,映射特征名称到它们的值。</font><font id="42">特征名称是区分大小写的字符串,通常提供一个简短的人可读的特征描述,例如本例中的<tt class="doctest"><span class="pre"><span class="pysrc-string">'last_letter'</span></span></tt>。</font><font id="43">特征值是简单类型的值,如布尔、数字和字符串。</font></p>
<div class="note"><p class="first admonition-title"><font id="44">注意</font></p>
<p class="last"><font id="45">大多数分类方法要求特征使用简单的类型进行编码,如布尔类型、数字和字符串。</font><font id="46">但要注意仅仅因为一个特征是简单类型,并不一定意味着该特征的值易于表达或计算。</font><font id="47">的确,它可以用非常复杂的和有信息量的值作为特征,如第2个有监督分类器的输出。</font></p>
</div>
<p><font id="48">现在,我们已经定义了一个特征提取器,我们需要准备一个例子和对应类标签的列表。</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">from</span> nltk.corpus <span class="pysrc-keyword">import</span> names
<span class="pysrc-prompt">>>> </span>labeled_names = ([(name, <span class="pysrc-string">'male'</span>) <span class="pysrc-keyword">for</span> name <span class="pysrc-keyword">in</span> names.words(<span class="pysrc-string">'male.txt'</span>)] +
<span class="pysrc-more">... </span>[(name, <span class="pysrc-string">'female'</span>) <span class="pysrc-keyword">for</span> name <span class="pysrc-keyword">in</span> names.words(<span class="pysrc-string">'female.txt'</span>)])
<span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">import</span> random
<span class="pysrc-prompt">>>> </span>random.shuffle(labeled_names)</pre>
<p><font id="49">接下来,我们使用特征提取器处理<tt class="doctest"><span class="pre">names</span></tt>数据,并划分特征集的结果链表为一个<span class="termdef">训练集</span>和一个<span class="termdef">测试集</span>。</font><font id="50">训练集用于训练一个新的“朴素贝叶斯”分类器。</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>featuresets = [(gender_features(n), gender) <span class="pysrc-keyword">for</span> (n, gender) <span class="pysrc-keyword">in</span> labeled_names]
<span class="pysrc-prompt">>>> </span>train_set, test_set = featuresets[500:], featuresets[:500]
<span class="pysrc-prompt">>>> </span>classifier = nltk.NaiveBayesClassifier.train(train_set)</pre>
<p><font id="51">在本章的后面,我们将学习更多关于朴素贝叶斯分类器的内容。</font><font id="52">现在,让我们只是在上面测试一些没有出现在训练数据中的名字:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>classifier.classify(gender_features(<span class="pysrc-string">'Neo'</span>))
<span class="pysrc-output">'male'</span>
<span class="pysrc-output"></span><span class="pysrc-prompt">>>> </span>classifier.classify(gender_features(<span class="pysrc-string">'Trinity'</span>))
<span class="pysrc-output">'female'</span></pre>
<p><font id="53">请看<em>《黑客帝国》</em>中这些角色的名字被正确分类。</font><font id="54">尽管这部科幻电影的背景是在2199年,但它仍然符合我们有关名字和性别的预期。</font><font id="55">我们可以在大数据量的未见过的数据上系统地评估这个分类器:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">print</span>(nltk.classify.accuracy(classifier, test_set))
<span class="pysrc-output">0.77</span></pre>
<p><font id="56">最后,我们可以检查分类器,确定哪些特征对于区分名字的性别是最有效的:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>classifier.show_most_informative_features(5)
<span class="pysrc-output">Most Informative Features</span>
<span class="pysrc-output"> last_letter = 'a' female : male = 33.2 : 1.0</span>
<span class="pysrc-output"> last_letter = 'k' male : female = 32.6 : 1.0</span>
<span class="pysrc-output"> last_letter = 'p' male : female = 19.7 : 1.0</span>
<span class="pysrc-output"> last_letter = 'v' male : female = 18.6 : 1.0</span>
<span class="pysrc-output"> last_letter = 'f' male : female = 17.3 : 1.0</span></pre>
<p><font id="57">此列表显示训练集中以a 结尾的名字中女性是男性的38倍,而以k 结尾名字中男性是女性的31倍。</font><font id="58">这些比率称为<span class="termdef">似然比</span>,可以用于比较不同特征-结果关系。</font></p>
<div class="note"><p class="first admonition-title"><font id="59">注意</font></p>
<p class="last"><font id="60"><strong>轮到你来:</strong>修改<tt class="doctest"><span class="pre">gender_features()</span></tt>函数,为分类器提供名称的长度、它的第一个字母以及任何其他看起来可能有用的特征。</font><font id="61">用这些新特征重新训练分类器,并测试其准确性。</font></p>
</div>
<p><font id="62">在处理大型语料库时,构建一个包含每一个实例的特征的单独的列表会使用大量的内存。</font><font id="63">在这些情况下,使用函数<tt class="doctest"><span class="pre">nltk.classify.apply_features</span></tt>,返回一个行为像一个列表而不会在内存存储所有特征集的对象:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">from</span> nltk.classify <span class="pysrc-keyword">import</span> apply_features
<span class="pysrc-prompt">>>> </span>train_set = apply_features(gender_features, labeled_names[500:])
<span class="pysrc-prompt">>>> </span>test_set = apply_features(gender_features, labeled_names[:500])</pre>
</div>
<div class="section" id="choosing-the-right-features"><h2 class="sigil_not_in_toc"><font id="64">1.2 选择正确的特征</font></h2>
<p><font id="65">选择相关的特征,并决定如何为一个学习方法编码它们,这对学习方法提取一个好的模型可以产生巨大的影响。</font><font id="66">建立一个分类器的很多有趣的工作之一是找出哪些特征可能是相关的,以及我们如何能够表示它们。</font><font id="67">虽然使用相当简单而明显的特征集往往可以得到像样的性能,但是使用精心构建的基于对当前任务的透彻理解的特征,通常会显著提高收益。</font></p>
<p><font id="68">典型地,特征提取通过反复试验和错误的过程建立的,由哪些信息是与问题相关的直觉指引的。</font><font id="69">它通常以“厨房水槽”的方法开始,包括你能想到的所有特征,然后检查哪些特征是实际有用的。</font><font id="70">我们在<a class="reference internal" href="./ch06.html#code-gender-features-overfitting">1.2</a>中对名字性别特征采取这种做法。</font></p>
<div class="pylisting"><p></p>
<pre class="doctest"><span class="pysrc-keyword">def</span> <span class="pysrc-defname">gender_features2</span>(name):
features = {}
features[<span class="pysrc-string">"first_letter"</span>] = name[0].lower()
features[<span class="pysrc-string">"last_letter"</span>] = name[-1].lower()
<span class="pysrc-keyword">for</span> letter <span class="pysrc-keyword">in</span> <span class="pysrc-string">'abcdefghijklmnopqrstuvwxyz'</span>:
features[<span class="pysrc-string">"count({})"</span>.format(letter)] = name.lower().count(letter)
features[<span class="pysrc-string">"has({})"</span>.format(letter)] = (letter <span class="pysrc-keyword">in</span> name.lower())
return features</pre>
<p><font id="73">然而,你要用于一个给定的学习算法的特征的数目是有限的——如果你提供太多的特征,那么该算法将高度依赖你的训练数据的特性,而一般化到新的例子的效果不会很好。</font><font id="74">这个问题被称为<span class="termdef">过拟合</span>,当运作在小训练集上时尤其会有问题。</font><font id="75">例如,如果我们使用<a class="reference internal" href="./ch06.html#code-gender-features-overfitting">1.2</a>中所示的特征提取器训练朴素贝叶斯分类器,将会过拟合这个相对较小的训练集,造成这个系统的精度比只考虑每个名字最后一个字母的分类器的精度低约1%:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>featuresets = [(gender_features2(n), gender) <span class="pysrc-keyword">for</span> (n, gender) <span class="pysrc-keyword">in</span> labeled_names]
<span class="pysrc-prompt">>>> </span>train_set, test_set = featuresets[500:], featuresets[:500]
<span class="pysrc-prompt">>>> </span>classifier = nltk.NaiveBayesClassifier.train(train_set)
<span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">print</span>(nltk.classify.accuracy(classifier, test_set))
<span class="pysrc-output">0.768</span></pre>
<p><font id="76">一旦初始特征集被选定,完善特征集的一个非常有成效的方法是<span class="termdef">错误分析</span>。</font><font id="77">首先,我们选择一个<span class="termdef">开发集</span>,包含用于创建模型的语料数据。</font><font id="78">然后将这种开发集分为<span class="termdef">训练集</span>和<span class="termdef">开发测试集</span>。</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>train_names = labeled_names[1500:]
<span class="pysrc-prompt">>>> </span>devtest_names = labeled_names[500:1500]
<span class="pysrc-prompt">>>> </span>test_names = labeled_names[:500]</pre>
<p><font id="79">训练集用于训练模型,开发测试集用于进行错误分析。</font><font id="80">测试集用于系统的最终评估。</font><font id="81">由于下面讨论的原因,我们将一个单独的开发测试集用于错误分析而不是使用测试集是很重要的。</font><font id="82">在<a class="reference internal" href="./ch06.html#fig-corpus-org">1.3</a>中显示了将语料数据划分成不同的子集。</font></p>
<div class="figure" id="fig-corpus-org"><img alt="Images/corpus-org.png" src="Images/86cecc4bd139ddaf1a5daf9991f39945.jpg" style="width: 319.0px; height: 166.0px;"/><p class="caption"><font id="83"><span class="caption-label">图 1.3</span>:用于训练有监督分类器的语料数据组织图。</font><font id="84">语料数据分为两类:开发集和测试集。</font><font id="85">开发集通常被进一步分为训练集和开发测试集。</font></p>
</div>
<p><font id="86">已经将语料分为适当的数据集,我们使用训练集训练一个模型<a class="reference internal" href="./ch06.html#err-analysis-train"><span id="ref-err-analysis-train"><img alt="[1]" class="callout" src="Images/78bc6ca8410394dcf6910484bc97e2b6.jpg"/></span></a>,然后在开发测试集上运行<a class="reference internal" href="./ch06.html#err-analysis-run"><span id="ref-err-analysis-run"><img alt="[2]" class="callout" src="Images/854532b0c5c8869f9012833955e75b20.jpg"/></span></a>。</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>train_set = [(gender_features(n), gender) <span class="pysrc-keyword">for</span> (n, gender) <span class="pysrc-keyword">in</span> train_names]
<span class="pysrc-prompt">>>> </span>devtest_set = [(gender_features(n), gender) <span class="pysrc-keyword">for</span> (n, gender) <span class="pysrc-keyword">in</span> devtest_names]
<span class="pysrc-prompt">>>> </span>test_set = [(gender_features(n), gender) <span class="pysrc-keyword">for</span> (n, gender) <span class="pysrc-keyword">in</span> test_names]
<span class="pysrc-prompt">>>> </span>classifier = nltk.NaiveBayesClassifier.train(train_set) <a href="./ch06.html#ref-err-analysis-train"><img alt="[1]" class="callout" src="Images/78bc6ca8410394dcf6910484bc97e2b6.jpg"/></a>
<span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">print</span>(nltk.classify.accuracy(classifier, devtest_set)) <a href="./ch06.html#ref-err-analysis-run"><img alt="[2]" class="callout" src="Images/854532b0c5c8869f9012833955e75b20.jpg"/></a>
<span class="pysrc-output">0.75</span></pre>
<p><font id="87">使用开发测试集,我们可以生成一个分类器预测名字性别时的错误列表:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>errors = []
<span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">for</span> (name, tag) <span class="pysrc-keyword">in</span> devtest_names:
<span class="pysrc-more">... </span> guess = classifier.classify(gender_features(name))
<span class="pysrc-more">... </span> <span class="pysrc-keyword">if</span> guess != tag:
<span class="pysrc-more">... </span> errors.append( (tag, guess, name) )</pre>
<p><font id="88">然后,可以检查个别错误案例,在那里该模型预测了错误的标签,尝试确定什么额外信息将使其能够作出正确的决定(或者现有的哪部分信息导致其做出错误的决定)。</font><font id="89">然后可以相应的调整特征集。</font><font id="90">我们已经建立的名字分类器在开发测试语料上产生约100个错误:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">for</span> (tag, guess, name) <span class="pysrc-keyword">in</span> sorted(errors):
<span class="pysrc-more">... </span> <span class="pysrc-keyword">print</span>(<span class="pysrc-string">'correct={:<8} guess={:<8s} name={:<30}'</span>.format(tag, guess, name))
<span class="pysrc-output">correct=female guess=male name=Abigail</span>
<span class="pysrc-output"> ...</span>
<span class="pysrc-output">correct=female guess=male name=Cindelyn</span>
<span class="pysrc-output"> ...</span>
<span class="pysrc-output">correct=female guess=male name=Katheryn</span>
<span class="pysrc-output">correct=female guess=male name=Kathryn</span>
<span class="pysrc-output"> ...</span>
<span class="pysrc-output">correct=male guess=female name=Aldrich</span>
<span class="pysrc-output"> ...</span>
<span class="pysrc-output">correct=male guess=female name=Mitch</span>
<span class="pysrc-output"> ...</span>
<span class="pysrc-output">correct=male guess=female name=Rich</span>
<span class="pysrc-output"> ...</span></pre>
<p><font id="91">浏览这个错误列表,它明确指出一些多个字母的后缀可以指示名字性别。</font><font id="92">例如,<span class="example">yn</span>结尾的名字显示以女性为主,尽管事实上,<span class="example">n</span>结尾的名字往往是男性;以<span class="example">ch</span>结尾的名字通常是男性,尽管以<span class="example">h</span>结尾的名字倾向于是女性。</font><font id="93">因此,调整我们的特征提取器包括两个字母后缀的特征:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">def</span> <span class="pysrc-defname">gender_features</span>(word):
<span class="pysrc-more">... </span> return {<span class="pysrc-string">'suffix1'</span>: word[-1:],
<span class="pysrc-more">... </span> <span class="pysrc-string">'suffix2'</span>: word[-2:]}</pre>
<p><font id="94">使用新的特征提取器重建分类器,我们看到测试数据集上的性能提高了近3个百分点(从76.5%到78.2%):</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>train_set = [(gender_features(n), gender) <span class="pysrc-keyword">for</span> (n, gender) <span class="pysrc-keyword">in</span> train_names]
<span class="pysrc-prompt">>>> </span>devtest_set = [(gender_features(n), gender) <span class="pysrc-keyword">for</span> (n, gender) <span class="pysrc-keyword">in</span> devtest_names]
<span class="pysrc-prompt">>>> </span>classifier = nltk.NaiveBayesClassifier.train(train_set)
<span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">print</span>(nltk.classify.accuracy(classifier, devtest_set))
<span class="pysrc-output">0.782</span></pre>
<p><font id="95">这个错误分析过程可以不断重复,检查存在于由新改进的分类器产生的错误中的模式。</font><font id="96">每一次错误分析过程被重复,我们应该选择一个不同的开发测试/训练分割,以确保该分类器不会开始反映开发测试集的特质。</font></p>
<p><font id="97">但是,一旦我们已经使用了开发测试集帮助我们开发模型,关于这个模型在新数据会表现多好,我们将不能再相信它会给我们一个准确地结果。</font><font id="98">因此,保持测试集分离、未使用过,直到我们的模型开发完毕是很重要的。</font><font id="99">在这一点上,我们可以使用测试集评估模型在新的输入值上执行的有多好。</font></p>
<div class="section" id="document-classification"><h2 class="sigil_not_in_toc"><font id="100">1.3 文档分类</font></h2>
<p><font id="101">在<a class="reference external" href="./ch02.html#sec-extracting-text-from-corpora">1</a>中,我们看到了语料库的几个例子,那里文档已经按类别标记。</font><font id="102">使用这些语料库,我们可以建立分类器,自动给新文档添加适当的类别标签。</font><font id="103">首先,我们构造一个标记了相应类别的文档清单。</font><font id="104">对于这个例子,我们选择电影评论语料库,将每个评论归类为正面或负面。</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">from</span> nltk.corpus <span class="pysrc-keyword">import</span> movie_reviews
<span class="pysrc-prompt">>>> </span>documents = [(list(movie_reviews.words(fileid)), category)
<span class="pysrc-more">... </span> <span class="pysrc-keyword">for</span> category <span class="pysrc-keyword">in</span> movie_reviews.categories()
<span class="pysrc-more">... </span> <span class="pysrc-keyword">for</span> fileid <span class="pysrc-keyword">in</span> movie_reviews.fileids(category)]
<span class="pysrc-prompt">>>> </span>random.shuffle(documents)</pre>
<p><font id="105">接下来,我们为文档定义一个特征提取器,这样分类器就会知道哪些方面的数据应注意(<a class="reference internal" href="./ch06.html#code-document-classify-fd">1.4</a>)。</font><font id="106">对于文档主题识别,我们可以为每个词定义一个特性表示该文档是否包含这个词。</font><font id="107">为了限制分类器需要处理的特征的数目,我们一开始构建一个整个语料库中前2000个最频繁词的列表<a class="reference internal" href="./ch06.html#document-classify-all-words"><span id="ref-document-classify-all-words"><img alt="[1]" class="callout" src="Images/78bc6ca8410394dcf6910484bc97e2b6.jpg"/></span></a>。</font><font id="108">然后,定义一个特征提取器<a class="reference internal" href="./ch06.html#document-classify-extractor"><span id="ref-document-classify-extractor"><img alt="[2]" class="callout" src="Images/854532b0c5c8869f9012833955e75b20.jpg"/></span></a>,简单地检查这些词是否在一个给定的文档中。</font></p>
<div class="pylisting"><p></p>
<pre class="doctest">all_words = nltk.FreqDist(w.lower() <span class="pysrc-keyword">for</span> w <span class="pysrc-keyword">in</span> movie_reviews.words())
word_features = list(all_words)[:2000] <a href="./ch06.html#ref-document-classify-all-words"><img alt="[1]" class="callout" src="Images/78bc6ca8410394dcf6910484bc97e2b6.jpg"/></a>
<span class="pysrc-keyword">def</span> <span class="pysrc-defname">document_features</span>(document): <a href="./ch06.html#ref-document-classify-extractor"><img alt="[2]" class="callout" src="Images/854532b0c5c8869f9012833955e75b20.jpg"/></a>
document_words = set(document) <a href="./ch06.html#ref-document-classify-set"><img alt="[3]" class="callout" src="Images/f202bb6a4c773430e3d1340de573d0e5.jpg"/></a>
features = {}
<span class="pysrc-keyword">for</span> word <span class="pysrc-keyword">in</span> word_features:
features[<span class="pysrc-string">'contains({})'</span>.format(word)] = (word <span class="pysrc-keyword">in</span> document_words)
return features</pre>
<div class="note"><p class="first admonition-title"><font id="110">注意</font></p>
<p class="last"><font id="111">在<a class="reference internal" href="./ch06.html#document-classify-set"><span id="ref-document-classify-set"><img alt="[3]" class="callout" src="Images/f202bb6a4c773430e3d1340de573d0e5.jpg"/></span></a>中我们计算文档的所有词的集合,而不仅仅检查是否<tt class="doctest"><span class="pre">word <span class="pysrc-keyword">in</span> document</span></tt>,因为检查一个词是否在一个集合中出现比检查它是否在一个列表中出现要快的多(<a class="reference external" href="./ch04.html#sec-algorithm-design">4.7</a>)。</font></p>
</div>
<p><font id="112">现在,我们已经定义了我们的特征提取器,可以用它来训练一个分类器,为新的电影评论加标签(<a class="reference internal" href="./ch06.html#code-document-classify-use">1.5</a>)。</font><font id="113">为了检查产生的分类器可靠性如何,我们在测试集上计算其准确性<a class="reference internal" href="./ch06.html#document-classify-test"><span id="ref-document-classify-test"><img alt="[1]" class="callout" src="Images/78bc6ca8410394dcf6910484bc97e2b6.jpg"/></span></a>。</font><font id="114">再一次的,我们可以使用<tt class="doctest"><span class="pre">show_most_informative_features()</span></tt>来找出哪些特征是分类器发现最有信息量的<a class="reference internal" href="./ch06.html#document-classify-smif"><span id="ref-document-classify-smif"><img alt="[2]" class="callout" src="Images/854532b0c5c8869f9012833955e75b20.jpg"/></span></a>。</font></p>
<div class="pylisting"><p></p>
<pre class="doctest">featuresets = [(document_features(d), c) <span class="pysrc-keyword">for</span> (d,c) <span class="pysrc-keyword">in</span> documents]
train_set, test_set = featuresets[100:], featuresets[:100]
classifier = nltk.NaiveBayesClassifier.train(train_set)</pre>
<p><font id="116">显然在这个语料库中,提到Seagal的评论中负面的是正面的大约8倍,而提到Damon的评论中正面的是负面的大约6倍。</font></p>
<div class="section" id="part-of-speech-tagging"><h2 class="sigil_not_in_toc"><font id="117">1.4 词性标注</font></h2>
<p><font id="118">第<a class="reference external" href="./ch05.html#chap-tag">5.</a>中,我们建立了一个正则表达式标注器,通过查找词内部的组成,为词选择词性标记。</font><font id="119">然而,这个正则表达式标注器是手工制作的。</font><font id="120">作为替代,我们可以训练一个分类器来算出哪个后缀最有信息量。</font><font id="121">首先,让我们找出最常见的后缀:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">from</span> nltk.corpus <span class="pysrc-keyword">import</span> brown
<span class="pysrc-prompt">>>> </span>suffix_fdist = nltk.FreqDist()
<span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">for</span> word <span class="pysrc-keyword">in</span> brown.words():
<span class="pysrc-more">... </span> word = word.lower()
<span class="pysrc-more">... </span> suffix_fdist[word[-1:]] += 1
<span class="pysrc-more">... </span> suffix_fdist[word[-2:]] += 1
<span class="pysrc-more">... </span> suffix_fdist[word[-3:]] += 1</pre>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>common_suffixes = [suffix <span class="pysrc-keyword">for</span> (suffix, count) <span class="pysrc-keyword">in</span> suffix_fdist.most_common(100)]
<span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">print</span>(common_suffixes)
<span class="pysrc-output">['e', ',', '.', 's', 'd', 't', 'he', 'n', 'a', 'of', 'the',</span>
<span class="pysrc-output"> 'y', 'r', 'to', 'in', 'f', 'o', 'ed', 'nd', 'is', 'on', 'l',</span>
<span class="pysrc-output"> 'g', 'and', 'ng', 'er', 'as', 'ing', 'h', 'at', 'es', 'or',</span>
<span class="pysrc-output"> 're', 'it', '``', 'an', "''", 'm', ';', 'i', 'ly', 'ion', ...]</span></pre>
<p><font id="122">接下来,我们将定义一个特征提取器函数,检查给定的单词的这些后缀:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">def</span> <span class="pysrc-defname">pos_features</span>(word):
<span class="pysrc-more">... </span> features = {}
<span class="pysrc-more">... </span> <span class="pysrc-keyword">for</span> suffix <span class="pysrc-keyword">in</span> common_suffixes:
<span class="pysrc-more">... </span> features[<span class="pysrc-string">'endswith({})'</span>.format(suffix)] = word.lower().endswith(suffix)
<span class="pysrc-more">... </span> return features</pre>
<p><font id="123">特征提取函数的行为就像有色眼镜一样,强调我们的数据中的某些属性(颜色),并使其无法看到其他属性。</font><font id="124">分类器在决定如何标记输入时,将完全依赖它们强调的属性。</font><font id="125">在这种情况下,分类器将只基于一个给定的词拥有(如果有)哪个常见后缀的信息来做决定。</font></p>
<p><font id="126">现在,我们已经定义了我们的特征提取器,可以用它来训练一个新的“决策树”的分类器(将在<a class="reference internal" href="./ch06.html#sec-decision-trees">4</a>讨论):</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>tagged_words = brown.tagged_words(categories=<span class="pysrc-string">'news'</span>)
<span class="pysrc-prompt">>>> </span>featuresets = [(pos_features(n), g) <span class="pysrc-keyword">for</span> (n,g) <span class="pysrc-keyword">in</span> tagged_words]</pre>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>size = int(len(featuresets) * 0.1)
<span class="pysrc-prompt">>>> </span>train_set, test_set = featuresets[size:], featuresets[:size]</pre>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>classifier = nltk.DecisionTreeClassifier.train(train_set)
<span class="pysrc-prompt">>>> </span>nltk.classify.accuracy(classifier, test_set)
<span class="pysrc-output">0.62705121829935351</span></pre>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>classifier.classify(pos_features(<span class="pysrc-string">'cats'</span>))
<span class="pysrc-output">'NNS'</span></pre>
<p><font id="127">决策树模型的一个很好的性质是它们往往很容易解释——我们甚至可以指示NLTK将它们以伪代码形式输出:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">print</span>(classifier.pseudocode(depth=4))
<span class="pysrc-output">if endswith(,) == True: return ','</span>
<span class="pysrc-output">if endswith(,) == False:</span>
<span class="pysrc-output"> if endswith(the) == True: return 'AT'</span>
<span class="pysrc-output"> if endswith(the) == False:</span>
<span class="pysrc-output"> if endswith(s) == True:</span>
<span class="pysrc-output"> if endswith(is) == True: return 'BEZ'</span>
<span class="pysrc-output"> if endswith(is) == False: return 'VBZ'</span>
<span class="pysrc-output"> if endswith(s) == False:</span>
<span class="pysrc-output"> if endswith(.) == True: return '.'</span>
<span class="pysrc-output"> if endswith(.) == False: return 'NN'</span></pre>
<p><font id="128">在这里,我们可以看到分类器一开始检查一个词是否以逗号结尾——如果是,它会得到一个特别的标记<tt class="doctest"><span class="pre"><span class="pysrc-string">","</span></span></tt>。</font><font id="129">接下来,分类器检查词是否以<tt class="doctest"><span class="pre"><span class="pysrc-string">"the"</span></span></tt>尾,这种情况它几乎肯定是一个限定词。</font><font id="130">这个“后缀”被决策树早早使用是因为词"the"太常见。</font><font id="131">分类器继续检查词是否以"s"结尾。</font><font id="132">如果是,那么它极有可能得到动词标记<tt class="doctest"><span class="pre">VBZ</span></tt>(除非它是这个词"is",它有特殊标记<tt class="doctest"><span class="pre">BEZ</span></tt>),如果不是,那么它往往是名词(除非它是标点符号“.”)。</font><font id="133">实际的分类器包含这里显示的if-then语句下面进一步的嵌套,参数<tt class="doctest"><span class="pre">depth=4</span></tt> 只显示决策树的顶端部分。</font></p>
</div>
<div class="section" id="exploiting-context"><h2 class="sigil_not_in_toc"><font id="134">1.5 探索上下文语境</font></h2>
<p><font id="135">通过增加特征提取函数,我们可以修改这个词性标注器来利用各种词内部的其他特征,例如词长、它所包含的音节数或者它的前缀。</font><font id="136">然而,只要特征提取器仅仅看着目标词,我们就没法添加依赖词出现的<em>上下文语境</em>特征。</font><font id="137">然而上下文语境特征往往提供关于正确标记的强大线索——例如,标注词"fly",如果知道它前面的词是“a”将使我们能够确定它是一个名词,而不是一个动词。</font></p>
<p><font id="138">为了采取基于词的上下文的特征,我们必须修改以前为我们的特征提取器定义的模式。</font><font id="139">不是只传递已标注的词,我们将传递整个(未标注的)句子,以及目标词的索引。</font><font id="140"><a class="reference internal" href="./ch06.html#code-suffix-pos-tag">1.6</a>演示了这种方法,使用依赖上下文的特征提取器定义一个词性标记分类器。</font></p>
<div class="pylisting"><p></p>
<pre class="doctest"><span class="pysrc-keyword">def</span> <span class="pysrc-defname">pos_features</span>(sentence, i): <a href="./ch06.html#ref-suffix-pos-tag-fd"><img alt="[1]" class="callout" src="Images/78bc6ca8410394dcf6910484bc97e2b6.jpg"/></a>
features = {<span class="pysrc-string">"suffix(1)"</span>: sentence[i][-1:],
<span class="pysrc-string">"suffix(2)"</span>: sentence[i][-2:],
<span class="pysrc-string">"suffix(3)"</span>: sentence[i][-3:]}
<span class="pysrc-keyword">if</span> i == 0:
features[<span class="pysrc-string">"prev-word"</span>] = <span class="pysrc-string">"<START>"</span>
<span class="pysrc-keyword">else</span>:
features[<span class="pysrc-string">"prev-word"</span>] = sentence[i-1]
return features</pre>
<p><font id="143">很显然,利用上下文特征提高了我们的词性标注器的准确性。</font><font id="144">例如,分类器学到一个词如果紧跟在词"large"或"gubernatorial"后面,极可能是名词。</font><font id="145">然而,它无法学到,一个词如果它跟在形容词后面可能是名词,这样更一般的,因为它没有获得前面这个词的词性标记。</font><font id="146">在一般情况下,简单的分类器总是将每一个输入与所有其他输入独立对待。</font><font id="147">在许多情况下,这非常有道理。</font><font id="148">例如,关于名字是否倾向于男性或女性的决定可以通过具体分析来做出。</font><font id="149">然而,有很多情况,如词性标注,我们感兴趣的是解决彼此密切相关的分类问题。</font></p>
<div class="section" id="sequence-classification"><h2 class="sigil_not_in_toc"><font id="150">1.6 序列分类</font></h2>
<p><font id="151">为了捕捉相关的分类任务之间的依赖关系,我们可以使用<span class="termdef">联合分类器</span>模型,收集有关输入,选择适当的标签。</font><font id="152">在词性标注的例子中,各种不同的<span class="termdef">序列分类器</span>模型可以被用来为一个给定的句子中的所有的词共同选择词性标签。</font></p>
<p><font id="153">一种序列分类器策略,称为<span class="termdef">连续分类</span>或<span class="termdef">贪婪序列分类</span>,是为第一个输入找到最有可能的类标签,然后使用这个问题的答案帮助找到下一个输入的最佳的标签。</font><font id="154">这个过程可以不断重复直到所有的输入都被贴上标签。</font><font id="155">这是<a class="reference external" href="./ch05.html#sec-n-gram-tagging">5</a>中的二元标注器采用的方法,它一开始为句子的第一个词选择词性标记,然后为每个随后的词选择标记,基于词本身和前面词的预测的标记。</font></p>
<p><font id="156">在<a class="reference internal" href="./ch06.html#code-consecutive-pos-tagger">1.7</a>中演示了这一策略。</font><font id="157">首先,我们必须扩展我们的特征提取函数使其具有参数<tt class="doctest"><span class="pre">history</span></tt>,它提供一个我们到目前为止已经为句子预测的标记的列表<a class="reference internal" href="./ch06.html#consec-pos-tag-features"><span id="ref-consec-pos-tag-features"><img alt="[1]" class="callout" src="Images/78bc6ca8410394dcf6910484bc97e2b6.jpg"/></span></a>。</font><font id="158"><tt class="doctest"><span class="pre">history</span></tt>中的每个标记对应<tt class="doctest"><span class="pre">sentence</span></tt>中的一个词。</font><font id="159">但是请注意,<tt class="doctest"><span class="pre">history</span></tt>将只包含我们已经归类的词的标记,也就是目标词左侧的词。</font><font id="160">因此,虽然是有可能查看目标词右边的词的某些特征,但查看那些词的标记是不可能的(因为我们还未产生它们)。</font></p>
<p><font id="161">已经定义了特征提取器,我们可以继续建立我们的序列分类器<a class="reference internal" href="./ch06.html#consec-pos-tagger"><span id="ref-consec-pos-tagger"><img alt="[2]" class="callout" src="Images/854532b0c5c8869f9012833955e75b20.jpg"/></span></a>。</font><font id="162">在训练中,我们使用已标注的标记为征提取器提供适当的历史信息,但标注新的句子时,我们基于标注器本身的输出产生历史信息。</font></p>
<div class="pylisting"><p></p>
<pre class="doctest"> <span class="pysrc-keyword">def</span> <span class="pysrc-defname">pos_features</span>(sentence, i, history): <a href="./ch06.html#ref-consec-pos-tag-features"><img alt="[1]" class="callout" src="Images/78bc6ca8410394dcf6910484bc97e2b6.jpg"/></a>
features = {<span class="pysrc-string">"suffix(1)"</span>: sentence[i][-1:],
<span class="pysrc-string">"suffix(2)"</span>: sentence[i][-2:],
<span class="pysrc-string">"suffix(3)"</span>: sentence[i][-3:]}
<span class="pysrc-keyword">if</span> i == 0:
features[<span class="pysrc-string">"prev-word"</span>] = <span class="pysrc-string">"<START>"</span>
features[<span class="pysrc-string">"prev-tag"</span>] = <span class="pysrc-string">"<START>"</span>
<span class="pysrc-keyword">else</span>:
features[<span class="pysrc-string">"prev-word"</span>] = sentence[i-1]
features[<span class="pysrc-string">"prev-tag"</span>] = history[i-1]
return features
<span class="pysrc-keyword">class</span> <span class="pysrc-defname">ConsecutivePosTagger</span>(nltk.TaggerI): <a href="./ch06.html#ref-consec-pos-tagger"><img alt="[2]" class="callout" src="Images/854532b0c5c8869f9012833955e75b20.jpg"/></a>
<span class="pysrc-keyword">def</span> <span class="pysrc-defname">__init__</span>(self, train_sents):
train_set = []
<span class="pysrc-keyword">for</span> tagged_sent <span class="pysrc-keyword">in</span> train_sents:
untagged_sent = nltk.tag.untag(tagged_sent)
history = []
<span class="pysrc-keyword">for</span> i, (word, tag) <span class="pysrc-keyword">in</span> enumerate(tagged_sent):
featureset = pos_features(untagged_sent, i, history)
train_set.append( (featureset, tag) )
history.append(tag)
self.classifier = nltk.NaiveBayesClassifier.train(train_set)
<span class="pysrc-keyword">def</span> <span class="pysrc-defname">tag</span>(self, sentence):
history = []
<span class="pysrc-keyword">for</span> i, word <span class="pysrc-keyword">in</span> enumerate(sentence):
featureset = pos_features(sentence, i, history)
tag = self.classifier.classify(featureset)
history.append(tag)
return zip(sentence, history)</pre>
<div class="section" id="other-methods-for-sequence-classification"><h2 class="sigil_not_in_toc"><font id="164">1.7 其他序列分类方法</font></h2>
<p><font id="165">这种方法的一个缺点是我们的决定一旦做出无法更改。</font><font id="166">例如,如果我们决定将一个词标注为名词,但后来发现的证据表明应该是一个动词,就没有办法回去修复我们的错误。</font><font id="167">这个问题的一个解决方案是采取转型策略。</font><font id="168">转型联合分类的工作原理是为输入的标签创建一个初始值,然后反复提炼那个值,尝试修复相关输入之间的不一致。</font><font id="169">Brill标注器,<a class="reference external" href="./ch05.html#sec-transformation-based-tagging">(1)</a>中描述的,是这种策略的一个很好的例子。</font></p>
<p><font id="170">另一种方案是为词性标记所有可能的序列打分,选择总得分最高的序列。</font><font id="171"><span class="termdef">隐马尔可夫模型</span>就采取这种方法。</font><font id="172">隐马尔可夫模型类似于连续分类器,它不光看输入也看已预测标记的历史。</font><font id="173">然而,不是简单地找出一个给定的词的单个最好的标签,而是为标记产生一个概率分布。</font><font id="174">然后将这些概率结合起来计算标记序列的概率得分,最高概率的标记序列会被选中。</font><font id="175">不幸的是,可能的标签序列的数量相当大。</font><font id="176">给定30 个标签的标记集,有大约600 万亿(30<sup>10</sup>)种方式来标记一个10个词的句子。</font><font id="177">为了避免单独考虑所有这些可能的序列,隐马尔可夫模型要求特征提取器只看最近的标记(或最近的<span class="math">n</span>个标记,其中<span class="math">n</span>是相当小的)。</font><font id="178">由于这种限制,它可以使用动态规划(<a class="reference external" href="./ch04.html#sec-algorithm-design">4.7</a>),有效地找出最有可能的标记序列。</font><font id="179">特别是,对每个连续的词索引<span class="math">i</span>,每个可能的当前及以前的标记都被计算得分。</font><font id="180">这种同样基础的方法被两个更先进的模型所采用,它们被称为<span class="termdef">最大熵马尔可夫模型</span>和<span class="termdef">线性链条件随机场模型</span>;但为标记序列打分用的是不同的算法。</font></p>
</div>
<div class="section" id="further-examples-of-supervised-classification"><h2 class="sigil_not_in_toc"><font id="181">2 有监督分类的更多例子</font></h2>
<div class="section" id="sentence-segmentation"><h2 class="sigil_not_in_toc"><font id="182">2.1 句子分割</font></h2>
<p><font id="183">句子分割可以看作是一个标点符号的分类任务:每当我们遇到一个可能会结束一个句子的符号,如句号或问号,我们必须决定它是否终止了当前句子。</font></p>
<p><font id="184">第一步是获得一些已被分割成句子的数据,将它转换成一种适合提取特征的形式:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>sents = nltk.corpus.treebank_raw.sents()
<span class="pysrc-prompt">>>> </span>tokens = []
<span class="pysrc-prompt">>>> </span>boundaries = set()
<span class="pysrc-prompt">>>> </span>offset = 0
<span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">for</span> sent <span class="pysrc-keyword">in</span> sents:
<span class="pysrc-more">... </span> tokens.extend(sent)
<span class="pysrc-more">... </span> offset += len(sent)
<span class="pysrc-more">... </span> boundaries.add(offset-1)</pre>
<p><font id="185">在这里,<tt class="doctest"><span class="pre">tokens</span></tt>是单独句子标识符的合并列表,<tt class="doctest"><span class="pre">boundaries</span></tt>是一个包含所有句子边界词符索引的集合。</font><font id="186">下一步,我们需要指定用于决定标点是否表示句子边界的数据特征:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">def</span> <span class="pysrc-defname">punct_features</span>(tokens, i):
<span class="pysrc-more">... </span> return {<span class="pysrc-string">'next-word-capitalized'</span>: tokens[i+1][0].isupper(),
<span class="pysrc-more">... </span> <span class="pysrc-string">'prev-word'</span>: tokens[i-1].lower(),
<span class="pysrc-more">... </span> <span class="pysrc-string">'punct'</span>: tokens[i],
<span class="pysrc-more">... </span> <span class="pysrc-string">'prev-word-is-one-char'</span>: len(tokens[i-1]) == 1}</pre>
<p><font id="187">基于这一特征提取器,我们可以通过选择所有的标点符号创建一个加标签的特征集的列表,然后标注它们是否是边界标识符:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>featuresets = [(punct_features(tokens, i), (i <span class="pysrc-keyword">in</span> boundaries))
<span class="pysrc-more">... </span> <span class="pysrc-keyword">for</span> i <span class="pysrc-keyword">in</span> range(1, len(tokens)-1)
<span class="pysrc-more">... </span> <span class="pysrc-keyword">if</span> tokens[i] <span class="pysrc-keyword">in</span> <span class="pysrc-string">'.?!'</span>]</pre>
<p><font id="188">使用这些特征集,我们可以训练和评估一个标点符号分类器:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>size = int(len(featuresets) * 0.1)
<span class="pysrc-prompt">>>> </span>train_set, test_set = featuresets[size:], featuresets[:size]
<span class="pysrc-prompt">>>> </span>classifier = nltk.NaiveBayesClassifier.train(train_set)
<span class="pysrc-prompt">>>> </span>nltk.classify.accuracy(classifier, test_set)
<span class="pysrc-output">0.936026936026936</span></pre>
<p><font id="189">使用这种分类器进行断句,我们只需检查每个标点符号,看它是否是作为一个边界标识符;在边界标识符处分割词列表。</font><font id="190">在<a class="reference internal" href="./ch06.html#code-classification-based-segmenter">2.1</a>中的清单显示了如何可以做到这一点。</font></p>
<div class="pylisting"><p></p>
<pre class="doctest"><span class="pysrc-keyword">def</span> <span class="pysrc-defname">segment_sentences</span>(words):
start = 0
sents = []
<span class="pysrc-keyword">for</span> i, word <span class="pysrc-keyword">in</span> enumerate(words):
<span class="pysrc-keyword">if</span> word <span class="pysrc-keyword">in</span> <span class="pysrc-string">'.?!'</span> <span class="pysrc-keyword">and</span> classifier.classify(punct_features(words, i)) == True:
sents.append(words[start:i+1])
start = i+1
<span class="pysrc-keyword">if</span> start < len(words):
sents.append(words[start:])
return sents</pre>
<div class="section" id="identifying-dialogue-act-types"><h2 class="sigil_not_in_toc"><font id="192">2.2 识别对话行为类型</font></h2>
<p><font id="193">处理对话时,将对话看作说话者执行的<em>行为</em>是很有用的。</font><font id="194">对于表述行为的陈述句这种解释是最直白的,例如"I forgive you"或"I bet you can't climb that hill"。</font><font id="195">但是问候、问题、回答、断言和说明都可以被认为是基于语言的行为类型。</font><font id="196">识别对话中言语下的<span class="termdef">对话行为</span>是理解谈话的重要的第一步。</font></p>
<p><font id="197">NPS聊天语料库,在<a class="reference external" href="./ch02.html#sec-extracting-text-from-corpora">1</a>中的展示过,包括超过10,000个来自即时消息会话的帖子。</font><font id="198">这些帖子都已经被贴上15 种对话行为类型中的一种标签,例如“陈述”,“情感”,“yn问题”和“Continuer”。</font><font id="199">因此,我们可以利用这些数据建立一个分类器,识别新的即时消息帖子的对话行为类型。</font><font id="200">第一步是提取基本的消息数据。</font><font id="201">我们将调用<tt class="doctest"><span class="pre">xml_posts()</span></tt>来得到一个数据结构,表示每个帖子的XML注释:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>posts = nltk.corpus.nps_chat.xml_posts()[:10000]</pre>
<p><font id="202">下一步,我们将定义一个简单的特征提取器,检查帖子包含什么词:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">def</span> <span class="pysrc-defname">dialogue_act_features</span>(post):
<span class="pysrc-more">... </span> features = {}
<span class="pysrc-more">... </span> <span class="pysrc-keyword">for</span> word <span class="pysrc-keyword">in</span> nltk.word_tokenize(post):
<span class="pysrc-more">... </span> features[<span class="pysrc-string">'contains({})'</span>.format(word.lower())] = True
<span class="pysrc-more">... </span> return features</pre>
<p><font id="203">最后,我们通过为每个帖子提取特征(使用<tt class="doctest"><span class="pre">post.get(<span class="pysrc-string">'class'</span>)</span></tt>获得一个帖子的对话行为类型)构造训练和测试数据,并创建一个新的分类器:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>featuresets = [(dialogue_act_features(post.text), post.get(<span class="pysrc-string">'class'</span>))
<span class="pysrc-more">... </span> <span class="pysrc-keyword">for</span> post <span class="pysrc-keyword">in</span> posts]
<span class="pysrc-prompt">>>> </span>size = int(len(featuresets) * 0.1)
<span class="pysrc-prompt">>>> </span>train_set, test_set = featuresets[size:], featuresets[:size]
<span class="pysrc-prompt">>>> </span>classifier = nltk.NaiveBayesClassifier.train(train_set)
<span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">print</span>(nltk.classify.accuracy(classifier, test_set))
<span class="pysrc-output">0.67</span></pre>
</div>
<div class="section" id="recognizing-textual-entailment"><h2 class="sigil_not_in_toc"><font id="204">2.3 识别文字蕴含</font></h2>
<p><font id="205">识别文字蕴含(RTE)是判断文本<em>T</em>的一个给定片段是否蕴含着另一个叫做“假设”的文本(已经在<a class="reference external" href="./ch01.html#sec-automatic-natural-language-understanding">5</a>讨论过)。</font><font id="206">迄今为止,已经有4个RTE挑战赛,在那里共享的开发和测试数据会提供给参赛队伍。</font><font id="207">这里是挑战赛3开发数据集中的文本/假设对的两个例子。</font><font id="208">标签<em>True</em>表示蕴含成立,<em>False</em>表示蕴含不成立。</font></p>
<font id="226"> <blockquote> <p>Challenge 3, Pair 34 (True)</p>
<blockquote> <p><strong>T</strong>: Parviz Davudi was representing Iran at a meeting of the Shanghai Co-operation Organisation (SCO), the fledgling association that binds Russia, China and four former Soviet republics of central Asia together to fight terrorism.</p>
<p><strong>H</strong>: China is a member of SCO.</p>
</blockquote>
<p>Challenge 3, Pair 81 (False)</p>
<blockquote> <p><strong>T</strong>: According to NC Articles of Organization, the members of LLC company are H. Nelson Beavers, III, H. Chester Beavers and Jennie Beavers Stewart.</p>
<p><strong>H</strong>: Jennie Beavers Stewart is a share-holder of Carolina Analytical Laboratory.</p>
</blockquote>
</blockquote></font><p><font id="209">应当强调,文字和假设之间的关系并不一定是逻辑蕴涵,而是一个人是否会得出结论:文本提供了合理的证据证明假设是真实的。</font></p>
<p><font id="210">我们可以把RTE当作一个分类任务,尝试为每一对预测<em>True</em>/<em>False</em>标签。</font><font id="211">虽然这项任务的成功做法似乎看上去涉及语法分析、语义和现实世界的知识的组合,RTE的许多早期的尝试使用粗浅的分析基于文字和假设之间的在词级别的相似性取得了相当不错的结果。</font><font id="212">在理想情况下,我们希望如果有一个蕴涵那么假设所表示的所有信息也应该在文本中表示。</font><font id="213">相反,如果假设中有的资料文本中没有,那么就没有蕴涵。</font></p>
<p><font id="214">在我们的RTE 特征探测器(<a class="reference internal" href="./ch06.html#code-rte-features">2.2</a>)中,我们让词(即词类型)作为信息的代理,我们的特征计数词重叠的程度和假设中有而文本中没有的词的程度(由<tt class="doctest"><span class="pre">hyp_extra()</span></tt>方法获取)。</font><font id="215">不是所有的词都是同样重要的——命名实体,如人、组织和地方的名称,可能会更为重要,这促使我们分别为<tt class="doctest"><span class="pre">word</span></tt>s和<tt class="doctest"><span class="pre">ne</span></tt>s(命名实体)提取不同的信息。</font><font id="216">此外,一些高频虚词作为“停用词”被过滤掉。</font></p>
<pre class="doctest"><span class="pysrc-keyword">def</span> <span class="pysrc-defname">rte_features</span>(rtepair):
extractor = nltk.RTEFeatureExtractor(rtepair)
features = {}
features[<span class="pysrc-string">'word_overlap'</span>] = len(extractor.overlap(<span class="pysrc-string">'word'</span>))
features[<span class="pysrc-string">'word_hyp_extra'</span>] = len(extractor.hyp_extra(<span class="pysrc-string">'word'</span>))
features[<span class="pysrc-string">'ne_overlap'</span>] = len(extractor.overlap(<span class="pysrc-string">'ne'</span>))
features[<span class="pysrc-string">'ne_hyp_extra'</span>] = len(extractor.hyp_extra(<span class="pysrc-string">'ne'</span>))
return features</pre>
<p><font id="222">为了说明这些特征的内容,我们检查前面显示的文本/假设对34的一些属性:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>rtepair = nltk.corpus.rte.pairs([<span class="pysrc-string">'rte3_dev.xml'</span>])[33]
<span class="pysrc-prompt">>>> </span>extractor = nltk.RTEFeatureExtractor(rtepair)
<span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">print</span>(extractor.text_words)
<span class="pysrc-output">{'Russia', 'Organisation', 'Shanghai', 'Asia', 'four', 'at',</span>
<span class="pysrc-output">'operation', 'SCO', ...}</span>
<span class="pysrc-output"></span><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">print</span>(extractor.hyp_words)
<span class="pysrc-output">{'member', 'SCO', 'China'}</span>
<span class="pysrc-output"></span><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">print</span>(extractor.overlap(<span class="pysrc-string">'word'</span>))
<span class="pysrc-output">set()</span>
<span class="pysrc-output"></span><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">print</span>(extractor.overlap(<span class="pysrc-string">'ne'</span>))
<span class="pysrc-output">{'SCO', 'China'}</span>
<span class="pysrc-output"></span><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">print</span>(extractor.hyp_extra(<span class="pysrc-string">'word'</span>))
<span class="pysrc-output">{'member'}</span></pre>
<p><font id="223">这些特征表明假设中所有重要的词都包含在文本中,因此有一些证据支持标记这个为<em>True</em>。</font></p>
<p><font id="224"><tt class="doctest"><span class="pre">nltk.classify.rte_classify</span></tt>模块使用这些方法在合并的RTE测试数据上取得了刚刚超过58%的准确率。</font><font id="225">这个数字并不是很令人印象深刻的,还需要大量的努力,更多的语言学处理,才能达到更好的结果。</font></p>
<div class="section" id="scaling-up-to-large-datasets"><h2 class="sigil_not_in_toc"><font id="227">2.4 扩展到大型数据集</font></h2>
<p><font id="228">Python提供了一个良好的环境进行基本的文本处理和特征提取。</font><font id="229">然而,它处理机器学习方法需要的密集数值计算不能够如C语言那样的低级语言那么快。因此,如果你尝试在大型数据集使用纯Python 的机器学习实现(如<tt class="doctest"><span class="pre">nltk.NaiveBayesClassifier</span></tt>),你可能会发现学习算法会花费大量的时间和内存。</font></p>
<p><font id="230">如果你打算用大量训练数据或大量特征来训练分类器,我们建议你探索NLTK与外部机器学习包的接口。</font><font id="231">只要这些软件包已安装,NLTK可以透明地调用它们(通过系统调用)来训练分类模型,明显比纯Python的分类实现快。</font><font id="232">请看NLTK网站上推荐的NLTK支持的机器学习包列表。</font></p>
</div>
<div class="section" id="evaluation"><h2 class="sigil_not_in_toc"><font id="233">3 评估</font></h2>
<p><font id="234">为了决定一个分类模型是否准确地捕捉了模式,我们必须评估该模型。</font><font id="235">评估的结果对于决定模型是多么值得信赖以及我们如何使用它是非常重要。</font><font id="236">评估也可以是一个有效的工具,用于指导我们在未来改进模型。</font></p>
<div class="section" id="the-test-set"><h2 class="sigil_not_in_toc"><font id="237">3.1 测试集</font></h2>
<p><font id="238">大多数评估技术为模型计算一个得分,通过比较它在<span class="termdef">测试集</span>(或<span class="termdef">评估集</span>)中为输入生成的标签与那些输入的正确标签。</font><font id="239">该测试集通常与训练集具有相同的格式。</font><font id="240">然而,测试集与训练语料不同是非常重要的:如果我们简单地重复使用训练集作为测试集,那么一个只记住了它的输入而没有学会如何推广到新的例子的模型会得到误导人的高分。</font></p>
<p><font id="241">建立测试集时,往往是一个可用于测试的和可用于训练的数据量之间的权衡。</font><font id="242">对于有少量平衡的标签和一个多样化的测试集的分类任务,只要100个评估实例就可以进行有意义的评估。</font><font id="243">但是,如果一个分类任务有大量的标签或包括非常罕见的标签,那么选择的测试集的大小就要保证出现次数最少的标签至少出现50次。</font><font id="244">此外,如果测试集包含许多密切相关的实例——例如来自一个单独文档中的实例——那么测试集的大小应增加,以确保这种多样性的缺乏不会扭曲评估结果。</font><font id="245">当有大量已标注数据可用时,只使用整体数据的10%进行评估常常会在安全方面犯错。</font></p>
<p><font id="246">选择测试集时另一个需要考虑的是测试集中实例与开发集中的实例的相似程度。</font><font id="247">这两个数据集越相似,我们对将评估结果推广到其他数据集的信心就越小。</font><font id="248">例如,考虑词性标注任务。</font><font id="249">在一种极端情况,我们可以通过从一个反映单一的文体(如新闻)的数据源随机分配句子,创建训练集和测试集:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">import</span> random
<span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">from</span> nltk.corpus <span class="pysrc-keyword">import</span> brown
<span class="pysrc-prompt">>>> </span>tagged_sents = list(brown.tagged_sents(categories=<span class="pysrc-string">'news'</span>))
<span class="pysrc-prompt">>>> </span>random.shuffle(tagged_sents)
<span class="pysrc-prompt">>>> </span>size = int(len(tagged_sents) * 0.1)
<span class="pysrc-prompt">>>> </span>train_set, test_set = tagged_sents[size:], tagged_sents[:size]</pre>
<p><font id="250">在这种情况下,我们的测试集和训练集将是<em>非常</em>相似的。</font><font id="251">训练集和测试集均取自同一文体,所以我们不能相信评估结果可以推广到其他文体。</font><font id="252">更糟糕的是,因为调用<tt class="doctest"><span class="pre">random.shuffle()</span></tt>,测试集中包含来自训练使用过的相同的文档的句子。</font><font id="253">如果文档中有相容的模式(也就是说,如果一个给定的词与特定词性标记一起出现特别频繁),那么这种差异将体现在开发集和测试集。</font><font id="254">一个稍好的做法是确保训练集和测试集来自不同的文件:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>file_ids = brown.fileids(categories=<span class="pysrc-string">'news'</span>)
<span class="pysrc-prompt">>>> </span>size = int(len(file_ids) * 0.1)
<span class="pysrc-prompt">>>> </span>train_set = brown.tagged_sents(file_ids[size:])
<span class="pysrc-prompt">>>> </span>test_set = brown.tagged_sents(file_ids[:size])</pre>
<p><font id="255">如果我们要执行更令人信服的评估,可以从与训练集中文档联系更少的文档中获取测试集:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>train_set = brown.tagged_sents(categories=<span class="pysrc-string">'news'</span>)
<span class="pysrc-prompt">>>> </span>test_set = brown.tagged_sents(categories=<span class="pysrc-string">'fiction'</span>)</pre>
<p><font id="256">如果我们在此测试集上建立了一个性能很好的分类器,那么我们完全可以相信它有能力很好的泛化到用于训练它的数据以外的数据。</font></p>
</div>
<div class="section" id="accuracy"><h2 class="sigil_not_in_toc"><font id="257">3.2 准确度</font></h2>
<p><font id="258">用于评估一个分类最简单的度量是<span class="termdef">准确度</span>,测量测试集上分类器正确标注的输入的比例。</font><font id="259">例如,一个名字性别分类器,在包含80个名字的测试集上预测正确的名字有60个,它有60/80 = 75%的准确度。</font><font id="260"><tt class="doctest"><span class="pre">nltk.classify.accuracy()</span></tt>函数会在给定的测试集上计算分类器模型的准确度:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span>classifier = nltk.NaiveBayesClassifier.train(train_set)
<span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">print</span>(<span class="pysrc-string">'Accuracy: {:4.2f}'</span>.format(nltk.classify.accuracy(classifier, test_set)))
<span class="pysrc-output">0.75</span></pre>
<p><font id="261">解释一个分类器的准确性得分,考虑测试集中单个类标签的频率是很重要的。</font><font id="262">例如,考虑一个决定词<span class="example">bank</span>每次出现的正确的词意的分类器。</font><font id="263">如果我们在金融新闻文本上评估分类器,那么我们可能会发现,<tt class="doctest"><span class="pre">金融机构</span></tt>的意思20个里面出现了19次。</font><font id="264">在这种情况下,95%的准确度也难以给人留下深刻印象,因为我们可以实现一个模型,它总是返回<tt class="doctest"><span class="pre">金融机构</span></tt>的意义。</font><font id="265">然而,如果我们在一个更加平衡的语料库上评估分类器,那里的最频繁的词意只占40%,那么95%的准确度得分将是一个更加积极的结果。</font><font id="266">(在<a class="reference external" href="./ch11.html#sec-life-cycle-of-a-corpus">2</a>测量标注一致性程度时也会有类似的问题。)</font></p>
</div>
<div class="section" id="precision-and-recall"><h2 class="sigil_not_in_toc"><font id="267">3.3 精确度和召回率</font></h2>
<p><font id="268">另一个准确度分数可能会产生误导的实例是在“搜索”任务中,如信息检索,我们试图找出与特定任务有关的文档。</font><font id="269">由于不相关的文档的数量远远多于相关文档的数量,一个将每一个文档都标记为无关的模型的准确度分数将非常接近100%。</font></p>
<div class="figure" id="fig-precision-recall"><img alt="Images/precision-recall.png" src="Images/e591e60c490795add5183c998132ebc0.jpg" style="width: 523.0px; height: 283.5px;"/><p class="caption"><font id="270"><span class="caption-label">图 3.1</span>:真与假的阳性和阴性</font></p>
</div>
<p><font id="271">因此,对搜索任务使用不同的测量集是很常见的,基于<a class="reference internal" href="./ch06.html#fig-precision-recall">3.1</a>所示的四个类别的每一个中的项目的数量:</font></p>
<ul class="simple"><li><font id="272"><span class="termdef">真阳性</span>是相关项目中我们正确识别为相关的。</font></li>
<li><font id="273"><a name="true_negatives_index_term"><span class="termdef">真阴性</span>是不相关项目中我们正确识别为不相关的。</a></font></li>
<li><font id="274"><a name="false_positives_index_term"><span class="termdef">假阳性</span>(或</a><span class="termdef">I 型错误</span>)是不相关项目中我们错误识别为相关的。</font></li>
<li><font id="275"><a name="false_negatives_index_term"><span class="termdef">假阴性</span>(或</a><span class="termdef">II型错误</span>)是相关项目中我们错误识别为不相关的。</font></li>
</ul>
<p><font id="276">给定这四个数字,我们可以定义以下指标:</font></p>
<ul class="simple"><li><font id="277"><a name="precision_index_term"><span class="termdef">精确度</span>,表示我们发现的项目中有多少是相关的,<span class="math">TP/(TP+FP)</span>。</a></font></li>
<li><font id="278"><a name="recall_index_term"><span class="termdef">召回率</span>,表示相关的项目中我们发现了多少,<span class="math">TP/(TP+FN)</span>。</a></font></li>
<li><font id="279"><span class="termdef">F-度量值</span>(或<span class="termdef">F-Score</span>),组合精确度和召回率为一个单独的得分,被定义为精确度和召回率的调和平均数(2 × <em>Precision</em> × <em>Recall</em>) / (<em>Precision</em> + <em>Recall</em>)。</font></li>
</ul>
</div>
<div class="section" id="confusion-matrices"><h2 class="sigil_not_in_toc"><font id="280">3.4 混淆矩阵</font></h2>
<p><font id="281">当处理有3 个或更多的标签的分类任务时,基于模型错误类型细分模型的错误是有信息量的。</font><font id="282">一个<span class="termdef">混淆矩阵</span>是一个表,其中每个cells [<span class="math">i</span>,<span class="math">j</span>]表示正确的标签<span class="math">i</span>被预测为标签<span class="math">j</span>的次数。</font><font id="283">因此,对角线项目(即cells <a href="./ch06.html#id17"><span class="problematic" id="id18">|ii|</span></a>)表示正确预测的标签,非对角线项目表示错误。</font><font id="284">在下面的例子中,我们为<a class="reference external" href="./ch05.html#sec-automatic-tagging">4</a>中开发的一元标注器生成一个混淆矩阵:</font></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">def</span> <span class="pysrc-defname">tag_list</span>(tagged_sents):
<span class="pysrc-more">... </span> return [tag <span class="pysrc-keyword">for</span> sent <span class="pysrc-keyword">in</span> tagged_sents <span class="pysrc-keyword">for</span> (word, tag) <span class="pysrc-keyword">in</span> sent]
<span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">def</span> <span class="pysrc-defname">apply_tagger</span>(tagger, corpus):
<span class="pysrc-more">... </span> return [tagger.tag(nltk.tag.untag(sent)) <span class="pysrc-keyword">for</span> sent <span class="pysrc-keyword">in</span> corpus]
<span class="pysrc-prompt">>>> </span>gold = tag_list(brown.tagged_sents(categories=<span class="pysrc-string">'editorial'</span>))
<span class="pysrc-prompt">>>> </span>test = tag_list(apply_tagger(t2, brown.tagged_sents(categories=<span class="pysrc-string">'editorial'</span>)))
<span class="pysrc-prompt">>>> </span>cm = nltk.ConfusionMatrix(gold, test)
<span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">print</span>(cm.pretty_format(sort_by_count=True, show_percents=True, truncate=9))
<span class="pysrc-output"> | N |</span>
<span class="pysrc-output"> | N I A J N V N |</span>
<span class="pysrc-output"> | N N T J . S , B P |</span>
<span class="pysrc-output">----+----------------------------------------------------------------+</span>
<span class="pysrc-output"> NN | <11.8%> 0.0% . 0.2% . 0.0% . 0.3% 0.0% |</span>
<span class="pysrc-output"> IN | 0.0% <9.0%> . . . 0.0% . . . |</span>
<span class="pysrc-output"> AT | . . <8.6%> . . . . . . |</span>
<span class="pysrc-output"> JJ | 1.7% . . <3.9%> . . . 0.0% 0.0% |</span>
<span class="pysrc-output"> . | . . . . <4.8%> . . . . |</span>
<span class="pysrc-output">NNS | 1.5% . . . . <3.2%> . . 0.0% |</span>
<span class="pysrc-output"> , | . . . . . . <4.4%> . . |</span>
<span class="pysrc-output"> VB | 0.9% . . 0.0% . . . <2.4%> . |</span>
<span class="pysrc-output"> NP | 1.0% . . 0.0% . . . . <1.8%>|</span>
<span class="pysrc-output">----+----------------------------------------------------------------+</span>
<span class="pysrc-output">(row = reference; col = test)</span>
<span class="pysrc-output"></span></pre>
<p><font id="285">这个混淆矩阵显示常见的错误,包括<tt class="doctest"><span class="pre">NN</span></tt>替换为了<tt class="doctest"><span class="pre">JJ</span></tt>(1.6%的词),<tt class="doctest"><span class="pre">NN</span></tt>替换为了<tt class="doctest"><span class="pre">NNS</span></tt>(1.5%的词)。</font><font id="286">注意点(<tt class="doctest"><span class="pre">.</span></tt>)表示值为0 的单元格,对角线项目——对应正确的分类——用尖括号标记。</font><font id="287">.. XXX explain use of "reference" in the legend above.</font></p>
</div>
<div class="section" id="cross-validation"><h2 class="sigil_not_in_toc"><font id="288">3.5 交叉验证</font></h2>
<p><font id="289">为了评估我们的模型,我们必须为测试集保留一部分已标注的数据。</font><font id="290">正如我们已经提到,如果测试集是太小了,我们的评价可能不准确。</font><font id="291">然而,测试集设置较大通常意味着训练集设置较小,如果已标注数据的数量有限,这样设置对性能会产生重大影响。</font></p>
<p><font id="292">这个问题的解决方案之一是在不同的测试集上执行多个评估,然后组合这些评估的得分,这种技术被称为<span class="termdef">交叉验证</span>。</font><font id="293">特别是,我们将原始语料细分为<span class="math">N</span>个子集称为<span class="termdef">折叠</span>。</font><font id="294">对于每一个这些的折叠,我们使用<em>除</em>这个折叠中的数据外其他所有数据训练模型,然后在这个折叠上测试模型。</font><font id="295">即使个别的折叠可能是太小了而不能在其上给出准确的评价分数,综合评估得分是基于大量的数据,因此是相当可靠的。</font></p>
<p><font id="296">第二,同样重要的,采用交叉验证的优势是,它可以让我们研究不同的训练集上性能变化有多大。</font><font id="297">如果我们从所有<span class="math">N</span>个训练集得到非常相似的分数,然后我们可以相当有信心,得分是准确的。</font><font id="298">另一方面,如果<span class="math">N</span>个训练集上分数很大不同,那么,我们应该对评估得分的准确性持怀疑态度。</font></p>
</div>
</div>
<div class="section" id="decision-trees"><h2 class="sigil_not_in_toc"><font id="299">4 决策树</font></h2>
<p><font id="300">接下来的三节中,我们将仔细看看可用于自动生成分类模型的三种机器学习方法:决策树、朴素贝叶斯分类器和最大熵分类器。</font><font id="301">正如我们所看到的,可以把这些学习方法看作黑盒子,直接训练模式,使用它们进行预测而不需要理解它们是如何工作的。</font><font id="302">但是,仔细看看这些学习方法如何基于一个训练集上的数据选择模型,会学到很多。</font><font id="303">了解这些方法可以帮助指导我们选择相应的特征,尤其是我们关于那些特征如何编码的决定。</font><font id="304">理解生成的模型可以让我们更好的提取信息,哪些特征对有信息量,那些特征之间如何相互关联。</font></p>
<p><font id="305"><span class="termdef">决策树</span>是一个简单的为输入值选择标签的流程图。</font><font id="306">这个流程图由检查特征值的<span class="termdef">决策节点</span>和分配标签的<span class="termdef">叶节点</span>组成。</font><font id="307">为输入值选择标签,我们以流程图的初始决策节点开始,称为其<span class="termdef">根节点</span>。</font><font id="308">此节点包含一个条件,检查输入值的特征之一,基于该特征的值选择一个分支。</font><font id="309">沿着这个描述我们输入值的分支,我们到达了一个新的决策节点,有一个关于输入值的特征的新的条件。</font><font id="310">我们继续沿着每个节点的条件选择的分支,直到到达叶节点,它为输入值提供了一个标签。</font><font id="311"><a class="reference internal" href="./ch06.html#fig-decision-tree">4.1</a>显示名字性别任务的决策树模型的例子。</font></p>
<div class="figure" id="fig-decision-tree"><img alt="Images/decision-tree.png" src="Images/1ab45939cc9babb242ac45ed03a94f7a.jpg" style="width: 197.5px; height: 93.0px;"/><p class="caption"><font id="312"><span class="caption-label">图 4.1</span>:名字性别任务的决策树模型。</font><font id="313">请注意树图按照习惯画出“颠倒的”,根在上面,叶子在下面。</font></p>
</div>
<p><font id="314">一旦我们有了一个决策树,就可以直接用它来分配标签给新的输入值。</font><font id="315">不那么直接的是我们如何能够建立一个模拟给定的训练集的决策树。</font><font id="316">但在此之前,我们看一下建立决策树的学习算法,思考一个简单的任务:为语料库选择最好的“决策树桩”。</font><font id="317"><span class="termdef">决策树桩</span>是只有一个节点的决策树,基于一个特征决定如何为输入分类。</font><font id="318">每个可能的特征值一个叶子,为特征有那个值的输入指定类标签。</font><font id="319">要建立决策树桩,我们首先必须决定哪些特征应该使用。</font><font id="320">最简单的方法是为每个可能的特征建立一个决策树桩,看哪一个在训练数据上得到最高的准确度,也有其他的替代方案,我们将在下面讨论。</font><font id="321">一旦我们选择了一个特征,就可以通过分配一个标签给每个叶子,基于在训练集中所选的例子的最频繁的标签,建立决策树桩(即选择特征具有那个值的例子)。</font></p>
<p><font id="322">给出了选择决策树桩的算法,生长出较大的决策树的算法就很简单了。</font><font id="323">首先,我们选择分类任务的整体最佳的决策树桩。</font><font id="324">然后,我们在训练集上检查每个叶子的准确度。</font><font id="325">没有达到足够的准确度的叶片被新的决策树桩替换,新决策树桩是在根据到叶子的路径选择的训练语料的子集上训练的。</font><font id="326">例如,我们可以使<a class="reference internal" href="./ch06.html#fig-decision-tree">4.1</a>中的决策树生长,通过替换最左边的叶子为新的决策树桩,这个新的决策树桩是在名字不以"k"开始或以一个元音或"l"结尾的训练集的子集上训练的。</font></p>
<div class="section" id="entropy-and-information-gain"><h2 class="sigil_not_in_toc"><font id="327">4.1 熵和信息增益</font></h2>
<p><font id="328">正如之前提到的,有几种方法来为决策树桩确定最有信息量的特征。</font><font id="329">一种流行的替代方法,被称为<span class="termdef">信息增益</span>,当我们用给定的特征分割输入值时,衡量它们变得更有序的程度。</font><font id="330">要衡量原始输入值集合如何无序,我们计算它们的标签的墒,如果输入值的标签非常不同,墒就高;如果输入值的标签都相同,墒就低。</font><font id="331">特别地,熵被定义为每个标签的概率乘以那个标签的log概率的总和:</font></p>
<p></p>
<pre class="doctest"><span class="pysrc-keyword">import</span> math
<span class="pysrc-keyword">def</span> <span class="pysrc-defname">entropy</span>(labels):
freqdist = nltk.FreqDist(labels)
probs = [freqdist.freq(l) <span class="pysrc-keyword">for</span> l <span class="pysrc-keyword">in</span> freqdist]
return -sum(p * math.log(p,2) <span class="pysrc-keyword">for</span> p <span class="pysrc-keyword">in</span> probs)</pre>
<p><font id="341">一旦我们已经计算了原始输入值的标签集的墒,就可以判断应用了决策树桩之后标签会变得多么有序。</font><font id="342">为了这样做,我们计算每个决策树桩的叶子的熵,利用这些叶子熵值的平均值(加权每片叶子的样本数量)。</font><font id="343">信息增益等于原来的熵减去这个新的减少的熵。</font><font id="344">信息增益越高,将输入值分为相关组的决策树桩就越好,于是我们可以通过选择具有最高信息增益的决策树桩来建立决策树。</font></p>
<p><font id="345">决策树的另一个考虑因素是效率。</font><font id="346">前面描述的选择决策树桩的简单算法必须为每一个可能的特征构建候选决策树桩,并且这个过程必须在构造决策树的每个节点上不断重复。</font><font id="347">已经开发了一些算法通过存储和重用先前评估的例子的信息减少训练时间。</font></p>
<p><font id="348">决策树有一些有用的性质。</font><font id="349">首先,它们简单明了,容易理解。</font><font id="350">决策树顶部附近尤其如此,这通常使学习算法可以找到非常有用的特征。</font><font id="351">决策树特别适合有很多层次的分类区别的情况。</font><font id="352">例如,决策树可以非常有效地捕捉进化树。</font></p>
<p><font id="353">然而,决策树也有一些缺点。</font><font id="354">一个问题是,由于决策树的每个分支会划分训练数据,在训练树的低节点,可用的训练数据量可能会变得非常小。</font><font id="355">因此,这些较低的决策节点可能</font></p>
<p><font id="356"><span class="termdef">过拟合</span>训练集,学习模式反映训练集的特质而不是问题底层显著的语言学模式。</font><font id="357">对这个问题的一个解决方案是当训练数据量变得太小时停止分裂节点。</font><font id="358">另一种方案是长出一个完整的决策树,但随后<span class="termdef">剪去</span>在开发测试集上不能提高性能的决策节点。</font></p>
<p><font id="359">决策树的第二个问题是,它们强迫特征按照一个特定的顺序进行检查,即使特征可能是相对独立的。</font><font id="360">例如,按主题分类文档(如体育、汽车或谋杀之谜)时,特征如<tt class="doctest"><span class="pre">hasword(football)</span></tt>极可能表示一个特定标签,无论其他的特征值是什么。</font><font id="361">由于决策树顶部附近的空间有限,大部分这些特征将需要在树中的许多不同的分支中重复。</font><font id="362">因为越往树的下方,分支的数量成指数倍增长,重复量可能非常大。</font></p>
<p><font id="363">一个相关的问题是决策树不善于利用对正确的标签具有较弱预测能力的特征。</font><font id="364">由于这些特征的影响相对较小,它们往往出现在决策树非常低的地方。</font><font id="365">决策树学习的时间远远不够用到这些特征,也不能留下足够的训练数据来可靠地确定它们应该有什么样的影响。</font><font id="366">如果我们能够在整个训练集中看看这些特征的影响,那么我们也许能够做出一些关于它们是如何影响标签的选择的结论。</font></p>
<p><font id="367">决策树需要按一个特定的顺序检查特征的事实,限制了它们的利用相对独立的特征的能力。</font><font id="368">我们下面将讨论的朴素贝叶斯分类方法克服了这一限制,允许所有特征“并行”的起作用。</font></p>
<div class="section" id="naive-bayes-classifiers"><h2 class="sigil_not_in_toc"><font id="369">5 朴素贝叶斯分类器</font></h2>
<p><font id="370">在<span class="termdef">朴素贝叶斯</span>分类器中,每个特征都得到发言权,来确定哪个标签应该被分配到一个给定的输入值。</font><font id="371">为一个输入值选择标签,朴素贝叶斯分类器以计算每个标签的<span class="termdef">先验概率</span>开始,它由在训练集上检查每个标签的频率来确定。</font><font id="372">之后,每个特征的贡献与它的先验概率组合,得到每个标签的似然估计。</font><font id="373">似然估计最高的标签会分配给输入值。</font><font id="374"><a class="reference internal" href="./ch06.html#fig-naive-bayes-triangle">5.1</a>说明了这一过程。</font></p>
<div class="figure" id="fig-naive-bayes-triangle"><img alt="Images/naive-bayes-triangle.png" src="Images/fff90c564d2625f739b442b23301906e.jpg" style="width: 370.2px; height: 237.6px;"/><p class="caption"><font id="375"><span class="caption-label">图 5.1</span>:使用朴素贝叶斯分类器为文档选择主题的程序的抽象图解。</font><font id="376">在训练语料中,大多数文档是有关汽车的,所以分类器从接近“汽车”的标签的点上开始。</font><font id="377">但它会考虑每个特征的影响。</font><font id="378">在这个例子中,输入文档中包含的词"dark",它是谋杀之谜的一个不太强的指标,也包含词"football",它是体育文档的一个有力指标。</font><font id="379">每个特征都作出了贡献之后,分类器检查哪个标签最接近,并将该标签分配给输入。</font></p>
</div>
<p><font id="380">个别特征对整体决策作出自己的贡献,通过“投票反对”那些不经常出现的特征的标签。</font><font id="381">特别是,每个标签的似然得分由于与输入值具有此特征的标签的概率相乘而减小。</font><font id="382">例如,如果词<span class="example">run</span>在12%的体育文档中出现,在10%的谋杀之谜的文档中出现,在2%的汽车文档中出现,那么体育标签的似然得分将被乘以0.12,谋杀之谜标签将被乘以0.1,汽车标签将被乘以0.02。</font><font id="383">整体效果是略高于体育标签的得分的谋杀之谜标签的得分会减少,而汽车标签相对于其他两个标签会显著减少。</font><font id="384">这个过程如<a class="reference internal" href="./ch06.html#fig-naive-bayes-bargraph">5.2</a>和<a class="reference internal" href="./ch06.html#fig-naive-bayes-graph">5.3</a>所示。</font></p>
<div class="figure" id="fig-naive-bayes-bargraph"><img alt="Images/naive_bayes_bargraph.png" src="Images/b502c97e1f935240559d38b397805b32.jpg" style="width: 660.0px; height: 367.5px;"/><p class="caption"><font id="385"><span class="caption-label">图 5.2</span>:计算朴素贝叶斯的标签似然得分。</font><font id="386">朴素贝叶斯以计算每个标签的先验概率开始,基于每个标签出现在训练数据中的频率。</font><font id="387">然后每个特征都用于估计每个标签的似然估计,通过用输入值中有那个特征的标签的概率乘以它。</font><font id="388">似然得分结果可以认为是从具有给定的标签和特征集的训练集中随机选取的值的概率的估计,假设所有特征概率是独立的。</font></p>
</div>
<div class="section" id="underlying-probabilistic-model"><h2 class="sigil_not_in_toc"><font id="389">5.1 底层的概率模型</font></h2>
<p><font id="390">理解朴素贝叶斯分类器的另一种方式是它为输入选择最有可能的标签,基于下面的假设:每个输入值是通过首先为那个输入值选择一个类标签,然后产生每个特征的方式产生的,每个特征与其他特征完全独立。</font><font id="391">当然,这种假设是不现实的,特征往往高度依赖彼此。</font><font id="392">我们将在本节结尾回过来讨论这个假设的一些后果。</font><font id="393">这简化的假设,称为<span class="termdef">朴素贝叶斯假设</span>(或<span class="termdef">独立性假设</span>),使得它更容易组合不同特征的贡献,因为我们不必担心它们相互影响。</font></p>
<div class="figure" id="fig-naive-bayes-graph"><img alt="Images/naive_bayes_graph.png" src="Images/3edaf7564caaab7c8ddc83db1d5408a3.jpg" style="width: 266.4px; height: 163.79999999999998px;"/><p class="caption"><font id="394"><span class="caption-label">图 5.3</span>:一个<span class="termdef">贝叶斯网络图</span>演示朴素贝叶斯分类器假定的生成过程。</font><font id="395">要生成一个标记的输入,模型先为输入选择标签,然后基于该标签生成每个输入的特征。</font><font id="396">对给定标签,每个特征被认为是完全独立于所有其他特征的。</font></p>
</div>
<p><font id="397">基于这个假设,我们可以计算表达式<span class="math">P(label|features)</span>,给定一个特别的特征集一个输入具有特定标签的概率。</font><font id="398">要为一个新的输入选择标签,我们可以简单地选择使<span class="math">P(l|features)</span>最大的标签<span class="math">l</span>。</font></p>
<p><font id="399">一开始,我们注意到<span class="math">P(label|features)</span>等于具有特定标签<em>和</em>特定特征集的输入的概率除以具有特定特征集的输入的概率:</font></p>
<p></p>
<pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">from</span> nltk.corpus <span class="pysrc-keyword">import</span> senseval
<span class="pysrc-prompt">>>> </span>instances = senseval.instances(<span class="pysrc-string">'hard.pos'</span>)
<span class="pysrc-prompt">>>> </span>size = int(len(instances) * 0.1)
<span class="pysrc-prompt">>>> </span>train_set, test_set = instances[size:], instances[:size]</pre>
<p><font id="736">使用这个数据集,建立一个分类器,预测一个给定的实例的正确的词意标签。</font><font id="737">关于使用Senseval 2 语料库返回的实例对象的信息请参阅<tt class="doctest"><span class="pre">http://nltk.org/howto</span></tt>上的语料库HOWTO。</font></p>
<li><p class="first"><font id="738">☼ 使用本章讨论过的电影评论文档分类器,产生对分类器最有信息量的30个特征的列表。</font><font id="739">你能解释为什么这些特定特征具有信息量吗?</font><font id="740">你能在它们中找到什么惊人的发现吗?</font></p></li>
<li><p class="first"><font id="741">☼ 选择一个本章所描述的分类任务,如名字性别检测、文档分类、词性标注或对话行为分类。</font><font id="742">使用相同的训练和测试数据,相同的特征提取器,建立该任务的三个分类器::决策树、朴素贝叶斯分类器和最大熵分类器。</font><font id="743">比较你所选任务上这三个分类器的准确性。</font><font id="744">你如何看待如果你使用了不同的特征提取器,你的结果可能会不同?</font></p></li>
<li><p class="first"><font id="745">☼ 同义词<span class="example">strong</span>和<span class="example">powerful</span>的模式不同(尝试将它们与<span class="example">chip</span>和<span class="example">sales</span>结合)。</font><font id="746">哪些特征与这种区别有关?</font><font id="747">建立一个分类器,预测每个词何时该被使用。</font></p></li>
<li><p class="first"><font id="748">◑ 对话行为分类器为每个帖子分配标签,不考虑帖子的上下文背景。</font><font id="749">然而,对话行为是高度依赖上下文的,一些对话行序列可能比别的更相近。</font><font id="750">例如,ynQuestion 对话行为更容易被一个<tt class="doctest"><span class="pre">yanswer</span></tt>回答而不是以一个<tt class="doctest"><span class="pre">问候</span></tt>来回答。</font><font id="751">利用这一事实,建立一个连续的分类器,为对话行为加标签。</font><font id="752">一定要考虑哪些特征可能是有用的。</font><font id="753">参见<a class="reference internal" href="./ch06.html#code-consecutive-pos-tagger">1.7</a>词性标记的连续分类器的代码,获得一些想法。</font></p></li>
<li><p class="first"><font id="754">◑ 词特征在处理文本分类中是非常有用的,因为在一个文档中出现的词对于其语义内容是什么具有强烈的指示作用。</font><font id="755">然而,很多词很少出现,一些在文档中的最有信息量的词可能永远不会出现在我们的训练数据中。</font><font id="756">一种解决方法是使用一个<span class="termdef">词典</span>,它描述了词之间的不同。</font><font id="757">使用WordNet词典,加强本章介绍的电影评论文档分类器,使用概括一个文档中出现的词的特征,使之更容易匹配在训练数据中发现的词。</font></p></li>
<li><p class="first"><font id="758">★ PP 附件语料库是描述介词短语附着决策的语料库。</font><font id="759">语料库中的每个实例被编码为<tt class="doctest"><span class="pre">PPAttachment</span></tt>对象:</font></p><pre class="doctest"><span class="pysrc-prompt">>>> </span><span class="pysrc-keyword">from</span> nltk.corpus <span class="pysrc-keyword">import</span> ppattach
<span class="pysrc-prompt">>>> </span>ppattach.attachments(<span class="pysrc-string">'training'</span>)
<span class="pysrc-output">[PPAttachment(sent='0', verb='join', noun1='board',</span>
<span class="pysrc-output"> prep='as', noun2='director', attachment='V'),</span>
<span class="pysrc-output"> PPAttachment(sent='1', verb='is', noun1='chairman',</span>
<span class="pysrc-output"> prep='of', noun2='N.V.', attachment='N'),</span>
<span class="pysrc-output"> ...]</span>
<span class="pysrc-output"></span><span class="pysrc-prompt">>>> </span>inst = ppattach.attachments(<span class="pysrc-string">'training'</span>)[1]
<span class="pysrc-prompt">>>> </span>(inst.noun1, inst.prep, inst.noun2)
<span class="pysrc-output">('chairman', 'of', 'N.V.')</span></pre><p><font id="760">选择<tt class="doctest"><span class="pre">inst.attachment</span></tt>为<tt class="doctest"><span class="pre">N</span></tt>的唯一实例:</font></p><pre class="doctest"><span class="pysrc-prompt">>>> </span>nattach = [inst <span class="pysrc-keyword">for</span> inst <span class="pysrc-keyword">in</span> ppattach.attachments(<span class="pysrc-string">'training'</span>)
<span class="pysrc-more">... </span> <span class="pysrc-keyword">if</span> inst.attachment == <span class="pysrc-string">'N'</span>]</pre><p><font id="761">使用此子语料库,建立一个分类器,尝试预测哪些介词是用来连接一对给定的名词。</font><font id="762">例如,给定的名词对"team"和"researchers",分类器应该预测出介词"of"。</font><font id="763">更多的使用PP附件语料库的信息,参阅<tt class="doctest"><span class="pre">http://nltk.org/howto</span></tt>上的语料库HOWTO。</font></p></li>
<li><p class="first"><font id="764">★ 假设你想自动生成一个场景的散文描述,每个实体已经有了一个唯一描述此实体的词,例如<span class="example">the jar</span>,只是想决定在有关的各项目中是否使用<span class="example">in</span>或<span class="example">on</span>,例如</font><font id="765"><span class="example">the book is in the cupboard</span>对比<span class="example">the book is on the shelf</span>。</font><font id="766">通过查找语料数据探讨这个问题;编写需要的程序。</font></p></li>
<p></p>
<table border="0" cellpadding="0" cellspacing="0" class="example"><tbody><tr valign="top"><td align="right" width="30"><font id="767">(13)</font></td>
<td width="15"></td>
<td><p></p>
<table border="0" cellpadding="0" cellspacing="0" class="example"><tbody><tr valign="top"><td align="right" width="30"><font id="768">a.</font></td>
<td width="15"></td>
<td><font id="769">in the car <em>versus</em> on the train</font></td>
</tr>
</tbody>
</table>
<p></p>
<table border="0" cellpadding="0" cellspacing="0" class="example"><tbody><tr valign="top"><td align="right" width="30"><font id="770">b.</font></td>
<td width="15"></td>
<td><font id="771">in town <em>versus</em> on campus</font></td>
</tr>
</tbody>
</table>
<p></p>
<table border="0" cellpadding="0" cellspacing="0" class="example"><tbody><tr valign="top"><td align="right" width="30"><font id="772">c.</font></td>
<td width="15"></td>
<td><font id="773">in the picture <em>versus</em> on the screen</font></td>
</tr>
</tbody>
</table>
<p></p>
<table border="0" cellpadding="0" cellspacing="0" class="example"><tbody><tr valign="top"><td align="right" width="30"><font id="774">d.</font></td>
<td width="15"></td>
<td><font id="775">in Macbeth <em>versus</em> on Letterman</font></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<div class="admonition-about-this-document admonition"><p class="first admonition-title"><font id="776">关于本文档...</font></p>
<p><font id="777">针对NLTK 3.0 作出更新。</font><font id="778">本章来自于<em>Natural Language Processing with Python</em>,<a class="reference external" href="http://estive.net/">Steven Bird</a>, <a class="reference external" href="http://homepages.inf.ed.ac.uk/ewan/">Ewan Klein</a> 和<a class="reference external" href="http://ed.loper.org/">Edward Loper</a>,Copyright © 2014 作者所有。</font><font id="779">本章依据<em>Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 United States License</em> [<a class="reference external" href="http://creativecommons.org/licenses/by-nc-nd/3.0/us/">http://creativecommons.org/licenses/by-nc-nd/3.0/us/</a>] 条款,与<em>自然语言工具包</em> [<tt class="doctest"><span class="pre">http://nltk.org/</span></tt>] 3.0 版一起发行。</font></p>
<p class="last"><font id="780">本文档构建于星期三 2015 年 7 月 1 日 12:30:05 AEST</font></p>
</div>
</div>
<div class="system-messages section"><h2 class="sigil_not_in_toc"><font id="781">Docutils System Messages</font></h2>
<div class="system-message" id="id17"><p class="system-message-title"><font id="782">System Message: ERROR/3 (<tt class="docutils">ch06.rst2</tt>, line 1264); <em><a href="./ch06.html#id18">backlink</a></em></font></p>
<font id="783"> Undefined substitution referenced: "ii".</font></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>