前言
nltk是一个很有用的关于自然语言处理的python模块。
安装和使用
nltk作为python模块安装并没有什么好说的,不过你还需要安装nltk_data
,通过运行:
import nltk
nltk.download()
即可,当然你也可以自己手工配置。其搜索路径是如下配置的:
if sys.platform.startswith("win"):
# Common locations on Windows:
path += [
os.path.join(sys.prefix, str("nltk_data")),
os.path.join(sys.prefix, str("share"), str("nltk_data")),
os.path.join(sys.prefix, str("lib"), str("nltk_data")),
os.path.join(os.environ.get(str("APPDATA"), str("C:\\")), str("nltk_data")),
str(r"C:\nltk_data"),
str(r"D:\nltk_data"),
str(r"E:\nltk_data"),
]
else:
# Common locations on UNIX & OS X:
path += [
os.path.join(sys.prefix, str("nltk_data")),
os.path.join(sys.prefix, str("share"), str("nltk_data")),
os.path.join(sys.prefix, str("lib"), str("nltk_data")),
str("/usr/share/nltk_data"),
str("/usr/local/share/nltk_data"),
str("/usr/lib/nltk_data"),
str("/usr/local/lib/nltk_data"),
]
具体里面的内容可以在 nltk_data 这个github项目中找到。具体内容在packages下面,其中corpora的内容是不需要zip解压的,其他的一般需要先解压再放好位置。
有用的工具集
FreqDist
很有用的一个类,其继承自Counter类,用于记数的一个字典。
输入的是就是你分词好的词汇列表:[a, b, c, a ,a ,b .....]
t = FreqDist([a,b,c,a....])
其内部会自动处理然后得到一个词的词频统计,比如下面是看频率最高的25个:
t.most_common(25)
[('天下', 56), ('不', 51), ('圣人', 32), ('道', 27), ('谓', 24), ('曰', 21), ('万物', 20), ('吾', 20), ('无', 18), ('欲', 14), ('无为', 13), ('亦', 10), ('章', 10), ('人', 9), ('天地', 8), ('不争', 8), ('下', 8), ('道者', 8), ('大', 8), ('夫', 8), ('无以', 8), ('贵', 7), ('不知', 7), ('知', 7), ('善人', 7)]
bigrams
自然语言处理研究中的n元语法模型中的二元语法:
>>> list(bigrams([1,2,3,4,5]))
[(1, 2), (2, 3), (3, 4), (4, 5)]
ConditionalFreqDist
其是一个字典套字典结构,第一个字典key是一些条件,具体再引用该条件cfd[condition]
返回的是一个FreqDist对象。更具体来说其用来存储某一条件下的FreqDist分布。
cfd = ConditionalFreqDist([['1','a'],['1','b'],['0','a'],['1','a']])
cfd['1']
FreqDist({'a': 2, 'b': 1})
cfd['0']
FreqDist({'a': 1})
str2tuple
一般POS(part of speech)标注或者其他标注都可以采用 word/tag
这样的格式,然后可以利用 str2tuple
函数来拆分它们。
tagged_token = str2tuple('fly/NN')
tagged_token
('fly', 'NN')
def str2tuple(s, sep="/"):
pass
第二个参数sep默认是 /
,你也可以指定其他的分隔符。
Index类
Index类是一个默认为列表的defaultdict类,可以用来构建多值字典。
index = nltk.Index([('a',1),('a',2),('b',3)])
index
Index(<class 'list'>, {'a': [1, 2], 'b': [3]})
语料库管理
nltk的语料库管理并不是重点,不过其提供的一些语料库接口是值得学习的,比如说:
from nltk.book import gutenberg
这个gutenberg具体利用的是 PlaintextCorpusReader
这个类。
可以利用这个类来加载你自己的txt文本,不过中文的话需要自己定义Tokenizer类。一个简单的类如下所示:
from nltk.tokenize.api import TokenizerI
class JiebaTokenizer(TokenizerI):
def tokenize(self, s):
return jieba.lcut(s)
你需要实现tokenize这个方法,切分字符串然后返回的是列表。
中文分句一个简单实现就是利用正则表达式来切分:
from nltk import RegexpTokenizer
from my_python_module.basic.list import combine_odd_even
class ChineseSentenceTokenizer(RegexpTokenizer):
def __init__(self):
RegexpTokenizer.__init__(self, r"(。|?|!)", gaps=True)
def tokenize(self, text):
res = super(ChineseSentenceTokenizer, self).tokenize(text)
return combine_odd_even(res)
上面的combine_odd_even是我自己定义的一个方法,将列表奇偶元素相加,这里不是讨论的重点,略过了。
然后我们定义 load_corpus
来加载txt语料:
def load_corpus(root, word_tokenizer=JiebaTokenizer(),
sent_tokenizer=ChineseSentenceTokenizer()):
return PlaintextCorpusReader(root, r"(?!\.).*\.txt",
word_tokenizer=word_tokenizer,
sent_tokenizer=sent_tokenizer,
encoding='utf8')
zh_gutenberg = load_corpus('D:/nlp_data/corpora/zh_gutenberg')
这个root指向你的语料库的文件夹即可,下面放着一些txt文本。
然后如下利用nltk的Text类来加载语料进行分析。
laozi = Text(zh_gutenberg.words("laozi.txt"))
lunyu = Text(zh_gutenberg.words("lunyu.txt"))
后面就对应上官方教材的叙述了。
语料库接口
如下调用语料库的原始文本,利用你之前设置好的word_tokenizer分词后的词语列表,利用你之前设置好的sent_tokenizer分句之后的句子列表。
zh_gutenberg.raw('xiyouji_s.txt')
zh_gutenberg.words('xiyouji_s.txt')
zh_gutenberg.sents('xiyouji_s.txt')
Text接口
concordance
索引词
text1.concordance("monstrous")
similar
根据上下词环境来找相似词
text1.similar("monstrous")
common_contexts
两个词之间的共同上下词环境
text2.common_contexts(["monstrous", "very"])
generater
根据上下文来随机生成一些文本。
laozi.generate()
collocations
原nltk的Text类里面的这个方法只是专门针对英文的,如果重新写一个ChineseText类继承并重载这个方法:
class ChineseText(Text):
def collocation_list(self, num=20, window_size=2):
"""
Return collocations derived from the text, ignoring stopwords.
>>> from nltk.book import text4
>>> text4.collocation_list()[:2]
[('United', 'States'), ('fellow', 'citizens')]
:param num: The maximum number of collocations to return.
:type num: int
:param window_size: The number of tokens spanned by a collocation (default=2)
:type window_size: int
:rtype: list(tuple(str, str))
"""
if not (
"_collocations" in self.__dict__
and self._num == num
and self._window_size == window_size
):
self._num = num
self._window_size = window_size
from .chinese_stop_words import STOP_WORDS
finder = BigramCollocationFinder.from_words(self.tokens,
window_size)
finder.apply_freq_filter(2)
finder.apply_word_filter(lambda w: w in STOP_WORDS)
# 移除空白字符
from my_python_module.nlp.utils import is_empty_string
finder.apply_word_filter(lambda w: is_empty_string(w))
bigram_measures = BigramAssocMeasures()
self._collocations = list(
finder.nbest(bigram_measures.likelihood_ratio, num))
return self._collocations
具体修改是原过滤器过滤了词语的长度,这个不适合中文。然后停用词换成了中文停用词词库。然后增加了一个空白字符去除动作。
具体里面评估二元语法组得分用的是 bigram_measures.likelihood_ratio
这个算法。有兴趣可以研究一下,这里暂时略过了。