Python天然语言处置惩罚入门
本文从概念和现实操纵量方面,从零开始,先容在Python中举行天然语言处置惩罚。
(作者案:本文是我最初发表在《ACM Crossroads》Volume 13,Issue 4 上的完备修订版。之以是修订是由于 Natural Language Toolkit(NLTK)改动较大。修订版代码兼容至最新版 NLTK(2013 年 9 月更新至 2.0.4 版)。只管本文的代码同等颠末测试,仍有大概出现一两个题目。假如你发现了题目,请向作者反映。假如你非用 0.7 版不可的话,请参考 这里。
1 缘起
本文试着向读者们先容天然语言处置惩罚(Natural Language Processing)这一范畴,通常简称为 NLP。然而,差别于一样平常只是形貌 NLP 紧张概念的文章,本文还借助 Python 来形象地阐明。对于不认识 Python 的读者们,本文也提供了部门参考资料教你怎样举行 Python 编程。
2 相干先容
2.1 天然语言处置惩罚
天然语言处置惩罚广纳了浩繁技能,对天然或人类语言举行主动天生,处置惩罚与分析。固然大部门 NLP 技能继续自语言学和人工智能,但同样受到诸如呆板学习,盘算统计学和认知科学这些相对新兴的学科影响。
在展示 NLP 技能的例子前,有须要先容些非常底子的术语。请留意:为了让文章普通易懂,这些界说在语言上就不肯定讲求。
[*]词例(Token):对输入文本做任何现实处置惩罚前,都必要将其分割成诸如词、标点符号、数字或纯字母数字(alphanumerics)等语言单位(linguistic units)。这些单位被称为词例。
[*]句子:由有序的词例序列构成。
[*]词例还原(Tokenization):将句子还原成所构成的词例。以分割型语言(segmented languages)英语为例,空格的存在使词例还原变得相对轻易同时也索然无味。然而,对于汉语和阿拉伯语,由于没有清楚的界限,这项工作就稍显困难。别的,在某些非分割型语言(non-segmented languages)中,险些全部的字符(characters)都能以单字(one-character)存在,但同样也可以组合在一起形成多字(multi-characterwords)情势。
[*]语料库:通常是由丰富句子构成的海量文本。
[*]词性标签(Part-of-speech (POS) Tag):任一单词都能被归入到至少一类词搜集(set of lexical)或词性条目(part-of-speech categories)中,比方:名词、动词、形容词和冠词等。词性标签用符号来代表一种词汇条目——NN(名词)、VB(动词)、JJ(形容词)和 AT(冠词)。Brown Corpus 是最久长,也是最常用的标注集之一。详情且听下回分解。
[*]分析树(Parse Tree):使用情势语法(formal grammar)的界说,可以用树状图来表现给定句子的句法(syntactic)布局。
熟悉了根本的术语,下面让我们相识 NLP 常见的使命:
[*]词性标注(POS Tagging):给定一个句子和组词性标签,常见的语言处置惩罚就是对句子中的每个词举行标注。举个例子,The ball is red,词性标注后将酿成 The/AT ball/NN is/VB red/JJ。最先辈的词性标注器正确率高达 96%。文本的词性标注对于更复杂的 NLP 题目,比方我们背面会讨论到的句法分析(parsing)和呆板翻译(machine translation)非常须要。
[*]盘算形态学(Computational Morphology):大量创建在“语素”(morphemes/stems)底子上的词构成了天然语言,语素固然是最小的语言单位,却富含意义。盘算形态学所关心的是用盘算机发掘和分析词的内部布局。
[*]句法分析(Parsing):在语法分析的题目中,句法分析器(parser)将给定句子构造成分析树。为了分析语法,某些分析器假定一系列语法规则存在,但现在的剖析器已经充足机警地借助复杂的统计模子直接推断分析树。多数分析器可以或许在监视式设置(supervised setting)下操纵而且句子已经被词性标注过了。统计句法分析是天然语言处置惩罚中非常活泼的研究范畴。
[*]呆板翻译(Machine Translation(MT)):呆板翻译的目标是让盘算机在没有人工干预的环境下,将给定某种语言的文本流通地翻译成另一种语言文本。这是天然语言处置惩罚中最困难的使命之一,这些年来已经用很多差别的方式办理。险些全部的呆板翻译方法都依靠了词性标注和句法分析作为预处置惩罚。
2.2 Python
Python 是一种动态范例(dynamically-typed),面向对象的表明式(interpreted)编程语言。固然它的重要上风在于答应编程职员快速开辟项目,但是大量的尺度库使它依然能顺应大规模产物级工程项目。Python 的学习曲线非常陡峭而且有很多良好的在线学习资源。
2.3 天然语言工具集(Natural Language Toolkit)
只管 Python 绝大部门的功能可以或许办理简朴的 NLP 使命,但不敷以处置惩罚尺度的天然语言处置惩罚使命。这就是 NLTK (天然语言处置惩罚工具集)诞生的缘故原由。NLTK 集成了模块和语料,以开源允许发布,答应门生对天然语言处置惩罚研究学习和生产研究。利用 NLTK 最大的上风是集成化(entirely self-contained),不但提供了方便的函数和封装用于创建常见天然语言处置惩罚使命块,而且提供原始和预处置惩罚的尺度语料库版本应用在天然语言处置惩罚的文献和课程中。
3 利用 NLTK
NLTK 官网提供了很棒的阐明文件和教程举行学习引导。单纯复述那些作者们的笔墨对于他们和本文都不公平。因此我会通过处置惩罚四个难度系数依次上升的 NLP 使命来先容 NLTK。这些使命都来自于 NLTK 教程中没有给出答案的训练大概变革过。以是每个使命的办理办法和分析都是本文原创的。
3.1 NLTK 语料库
正如前文所说,NLTK 席卷数个在 NLP 研究圈里广泛利用的实用语料库。在本节中,我们来看看三个下文会用到的语料库:
[*]布朗语料库(Brown Corpus):Brown Corpus of Standard American English 被以为是第一个可以在盘算语言学处置惩罚中利用的通用英语语料库。它包罗了一百万字 1961 年出书的美语文本。它代表了通用英语的样本,采样自小说,消息和宗教文本。随后,在大量的人工标注后,诞生了词性标注过的版本。
[*]古登堡语料库(Gutenberg Corpus):古登堡语料库从最大的在线免费电子书平台 古登堡筹划(Gutenberg Project) 中选择了 14 个文本,整个语料库包罗了一百七十万字。
[*]Stopwords Corpus:除了通例的文本笔墨,另一类诸如介词,补语,限定词等含有紧张的语法功能,自身却没有什么寄义的词被称为停用词(stop words)。NLTK 所网络的停用词语料库(Stopwords Corpus)包罗了 来自 11 种差别语言(包罗英语)的 2400 个停用词。
3.2 NLTK 定名约定
在开始使用 NLTK 处置惩罚我们的使命从前,我们先来认识一下它的定名约定(naming conventions)。最顶层的包(package)是 nltk,我们通过利用完全限定(fully qualified)的加点名称比方:nltk.corpus and nltk.utilities 来引用它的内置模块。任何模块都能使用 Python 的尺度布局 from . . . import . . . 来导入顶层的定名空间。
3.3 使命 1 : 探索语料库
上文提到,NLTK 含有多个 NLP 语料库。我们把这个使命订定为探索此中某个语料库。
使命:用 NLTK 的 corpus 模块读取包罗在古登堡语料库的 austen-persuasion.txt,答复以下题目:
[*]这个语料库一共有多少字?
[*]这个语料库有多少个唯一单词(unique words)?
[*]前 10 个频率最高的词出现了频频?
使用 corpus 模块可以探索内置的语料库,而且 NLTK 还提供了包罗多个好用的类和函数在概率模块中,可以用来盘算使命中的概率分布。此中一个是 FreqDist,它可以跟踪分布中的采样频率(sample frequencies)。清单1 演示了怎样利用这两个模块来处置惩罚第一个使命。
清单 1: NLTK 内置语料库的探索.
# 导入 gutenberg 集
>>> from nltk.corpus import gutenberg
# 都有些什么语料在这个聚集里?
>>> print gutenberg.fileids()
['austen-emma.txt', 'austen-persuasion.txt', 'austen-sense.txt', 'bible-kjv.txt', 'blake-poems.txt', 'bryant-stories.txt', 'burgess-busterbrown.txt', 'carroll-alice.txt', 'chesterton-ball.txt', 'chesterton-brown.txt', 'chesterton-thursday.txt', 'edgeworth-parents.txt', 'melville-moby_dick.txt', 'milton-paradise.txt', 'shakespeare-caesar.txt', 'shakespeare-hamlet.txt', 'shakespeare-macbeth.txt', 'whitman-leaves.txt'
# 导入 FreqDist 类
>>> from nltk import FreqDist
# 频率分布实例化
>>> fd = FreqDist()
# 统计文本中的词例
>>> for word in gutenberg.words('austen-persuasion.txt'):
... fd.inc(word)
...
>>> print fd.N() # total number of samples
98171
>>> print fd.B() # number of bins or unique samples
6132
# 得到前 10 个按频率排序后的词
>>> for word in fd.keys()[:10:
... print word, fd[word
, 6750
the 3120
to 2775
. 2741
and 2739
of 2564
a 1529
in 1346
was 1330
; 1290
解答:简奥斯丁的小说 Persuasion 统共包罗 98171 字和 6141 个唯一单词。别的,最常见的词例是逗号,接着是单词the。究竟上,这个使命末了一部门是最风趣的履历观察之一,完善说明白单词的出现征象。假如你对海量的语料库举行统计,将每个单词的出现次数和单词出现的频率由高到低记载在表中,我们可以直观地发现列表中词频和词序的关系。究竟上,齐普夫(Zipf)证明白这个关系可以表达为数学表达式,比方:对于恣意给定单词,$fr$ = $k$, $f$ 是词频,$r$ 是词的分列,大概是在排序后列表中的词序,而 $k$ 则是一个常数。以是,举个例子,第五高频的词应该比第十高频的词的出现次数要多两倍。在 NLP 文献中,以上的关系通常被称为“齐普夫定律(Zipf’s Law)”。
纵然由齐普夫定律形貌的数学关系不肯定完全正确,但它依然对于人类语言中单词分布的描画很有效——词序小的词很常出现,而轻微词序大一点的则较为少出现,词序非常大的词则险些没有怎么出现。使命 1 末了一部门利用 NLTK 非常轻易通过图形举行可视化,如 清单 1a 所示。相干的 log-log 关系,如图 1,可以很清楚地发现我们语料库中对应的扩展关系。
清单 1a: 利用 NLTK 对齐普夫定律举行作图
>>> from nltk.corpus import gutenberg
>>> from nltk import FreqDist
# 作图必要 matplotlib(可以从 NLTK 下载页得到)
>>> import matplotlib
>>> import matplotlib.pyplot as plt
# 统计 Gutenberg 中每个词例数目
>>> fd = FreqDist()
>>> for text in gutenberg.fileids():
... for word in gutenberg.words(text):
... fd.inc(word)
# 初始化两个空列表来存放词序和词频
>>> ranks = []
>>> freqs = []
# 天生每个词例的(词序,词频)点而且将其添加到相应列表中,
# 留意循环中的 fd 会主动排序
>>> for rank, word in enumerate(fd):
... ranks.append(rank+1)
... freqs.append(fd[word])
...
# 在 log-log 图中展示词序和词频的关系
>>> plt.loglog(ranks, freqs)
>>> plt.xlabel(’frequency(f)’, fontsize=14, fontweight=’bold’)
>>> plt.ylabel(’rank(r)’, fontsize=14, fontweight=’bold’)
>>> plt.grid(True)
>>> plt.show()
图 1: 齐普夫定律在古登堡语料库中实用吗?
3.4 使命 2:猜测单词
如今我们已经探索过语料库了,让我们界说一个使命,可以或许用上之前探索的效果。
使命:练习和创建一个单词猜测器,比方:给定一个练习过语料库,写一个可以或许猜测给定单词的一下个单词的步伐。利用这个猜测器随机天生一个 20 个词的句子。
要创建单词猜测器,我们起首要在练习过的语料库中盘算两个词的次序分布,比方,我们必要累加给定单词接下来这个单词的出现次数。一旦我们盘算出了分布,我们就可以通过输入一个单词,得到它在语料库中全部大概出现的下一个单词列表,而且可以从列表中随机输出一个单词。为了随机天生一个 20 个单词的句子,我只必要给定一个初始单词,使用猜测器来猜测下一个单词,然后重复操纵引导直到句子满 20 个词。清单 2 形貌了怎么使用 NLTK 提供的模块来简朴实现。我们使用简奥斯丁的 Persuasion 作为练习语料库。
清单 2:使用 NLTK 猜测单词
>>> from nltk.corpus import gutenberg
>>> from nltk import ConditionalFreqDist
>>> from random import choice
# 分布实例化
>>> cfd = ConditionalFreqDist()
# 对于每个实例,统计给定词的下一个词数目
>>> prev_word = None
>>> for word in gutenberg.words(’austen-persuasion.txt’):
... cfd[prev_word].inc(word)
... prev_word = word
# 给定“therefore”作为给定词作为猜测器的初始词
>>> word = ’therefore’
>>> i = 1
# 找到给定词的全部下一个大概的词,并随机选择一个
>>> while i < 20:
... print word,
... lwords = cfd[word].samples()
... follower = choice(lwords)
... word = follower
... i += 1
...
therefore it known of women ought. Leave me so well
placed in five altogether well placed themselves delighted
解答:输出的 20 个单词的句子固然不合语法。但就词的角度两两来看,是合语法的,由于用以估计条件分布概率(conditional frequency distribution)的练习语料库是合乎语法的,而我们正是利用了这个条件分布概率。留意在本使命中,我们利用前一个词作为猜测器的上下文提示。显然也可以利用前两个,乃至前三个词。
3.5 Task 3: 探索词性标签
NLTK 联合了一系列良好的模块答应我们练习和构建相对复杂的词性标注器。然而,对于这次的使命,我们只限于对 NTLK 内置的已经标注过的语料库举行简朴分析。
使命:对内置的布朗语料库分词(Tokenize)并创建一个或多个得当的数据布局能让我们答复以下题目:
[*]最高频的词性标签是什么?
[*]哪个词被差别的词性标签标注最多
[*]男性代词对于女性代词的出现频率比率怎样?
[*]多少个词是有歧义(ambiguous)的?也就是至少有两个词性标签?
对于这个使命,肯定要留意 NTLK 内置了两个版本的布朗语料库:第一个我们已经在前两个使命中利用了,是原始的版本,第二个是被标注过的版本,亦便是每个句子的每个词例都被精确地词性标注过了。这一版的每个句子生存在元素为二元元组的列表中,形如: (token,tag)。比方标注过的语料库中的一个句子 the ball is green,在 NLTK 会被表现为 [(’the’,’at’), (’ball’,’nn’), (’is’,’vbz’), (’green’,’jj’)]。
前面已经表明过了,布朗语料库包罗 15 个差别的部门,用单词 “a” 到 “r” 来表现。每个部门代表差别的文本范例,如许分是很有须要的,但这不在本文讨论范围内。有了这个信息,我们必须要构建数据布局来分析这个标注过的语料库。思索我们必要办理的题目,我们要运用文本中的词例来发现词性尺度的频率分布和词性标签的条件频率分布。留意 NLTK 同时也答应我们直接从顶层定名空间导入 FreqDist 和 ConditionalFreqDist 类。清单 3 演示了怎样在 NLTK 利用。
清单 3: 借助 NLTK 分析标注过的语料库
>>> from nltk.corpus import brown
>>> from nltk import FreqDist, ConditionalFreqDist
>>> fd = FreqDist()
>>> cfd = ConditionalFreqDist()
# 对于语料库中每个标注过的句子,以(词例,词性标签)对情势表现并统计词性标签和词例的词性标签
>>> for sentence in brown.tagged_sents():
... for (token, tag) in sentence:
... fd.inc(tag)
... cfd[token].inc(tag)
>>> fd.max() # 频率最高的词性标签是 ...
’NN’
>>> wordbins = [] # Initialize a list to hold (numtags,word) tuple
# 添加每个(词例的唯一词性标签,词例)元组到列表中
>>> for token in cfd.conditions():
... wordbins.append((cfd[token].B(), token))
...
# 按唯一词性标签数从高到低对元组举行排序
>>> wordbins.sort(reverse=True)
>>> print wordbins[0 # 标签最多的词例是 ...
(12, ’that’)
>>> male = [’he’,’his’,’him’,’himself’ # 男性代词
>>> female = [’she’,’hers’,’her’,’herself’ # 女性代词
>>> n_male, n_female = 0, 0 # 初始化计数器
# 男性代词总计:
>>> for m in male:
... n_male += cfd[m].N()
...
# 女性代词总计:
>>> for f in female:
... n_female += cfd[f].N()
...
>>> print float(n_male)/n_female # 盘算比率
3.257
>>> n_ambiguous = 0
>>> for (ntags, token) in wordbins:
... if ntags > 1:
... n_ambiguous += 1
...
>>> n_ambiguous # 词性标签归属多于一个的词例数
8729
解答:在布朗语料库中最高频的词性标签理所固然是名词(NN)。含有最多词性标签的是词是 that。语料库中的男性代词利用率差不多是女性代词的三倍。末了,语料库中有 8700 多的词仍有歧义——这个数字可以看出词性标注使命的困难水平。
3.6 使命 4: 单词遐想(Word Association)
自由单词遐想是神经语言学(Psycholinguistics)常见的使命,尤其是在词汇检索(lexical retrieval)的语境下——对于人类受试者(human subjects)而言,在单词遐想上,更倾向于选择有高度遐想性词,而非完全无关的词。这阐明单词遐想的处置惩罚是相称直接的——受试者在听到一个特别的词时要立刻从内心泛起另一个词。
使命:使用大规模词性标注过的语料库来实现自由单词遐想。忽略功能词(function words),假设遐想词都是名词。
对于这个使命而言,必要用到“词共现”(word co-occurrences)这一概念,比方:统计相互间最靠近的单词出现次数,然后藉此估算出遐想度。对于句子中的每个词例,我们将其观察规定范围内接下来全部的词而且使用条件频率分布统计它们在该语境的出现率。清单 4 演示了我们怎么用 Python 和 NLTK 对规定在 5 个单词的范围内的词性标注过的布朗语料库举行处置惩罚。
Listing 4: 使用 NLTK 实现单词遐想
>>> from nltk.corpus import brown, stopwords
>>> from nltk import ConditionalFreqDist
>>> cfd = ConditionalFreqDist()
# 得到英文停用词表
>>> stopwords_list = stopwords.words(’english’)
# 界说一个函数,假如属于名词类则返回true
>>> def is_noun(tag):
... return tag.lower() in [’nn’,’nns’,’nn$’,’nn-tl’,’nn+bez’,
’nn+hvz’, ’nns$’,’np’,’np$’,’np+bez’,’nps’,
’nps$’,’nr’,’np-tl’,’nrs’,’nr$’
...
# 统计前 5 个单词的出现次数
>>> for sentence in brown.tagged_sents():
... for (index, tagtuple) in enumerate(sentence):
... (token, tag) = tagtuple
... token = token.lower()
... if token not in stopwords_list and is_noun(tag):
... window = sentence[index+1:index+5
... for (window_token, window_tag) in window:
... window_token = window_token.lower()
... if window_token not in stopwords_list and
is_noun(window_tag):
... cfd[token].inc(window_token)
# 好了。我们完成了!让我们开始举行遐想!
>>> print cfd[’left’].max()
right
>>> print cfd[’life’].max()
death
>>> print cfd[’man’].max()
woman
>>> print cfd[’woman’].max()
world
>>> print cfd[’boy’].max()
girl
>>> print cfd[’girl’].max()
trouble
>>> print cfd[’male’].max()
female
>>> print cfd[’ball’].max()
player
>>> print cfd[’doctor’].max()
bills
>>> print cfd[’road’].max()
block
解答:我们构建的“单词遐想器(word associator)”结果好像出乎料想得好,尤其是在这么小的工作量下(究竟上,在大众生理学的的语境下,我们的遐想器好像具备人类的特性,只管是悲观和灰心的)。我们的使命效果明白指出了通用语料库语言学的有用性。作为进一步的训练,遐想器借助句法分析过的语料库和基于信息理论的遐想测试可以或许很轻易举行成熟的拓展。
4 讨论
固然本文利用了 Python 和 NLTK 作为底子 NLP 的先容,但请留意,在 NLTK 之外另有别的的 NLP 框架活泼于学术界和工业界。此中比力盛行的是 GATE(General Architecture for Text Engineering),由 University of Sheffield 的 NLP 研究小组开辟。利用 Java 编写的视窗情况,底子架构形貌了语言处置惩罚部件相互间的接洽。GATE 提供免费下载,重要应用于文本发掘(text mining)和信息抽取(information extraction)。
每种编程语言和框架都各有优劣。就本文而言,我们选择 Python 是由于与其他语言相比,它的上风在于:(a)可读性高(b)面相对象的范例(object-oriented paradigm)易于入门(c)易于扩展(d)强盛的解码支持(e)强盛的尺度库。而且特殊妥当,高效,这些都已经在复杂和大规模的 NLP 项目中得到了验证,比方最新的呆板翻译解码器。
5 结论
天然语言处置惩罚黑白常热门的研究范畴因此每年吸引了非常多研究生。它聚集了多个学科诸如语言学,生理学,盘算科学和数学的上风来研究人类语言。别的选择 NLP 作为研究生生活更紧张的缘故原由是大量故意思的困难都没有固定的办理办法。举个例子,呆板翻译初始题目(original problem)的存在推动了该范畴的发展,纵然颠末二十年诱人而又活泼的研究以后,这个困难仍旧尚待办理。另有别的几个前沿的 NLP 题目现在已经有大量的研究工作,此中一些枚举如下:
[*]基于句法的呆板翻译:从已往的数十年到如今,绝大部门的呆板翻译都聚焦在利用统计方法通过大量语料库来学习词和短语的翻译。然而,越来越多的研究者开始在研究中参加句法。
[*]多文本择要:现在大量工作都是使用盘算机从相近的文档聚集中主动天生高度相干的择要。这个使命被视为比单文本的择要困难,由于多文本中冗余信息更多。
[*]盘算句法分析:固然利用概率模子主动天生给定文本的句法布局由来已久,但进步空间还很大。最大的挑衅是正确的分析,当英语拿来和中文、阿拉伯语比力的时间,语言特性差别很大。
Python 和 NLTK 使每个编程职员不必要耗费大量时间在获取资源上,直接可以打仗 NLP 使命。文本意在给任何对学习 NLP 感爱好的人提供办理这些简朴的使命例子和参考。
6 作者简介
Nitin Madnani 是 Educational Testing Service(ETS)研究科学家。他此前是 University of Maryland, College Park 盘算机科学系的博士生和 Institute for Advanced Computer Studies 助理研究员。他的研究方向是统计天然语言处置惩罚,尤其是呆板翻译和文本择要。岂论使命巨细,他始终坚信:Python大法好。
参考文献
: Dan Bikel. 2004. On the Parameter Space of Generative Lexicalized Statistical Parsing Models. Ph.D. Thesis. http://www.cis.upenn.edu/~dbikel/papers/thesis.pdf
: David Chiang. 2005. A hierarchical phrase-based model for statistical machine translation. Proceedings of ACL.
: Kenneth W. Church and Patrick Hanks. 1990. Word association norms, mutual information, and lexicography. Computational Linguistics. 16(1).
H. Cunningham, D. Maynard, K. Bontcheva. and V. Tablan. 2002. GATE: A Framework and Graphical Development Environment for Robust NLP Tools and Applications. Proceedings of the 40th Anniversary Meeting of the Association for Computational Linguistics. 15
: Michael Hart and Gregory Newby. Project Gutenberg. Proceedings of the 40th Anniversary Meeting of the Association for Computational Linguistics. http://www.gutenberg.org/wiki/Main_Page
: H. Kucera and W. N. Francis. 1967. Computational Analysis of PresentDay American English. Brown University Press, Providence, RI.
: Roger Levy and Christoper D. Manning. 2003. Is it harder to parse Chinese, or the Chinese Treebank ? Proceedings of ACL.
: Dragomir R. Radev and Kathy McKeown. 1999. Generating natural language summaries from multiple on-line sources. Computational Linguistics. 24:469-500.
: Adwait Ratnaparkhi 1996. A Maximum Entropy Part-Of-Speech Tagger. Proceedings of Empirical Methods on Natural Language Processing.
: Dekai Wu and David Chiang. 2007. Syntax and Structure in Statistical Translation. Workshop at HLT-NAACL.
: The Official Python Tutorial. http://docs.python.org/tut/tut.html
: Natural Language Toolkit. http://nltk.sourceforge.net
: NLTK Tutorial. http://nltk.sourceforge.net/index.php/Book
绝对干货,楼主给力,支持了!!! 楼主加油,我们都看好你哦。 真是被感动的痛哭流涕…… 论坛有你更精彩! 看到这帖子真是高兴! 淡定,淡定,淡定…… 好东西一定要看看! 无私奉献,好工控人,32个赞送给你!! 看完楼主的帖子,我的心情竟是久久不能平息,受教了