Python自然语言处理—算法基础

本章主要介绍文本分析的算法设计过程中会用到的一些技巧,我只把书中对我来说有意思的例子拿出来了。

一  递归

递归就是循环的一种,为了实现某种目的反复调用自身。下面这个例子的有意思的地方不仅限于迭代,还用了yield,可以参考廖雪峰老师关于Yield的解释https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/。为了让大家理解yield,我添加了Print函数。

#  迭代
def permutations(seq):

    if len(seq) <= 1:
        print(seq)
        yield seq  # 如果序列长度为1,无需操作直接返回自身

    else:
        for perm in permutations(seq[1:]):  # 如果序列不为1,调用函数生成[1:]的所有排序方法
            for i in range(len(perm)+1):    # 将第一位数循环放到后几位数的中间,生成多种排序组合
                print(perm[:i] + seq[0:1] + perm[i:])
                yield perm[:i] + seq[0:1] + perm[i:]

list(permutations(['a','b', 'c']))

结果如下

['c']
['b', 'c']
['a', 'b', 'c']
['b', 'a', 'c']
['b', 'c', 'a']
['c', 'b']
['a', 'c', 'b']
['c', 'a', 'b']
['c', 'b', 'a']

我们来分析一下整个程序如何运行的

第一步 开始permutations(['a','b', 'c']),因为seq长度为3所以 程序跑到了 for perm in permutations(seq[1:])这里,开始第一次循环

第二步 开始permutations(['b', 'c']),因为seq长度为2所以 程序跑到了 程序跑到了 for perm in permutations(seq[1:])这里,开始第一次循环

第三步 开始开始permutations(['c']),因为seq长度1,所以yield seq,同时打印了['c']

第四步 这里yield出来的['c'],其实是第二步程序中需要的结果,所以返回第二步程序中,开始for perm in [['c']]

第五步 重点来了yield的效果开始体现,for i in range(2) 其实会有2个序列生成[['b', 'c'],['c', 'b']],但是yield是惰性的。程序只会跑出一个['b', 'c'],同时打印出['b', 'c']

第六步 开始第一步的程序,因为 perm现在等于['b', 'c']了,所以开始把a依次放入 bc前面,中间和后面。结果依次yield['a', 'b', 'c']['b', 'a', 'c']['b', 'c', 'a'],这里yield也是惰性的,但是list(permutations(['a','b', 'c']))这个了,所以结果都出来了

第七步 开始for perm in permutations(seq[1:])这里,开始第二次循环,又调用了permutations(['b', 'c']),这时候惰性的yield会从上一次结束的地方继续运行,吐出第二结果['c', 'b']

第八步 把a 依次放入['c', 'b'] 前中后,吐出结果

如果把程序中的yield换成return,是会报错的。

二  建立辅助的数据结构

有时候原始数据并不方便我们实现某一种功能,这时候不妨在原始数据基础上建立一个辅助的数据结构。比如下面的例子,如果在大量文章中找出某些词的上下文呢?不妨就先建立索引,保存着每个词出现过在哪些文章中。原书中是通过open函数直接开打zip文件夹中的大量电影评论数据,但是我python3不行我又懒得解决,所以我直接把zip中的文件都解压出来了,这样直接open就没问题了。

# 建立辅助数据结构
def raw(file):  #打开文件去除符号

    contents = open(path + r'\\' + file, 'r').read()

    contents = re.sub(r'<.*?>', ' ', contents)

    contents = re.sub('\s+', ' ', contents)

    return contents

 

def snippet(doc, term): # 打印结果

    text = ' '*30 + raw(doc) + ' '*30  # 防止文本过短打印报错

    pos = text.index(term)  # 获取term的第一个index

    return text[pos-30:pos+30]  # 打印上下文

 

print ("Building Index...")

import os
path = r'C:\Users\BF\AppData\Roaming\nltk_data\corpora\movie_reviews\neg'  
files = os.listdir(path)  # 获取该文件下的文件名
# files = nltk.corpus.movie_reviews.abspaths()


idx = nltk.Index((w, f) for f in files for w in raw(f).split())  # 建立辅助数据结构,结果就是字典每个词属于哪些文章

 
#  这里是根据你输入的词实时打印结果
query = ''

while query != "quit":  # 输入quit则退出当前程序

    query = input("query> ")  # 捕捉你当前获取的词

    if query in idx:  #首先判断这个词是不是当前文本中包含的

        for doc in idx[query]:  # 找出这个词所在的文章

            print (snippet(doc, query))  # 打印这个词上下文

    else:

        print ("Not found")

循行程序后就可以在开始输入你想查询的词了。

三  动态规划

用于解决多个重叠子问题的问题,解决的核心思想就是不能重复的计算每个子问题,而是应该将子问题的结果存储在一个查找表里。有两种音节SL,S占1个长度,L占2个长度,问有多少组合方式能组成20个长度。

# 硬递归,不做任何过程数据的存储,最慢
def virahanka1(n):

    if n == 0:

        return [""]

    elif n == 1:

        return ["S"]

    else:

        s = ["S" + prosody for prosody in virahanka1(n-1)]

        l = ["L" + prosody for prosody in virahanka1(n-2)]

        return s + l

 
# 将过程数据apend到一个list中
def virahanka2(n):

    lookup = [[""], ["S"]]

    for i in range(n-1):

        s = ["S" + prosody for prosody in lookup[i+1]]

        l = ["L" + prosody for prosody in lookup[i]]

        lookup.append(s + l)

    return lookup[n]

 
# 将过程数据存储到一个字典中
def virahanka3(n, lookup={0:[""], 1:["S"]}):

    if n not in lookup:

        s = ["S" + prosody for prosody in virahanka3(n-1)]

        l = ["L" + prosody for prosody in virahanka3(n-2)]

        lookup[n] = s + l

    return lookup[n]

 

from nltk import memoize
# 使用装饰器将函数的过程数据保存下来
@memoize  # 装饰器用于存储迭代过程中产生的值

def virahanka4(n):

    if n == 0:

        return [""]

    elif n == 1:

        return ["S"]

    else:

        s = ["S" + prosody for prosody in virahanka4(n-1)]

        l = ["L" + prosody for prosody in virahanka4(n-2)]

        return s + l


 

virahanka1(10)


virahanka2(10)


virahanka3(10)


virahanka4(20)

代码中涉及到一个装饰器的概念,本章调用的装饰器是存储中间结果,还有各式各样的装饰器。装饰器可以理解成

这样memoize (virahanka4(20)),简单把他想成一个函数或者一个类,具体如何写一个装饰器可以参考https://blog.csdn.net/xiangxianghehe/article/details/77170585

猜你喜欢

转载自blog.csdn.net/m0_38126215/article/details/84170426