Python自然语言处理 | 加工原料文本

  • 本章的目的是要回答下列问题:
  1. 我们怎样才能编写程序访问本地和网络上的文件,从而获得无限的语言材料?
  2. 我们如何把文档分割成单独的词和标点符号,这样我们就可以开始像前面章节中在文本语料上做的那样的分析?
  3. 我们怎样编程程序产生格式化的输出,并把结果保存在一个文件中?

1 从网络和硬盘访问文本

1.1 电子书

  1. NLTK语料库集合中有古腾堡项目的一小部分样例文本。然而,你可能对分析古腾堡项目的其它文本感兴趣。
  2. 你可以在http:/www.gutenberg.org/catalog/上浏览25,000本免费在线书籍的目录,获得ASCII码文本文件的URL。
  3. 虽然90%的古腾堡项目的文本是英语的,它还包括超过50种语言的材料,包括加泰罗尼亚语、中文、荷兰语、芬兰语、法语、德语、意大利语、葡萄牙语和西班牙语(每种语言都有超过100个文本)。
"""
编号2554的文本是《罪与罚》的英文翻译,我们可以如下方式访问它。
"""
import nltk
from urllib.request import urlopen

url = "http://www.gutenberg.org/files/2554/2554.txt"
raw=urlopen(url).read()
 
print(type(raw)) #文本的类型: str
print(len(raw))  #文本长度,176831
print(raw[:75])  #文本前75个字符,不要直接打印出raw,太长了
 
#使用代理访问:
#proxies={'http':'http://www.someproxy.com:3128'}
#raw=urlopen(url,proxies=proxies).read()
"""
分词:将字符串分解为词和标点符号;经过分词,产生一个词汇和标点符号的链表 str --> list 
"""

# 将其str转为list
tokens=nltk.word_tokenize(raw)  
print(type(tokens))
print(len(tokens))  # 225809
print(tokens[:10])

#从链表创建一个NLTK文本,对其进行操作
text=nltk.Text(tokens)
print(type(text))
print(text[:10])     #text似乎同tokens没什么区别
print (text.collocations())  # text.collocations() : 统计文本中频繁的双连词
"""
古腾堡项目的每个文本:包含一个首部,涵盖了文本的名称、作者、扫描和校对文本的人的名字、许可证等信息。
从原始文本中挑出内容之前,我们需要手工检查文件以发现标记内容开始和结尾的独特的字符串。
"""
print(raw.find("PART I"))  # 寻找“PART I”的索引
print(raw.rfind("End of Project Gutenberg's Crime"))   #逆向查找,从最后开始找索引

#重新复制,将从"PART I"到"End of Project Gutenberg's Crime"部分截下来,赋给raw
raw=raw[raw.find("PART I"):raw.rfind("End of Project Gutenberg's Crime")]  # 截取中间正文部分

1.2处理的HTML

  • HTML全部内容包括:meta元标签、图像标签、map标签、JavaScript、表单和表格。
  • 提取文本:clean_html()将HTML字符串作为参数,返回原始文本,然后对原始文本进行分词,获得熟悉的文本结构
# 书籍版本是python2 所以运行报错了,修正为
import nltk  
from urllib.request import urlopen  
from bs4 import BeautifulSoup

url = "http://news.bbc.co.uk/2/hi/health/2284783.stm"  
html = urlopen(url).read()  
print(html[:60]) 

soup = BeautifulSoup(html)
raw = soup.get_text()
#print(nltk.word_tokenize(raw))
tokens = nltk.word_tokenize(raw)  
tokens=tokens[96:399]  

text=nltk.Text(tokens)
print(text.concordance('ok'))

1.3 处理搜索引擎的结果

  • 优点
  1. 主要优势规模,具有庞大的文件集;
  2. 其次优势是非常容易使用。
  • 缺点
  1. 允许搜索的范围受到严格的限制;
  2. 搜索引擎给出的结果不一致;
  3. 搜索引擎返回的结果的标记可能会有不可预期的改变。

1.4 处理RSS订阅

博客圈是文本的重要来源,无论是正式的还是非正式的。在一个叫做Universal Feed Parser的第三方Python库(可从http:/lfeedparser.org/免费下载)的帮助下,我们可以访问个博客的内容,如下所示:

import feedparser

llog = feedparser.parse("http://languagelog.ldc.upenn.edu/nll/?feed=atom")
llog['feed']['title']
len(llog.entries)
post = llog.entries[2]
post.title
print(post.content[0])
content = post.content[0].value
content[:70]

nltk.word_tokenize(nltk.html_clean(content))
nltk.word_tokenize(nltk.clean_html(llog.entries[2].content[ 0].value)) # 结果字符串有一个u前缀表示它们是 Unicode字符串

1.5 读取本地文件

python数据分析 | csv,json,xls文件读写

1.6 从 PDF、MS Word 及其他二进制格式中提取文本

打开PDF和MSWord,用第三方函数库如pypdf和pywin32

  • pdf提取文本参考学习
  1. 深入学习python解析并读取PDF文件内容的方法
  2. 如何用Python批量提取PDF文本内容?
  • Ms Word提取文本参考学习
  1. Python学习笔记(28)-Python读取word文本

1.7 捕获用户的输入

使用input函数+变量存储 获得用户输入

1.8 NLP的流程

在这里插入图片描述

2 字符串:最底层的文本处理

将详细探讨字符串,并展示字符串与词汇、文本和文件之间的联系

2.1 字符串的基本操作

  1. 字符串中包含单引号,需要用""转义;
  2. 可用单引号,双引号,三重引号来指定字符串,其中双引号可以加在有单引号的句子中,代表句子结束,三重引号可以换行打印出来;
  3. 字符串跨好几行,a:使用反斜杠"",解释器就知道第一行的表达式不完整;b:使用括号,将两个字符串括起来,中间换行即可,不用加逗号;
  4. 对字符串操作,“+”加法:连接字符串;“*”乘法:多倍连接字符串;不能使用减法和除法

2.2 输出字符串

变量 = ‘字符串’
print(变量)

2.3 访问单个字符

  1. 从0开始,长度为1的字符串,用索引符号[]调用,
  2. 超出索引范围,出错
  3. 字符串的负数索引,-1为最后一个字符的索引,-2,-3,…对应着过去,
  4. 计数单个字符。将所有字符小写,忽略掉大小写,并过滤掉非字母字符
import nltk
from nltk.corpus import gutenberg

raw=gutenberg.raw('melville-moby_dick.txt')
fdist=nltk.FreqDist(ch.lower() for ch in raw if ch.isalpha())
print(fdist.keys()) # 出现频率最高排在最先的顺序显示出英文字母
print(fdist.values()) # fdist如同key-value一般,调用keys和values方法,能够显示对应的字符情况
fdist.plot() # 可视化输出

在这里插入图片描述

2.4 访问子字符串

  1. 使用切片,开始于第一个索引,结束于最后一个索引的前一个。注意,最后索引的前一个
  2. 负数索引切片,-1为最后一个,-2,-3…推算过去
  3. 省略:第一个值,即从字符串开头开始;第二个值,切到字符结尾结束; 字符[:]
  4. in操作符:测试一个字符串是否包含一个特定的子字符串
  5. find()函数操作:子字符串在字符串内的位置;从开头到找到的第一个位置.(若是第二个怎么算?)
  6. rfind()函数,从末尾开始查找,同findd().只是开始位置相反而已。
    在这里插入图片描述
monty='Monty Python'
monty[6:10]
monty[-12:-7]
phrase = 'And now for something completely different'
if 'thing' in phrase:
    print('''find "thing"''')

2.5 更多的字符串操作

help(str)可以找到所有的有关函数在这里插入图片描述

2.6 链表(列表,list)与字符串(str)的区别

  1. 字符串和链表之间不能连接;
  2. 我们使用一个for 循环来处理读入文件(对应的文件内容对应一个字符串),所有我们可以挑选出的只是单个的字符——我们不能选择粒度链表中的元素可以很大也可以很小,它们可能是段落、句子、短语、单词、字符。链表的优势在于我们可以灵活的决定它包含的元素,相应的后续的处理也变得灵活
  3. 我们在一段NLP 代码中可能做的第一件事情就是将一个字符串分词放入一个字符,变成列表(nltk.word_tokenize(raw));当我们要将结果写入到一个文件或终端,我们通常会将它们格式化为一个字符串
  4. 字符串是不可改变的:一旦你创建了一个字符串,就不能改变它。链表是可变的,内容可以随时修改

3 使用 Unicode 进行文字处理

在本节中,我们将概述如何使用Unicode处理使用非ASCII字符集的文本。

3.1 什么是Unicode

  1. Unicode支持超过一百万种字符。每个字符分配一个编号,称为编码点。在Python中,编码点写作\uXXXX的形式,其中XXXX是四位十六进制形式数。
  2. 当Unicode字符串被存储在文件或在终端上显示,他们必须为编码为字节流。一些编码(如 ASCII和Latin-2)中每个编码点使用单字节,所以它们可以只支持Unicode 的一个小的子集就足够一种语言使用了。其它的编码(如UTF-8)使用多个字节,可以表示全部的Unicode字符。
  3. 文件中的文本都是有特定编码的,所以我们需要一些机制来将文本翻译成Unicode,即解码(decode)。相对的,要将Unicode写入一个文件或终端,我们首先需要将Unicode,转化为合适的编码,即编码(encode)。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eAPmimBu-1612941977208)(attachment:image.png)]

3.2 从文件中提取已编码文本

"""
nltk.data.find()函数:定位文件
"""
import nltk
path = nltk.data.find('corpora/unicode_samples/polish-lat2.txt')
"""
- codecs模块:提供了将编码数据读入为Unicode 字符串和将Unicode 字符串以编码形式写出的函数。
- codecs.open()函数:encoding 参数来指定被读取或写入的文件的编码。
- unicode_escape编码:Python的一个虚拟的编码;把所有非ASCII 字符转换成它们的\uXXXX 形式
"""
path = nltk.data.find('corpora/unicode_samples/polish-lat2.txt')
f= codecs.open(path,encoding='latin2')  # 告诉文件的编码形式为latin2

print(f)

#文件对象f 读出的文本将以Unicode 返回
for line in f.readlines():
    line=line.strip()
    print( line.encode('unicode_escape'))
"""
Unicode 字符串常量:在字符串常量前面加一个u,
ord()函数:查找一个字符的整数序列。如ord('a')
"""
a=u'\u0061'  #对其进行转义
print( a)
"""
print 语句:假设Unicode 字符的默认编码是ASCII 码。
repr()函数:转化的字符串,==输出utf-8转义序列(以\xXX的形式)==
"""
nacute = u'\u0144'
nacute_utf = nacute.encode('utf8')
print( nacute)
print (repr(nacute_utf))
"""
unicodedata模块:检查Unicode 字符的属性。
"""
import unicodedata

path = nltk.data.find('corpora/unicode_samples/polish-lat2.txt')
lines = codecs.open(path,encoding='latin2').readlines()  # 告诉文件的编码形式为latin2
line = lines[2]
print( line.encode('unicode_escape'))

for c in line:
    if ord(c) >127:
    print ('%r U+%04x %s'% (c.encode('utf8'),ord(c),unicodedata.name(c)))

3.3 在python中使用本地编码

在这里插入图片描述

4 使用正则表达式检测词组搭配

# 导入词库
import re
wordlist = [w for w in nltk.corpus.words.words('en') if w.islower()]
print(wordlist[:10])

4.1 使用基本的元字符

  1. 美元符号$:用来匹配单词的末尾;
  2. 乘方符号*^:用来匹配单词的开始;
  3. 问号符号“?”:表示前面的一个字符可选;
  4. 通配符“.”:匹配任何单个字符。

例子1:r’^e-?mail$'将匹配email 和e-mail

例子2:查找以ed结尾的词汇,r’ed$’

import re
 
print ([w for w in wordlist if re.search(r'ed$',w)])
print( [w for w in wordlist if re.search(r'^..j..t..$',w)])
print( sum(1 for w in text if re.search(r'^e-? mail$',w)))
#用IDLE运行有点慢,直接用命令窗口的话,更快。。。

4.2 范围与闭包

  1. 闭包:+、*
  • “+”:前面的项目的一个或多个实例
  • “*”:前面的项目的零个或多个实例
  • “^”:出现在方括号内的第一个字符位置
  • 查找非元音字母组成的词汇:«[aeiouAEIOU]+$»
  1. “.”:匹配一个句号。
  2. 大括号表达:如{3,5},表示前面的项目重复指定次数。
  3. 管道字符[]:从其左边的内容和右边的内容中选择一个。
  4. 圆括号:表示一个操作符的范围,它们可以与管道(或叫析取)符号一起使用,如:r’w(i|e|ai|oo)t’,匹配wit、wet、wait 和woot。
  5. 原始字符串:前缀"r";例如:原始字符串r’\band\b’包含两个“\b”符号会被re 库解释为匹配词的边界而不是解释为退格字符。


在这里插入图片描述

[w for w in wordlist if re.search(r'^[ghi][mno][jlk][def]$',w)] # 以g或者h或者i开头,以d或者e或者f结尾的,并且第二个字符是m,n,o中的一个,第三个字符是j,l,k中的一个
chat_words = sorted(set(w for w in nltk.corpus.nps_chat.words()))
print( [w for w in chat_words if re.search(r'^m+i+n+e+$', w)] )#1个或者多个m,i,n,e,并且以m开头,e结尾
print( [w for w in chat_words if re.search(r'^[ha]+$', w)] )#以ha开头,并且有1一个或者多个ha,

5 正则表达式的有益应用

5.1 提取字符块

通过re.findall() (“find all”即找到所有)方法找出所有(无重叠的)匹配指定正则表达式的。

# 找出一个词中的元音,再计数它们
word = 'supercalifragilisticexpialidocious'
len(re.findall(r'[aeiou]',word))
# 看看一些文本中的两个或两个以上的元音序列,并确定它们的相对频率
wsj = sorted(set(nltk.corpus.treebank.words()))
fd = nltk.FreqDist(vs 
                   for word in wsj
                   for vs in re.findall(r'[aeiou]{2,}',word))
fd.items()

#将字符串'2009-12-31'转换为一 个整数链表[2009, 12, 31]:
print([int(n) for n in re.findall(r'[0-9]+','2009-12-31')])

5.2 在字符块上做更多事情

"""
英文文本是高度冗余的,忽略掉词内部的元音仍然可以很容易的阅读,有些时候这很明显
例如:declaration变成 dclrtn,inalienable变成 inlnble,保留所有词首或词尾的元音序列。
正则表达式匹配词首元音序列,词尾元音序列和所有的辅音;其它的被忽略。
使用 re.findall()提取所有匹配的词中的字符,然后使用''.join()将它们连接在一 起
"""
regexp = r'^[AEIOUaeiou]+|[AEIOUaeiou]+$|[^AEIOUaeiou]'
def compress(word):
    pieces = re.findall(regexp, word)
    return "".join(pieces)
 
english_udhr = nltk.corpus.udhr.words("English-Latin1")
print(nltk.tokenwrap(compress(w) for w in english_udhr[:75]))

#将正则表达式与条件频率分布结合起来

rotokas_words = nltk.corpus.toolbox.words("rotokas.dic")
cvs = [cv for w in rotokas_words for cv in re.findall(r'[ptksvr][aeiou]', w)]
cfd = nltk.ConditionalFreqDist(cvs)
cfd.tabulate()

5.3 查找词干

  1. 使用网络搜索引擎时,我们通常不介意(甚至没有注意到)文档中的词汇与我们的搜 索条件的后缀形式是否相同
  2. 对于一些语言处理任务,我们想忽略词语结尾,只是处理词干
"""
抽出一个词的词干的方法
"""
def stem(word):
    for suffix in ['ing', 'ly', 'ed', 'ious', 'ive', 'es', 's', 'ment']:
        if word.endswith(suffix):
            return word[:-len(suffix)]
        
word = "interesting"
print(stem(word))
"""
运用正则表达式进行:
print(re.findall(r'^(.*)(ing|ly|ed|ious|ies|ive|es|s|ment)$', 'processes'))
匹配的结果式是processe,是贪婪模式
所以表达式的“.*”部分试图尽可能多的匹配输入的字符串。如果我 们使用“非贪婪”版本的“*”操作符,写成“*?”,我们就得到我们想要的
完整方法如下
"""
def stem(word):
    regexp = r'^(.*?)(ing|ly|ed|ious|ies|es|s|ment)?$'
    stem, suffix = re.findall(regexp, word)[0]
    return stem

raw = """DENNIS: Listen, strange women lying in ponds distributing swords
  is no basis for a system of government. Supreme executive power derives from
  a mandate from the masses, not from some farcical aquatic ceremony."""
tokens = nltk.word_tokenize(raw)
print([stem(t) for t in tokens][:5])

5.4 搜索已分词文本

  1. 尖括号用于标记标识符 的边界,尖括号之间的所有空白都被忽略
  2. 我们使用<.*>,它将匹配所有单个标识符,将它括在括号里,
# 只匹配词(例如:monied)而不匹配短语(例如:a monied man)

from nltk.corpus import gutenberg, nps_chat
moby = nltk.Text(gutenberg.words('melville-moby_dick.txt'))
print(moby.findall(r'<a>(<.*>)<man>'))
# 找出以词“br o”结尾的三个词组成的短语
chat = nltk.Text(nps_chat.words())
print(chat.findall(r'<.*><.*><bro>'))
# 找出以字母“l”开始的三个或更多词组成的 序列
print(chat.findall(r'<l.*>{3,}'))
"""
# \w 表示单词字符
#在大型文本语料库中搜索“x and other ys”形式 的表达式能让我们发现上位词
"""

from nltk.corpus import brown
hobbies_learned = nltk.Text(brown.words(categories=['hobbies', 'learned']))
print(hobbies_learned.findall(r'<\w*><and><other><\w*s>'))
"""#查找模式“as x as y”的实例以发现实体及其属性信息"""

from nltk.corpus import brown
hobbies_learned = nltk.Text(brown.words(categories=['hobbies', 'learned']))
print(hobbies_learned.findall(r'<as><\w*><as><\w*>'))

6 规范化文本

#首先,定义我们将在本节中使用的数据:
raw = """DENNIS: Listen, strange women lying in ponds distributing swords 
    is no basis for a system of government. Supreme executive power derives from 
    a mandate from the masses, not from some farcical aquatic ceremony."""

tokens = nltk.word_tokenize(raw)
print(tokens)

6.1 词干提取器

#Porter 词干提取器正确处理了词 lying(将它映射为 lie),而 Lancaster 词干提取 器并没有处理好。
porter = nltk.PorterStemmer()
lancaster = nltk.LancasterStemmer()
print([porter.stem(t) for t in tokens][:])
print([lancaster.stem(t) for t in tokens][:])
# 词干提取器过程没有明确定义,通常选择合适应用的词干提取器。
# 如果要索引文本或者使搜索支持不同词汇形式的话,Porter词干提取器是一个很好的选择。
# 介绍:nltk.Index 获得某个单词的索引
# output = nltk.Index((word , i) for (i,word) in enumerate(['a','b','a']))  # 输出Index(list, {'a': [0, 2], 'b': [1]})
# output['a']  # [0, 2]

class IndexedText(object):
     # 初始化提取器和文本
    def __init__(self, stemmer, text):
        self._text = text  
        self._stemmer = stemmer
        self._index = nltk.Index((self._stem(word), i)
                                 for (i, word) in enumerate(text))  # 把text的文本统一成大小写,然后获得文本每个词的分布索引,
 
    def concordance(self, word, width=40):
        key = self._stem(word)  # lie
        wc = int(width/4)                # words of context
        for i in self._index[key]:   # 遍历出lie分布的索引, 如[3,6]
            lcontext = ' '.join(self._text[i-wc:i])  # 获得lie前后10个单词
            rcontext = ' '.join(self._text[i:i+wc])
            ldisplay = '{:>{width}}'.format(lcontext[-width:], width=width)
            rdisplay = '{:{width}}'.format(rcontext[:width], width=width)
            print(ldisplay, rdisplay)
 
    def _stem(self, word):
        return self._stemmer.stem(word).lower()  # lie


porter = nltk.PorterStemmer()
grail = nltk.corpus.webtext.words('grail.txt')
text = IndexedText(porter,grail)
text.concordance('lie')

6.2 词形归并

WordNet词形归并器删除词缀产生的词都是在它的字典中的词。这个额外的检查过程使 词形归并器比刚才提到的词干提取器要慢。

#没有处理“lying”,但它将“women”转换为“woman”。
wnl = nltk.WordNetLemmatizer()
print([wnl.lemmatize(t) for t in tokens][:])
#如果你想编译一些文本的词汇,或者想要一个有效词条(或中心词)列表,WordNet词形归并器是一个不错的选择。

7 用正则表达式为文本分词

分词是将字符串切割成可识别的构成一块语言数据的语言单元。

7.1 分词的简单方法

在这里插入图片描述

raw = """'When I'M a Duchess,' she said to herself, (not in a very hopeful tone
      though), 'I won't have any pepper in my kitchen AT ALL. Soup does very
      well without--Maybe it's always pepper that makes people hot-tempered,'..."""
#使用 raw.split()在空格符处分割原始文本。
#使用正则表达式能做同样的事情,print(re.split(r' ', raw)匹配字符串中的所有空格符是不够的,
#因为这将导致分词结果包含“\n”换行符;我们需 要匹配任何数量的空格符、制表符或换行符
# 所以,用re.split(r'[ \t\n]+', raw
print(re.split(r'[ \t\n]+', raw))
print(re.findall(r'\w+|\S\w*', raw)) # 分离出I',M
print(re.findall(r"\w+(?:[-']\w+)*|'[-.(]+|\S\w*",raw))  # 分离出I'M

7.2 NLTK 的正则表达式分词器

函数nltk.regexp_tokenize()与re.findall()类似(我们一直在使用它进行分词)。然而,nltk.regexp_tokenize()分词效率更高,且不需要特殊处理括号。为了增强可读性,我们将正则表达式分几行写,每行添加一个注释。特别的“(?x)”“verbose标志”告诉Python去掉嵌入的空白字符和注释。

text = 'That U.S.A.poster-print costs $12.40...'

pattern = r"""(?x)   # set flag to allow verbose regexps
    ([A-Z].)+   # abbreviations,e.g.U.S.A.
    | \w+(-\w+)*   # words with optional internal hyphens
     \$?\d+(\.\d+)?%?   # currency and percentages,e.g. $12.40,82%
    |\.\.\. # ellipsis
    |[][.,,""?():-_`]# these are separate tokens
"""
nltk.regexp_tokenize(text,pattern)

8 分割

分词是一个更普遍的分割问题的一个实例。在本节中,我们将看到这个问题的另外两个实例,它们使用与到目前为止我们已经在本章看到的完全不同的技术。

8.1 断句

  1. 在词级水平处理文本通常假定能够将文本划分成单个句子。正如我们已经看到,一些语 料库已经提供在句子级别的访问。
#计算布朗语料库中每个句子的平均词数
print(len(nltk.corpus.brown.words()) / len(nltk.corpus.brown.sents()))
# 分句器
sent_tokenizer=nltk.data.load('tokenizers/punkt/english.pickle')
text = nltk.corpus.gutenberg.raw('chesterton-thursday.txt')
sents = sent_tokenizer.tokenize(text)
print(sents[171:181])  # 被分句了

# 断句是困难的,因为句号会被用来标记缩写而另一些句号同时标记缩写和句子结束,就像发生在缩写如“U.S.A.”上的那样。
# 断句的另一种方法见6.2节。

8.2 分词

在这里插入图片描述

"""
例3-2.从分词表示字符串seg1和seg2中重建文本分词。
seg1和seg2表示假设的一些儿童讲话的初始和最终分词。
函数segment()可以使用它们重现分词的文本。
"""

text = "doyouseethekittyseethedoggydoyoulikethekittylikethedoggy"
seg1 = "0000000000000001000000000010000000000000000100000000000"
seg2 = "0100100100100001001001000010100100010010000100010010000"
def segment(text, segs):
    words = []
    last = 0
    for i in range(len(segs)):
        if segs[i] == '1':
            words.append(text[last:i+1])
            last = i + 1
    words.append(text[last:])
    return words

print(segment(text, seg1))
print(segment(text, seg2))


"""
#现在分词的任务变成了一个搜索问题:找到将文本字符串正确分割成词汇的字位串
#我们可以定义一个目标函数,一个打分函数,我们将基于词典的大小和从词典中重构源文本所需的信息量尽力优化它的值。
#即模拟退火算法:https://baike.baidu.com/item/%E6%A8%A1%E6%8B%9F%E9%80%80%E7%81%AB%E7%AE%97%E6%B3%95/355508?fromtitle=%E9%80%80%E7%81%AB%E7%AE%97%E6%B3%95&fromid=6081525&fr=aladdin
#例3-3. 计算存储词典和重构源文本的成本。
"""

def evaluate(text, segs):
    words = segment(text, segs)
    text_size = len(words)
    lexicon_size = len(''.join((set(words))))
    return (text_size + lexicon_size)
text = "doyouseethekittyseethedoggydoyoulikethekittylikethedoggy"
seg1 = "0000000000000001000000000010000000000000000100000000000" 
seg2 = "0100100100100001001001000010100100010010000100010010000"
seg3 = "0000100100000011001000000110000100010000001100010000001"
print(segment(text, seg1))
print(segment(text, seg2))
print(segment(text, seg3))
print(evaluate(text, seg1))
print(evaluate(text, seg2))
print(evaluate(text, seg3))

9 格式化:从链表到字符串

9.1 从链表到字符串

#我们用于文本处理的最简单的一种结构化对象是词链表。
#当我们希望把这些输出到显示器或文件时,必须把这些词的链表转换成字符串。
#在 Python 做这些,我们使用的 join()方 法,并指定作为“胶水”使用的字符串:
silly = ['We', 'called', 'him', 'Tortoise', 'because', 'he', 'taught', 'us', '.']
print(' '.join(silly))
print(','.join(silly))
#join()方法只适用于一个字符串的链表— —我们一直把它叫做一个文本——在 Python 中享有某些特权的一个复杂的类型。

9.2 字符串与格式

#格式化输出通常包含变量和预先指定的字符串的一个组合
fdist = nltk.FreqDist(['dog', 'cat', 'dog', 'cat', 'dog', 'snake', 'dog', 'cat'])
for word in fdist:
    print(word,'->', fdist[word],';')
    
#除了不必要的空格符问题,输出包含变量和常量交替出现的表达式是难以阅读和维护的。
#一个更好的解决办法是使用字符串格式化表达式。
for word in fdist:
    print('%s->%d;'%(word, fdist[word]))
#特殊符号%s 和%d 是字符串和整数(十进制数)的占位符。
#我们可以将这些嵌入在一个字符串中,然后使用%操作符把它们组合起来。
 
 
#我们还可以间接的提供占位符的值。下面是使用 for 循环的一个例子
template = 'Lee wants a %s right now'
menu = ['sandwich', 'spam fritter', 'pancake']
for snack in menu:
    print(template % snack)
    
# 符号%s和%d被称为转换说明符。它们以%字符开始,以一个转换字符如s(表示字符串)或d(十进制整数)结束。
# 其中包含转换说明符的字符串被称为格式字符串。我们组合一个格式字符串和%操作符以及一个值的元组,来创建一个完整的字符串格式化表达式。

9.3 排列

#指定宽度,如%6s,产生一个宽度为 6 的字符串
print('%6s'%'dog')
 
#包括一个减号使它左对齐
print('%-6s'%'dog')
 
#事先不知道要显示的值应该有多宽时, 可以在格式化字符串中用*替换宽度值,然后指定一个变量。
width = 6
print('%-*s'%(width, 'dog'))
 
#其他控制字符用于十进制整数和浮点数。
#因为百分号%在格式化字符串中有特殊解释,我们要在它前面加另一个%才能输出它。
count, total = 3205, 9375
print("accuracy for %d words:%2.4f%%"%(total, 100 * count / total))

9.4 将结果写入文件

#打开可写文件 output.txt ,将程序的输出保存到文件。
output_file = open('output.txt', 'w')
words = set(nltk.corpus.genesis.words('english-kjv.txt'))
for word in sorted(words):
    output_file.write(word + "\n")
    
print(len(words))
print(str(len(words)))
 
output_file.write(str(len(words)) + "\n")
output_file.close()
#你应该避免文件名包含空格字符,例如:output file.txt
#避免使用除了大小 写区别外其他都相同的文件名,例如:Output.txt 与 output.TXT。

9.5 文本换行

#可以在 Python 的 textwrap 模块的帮助下采取换行
saying = ['After', 'all', 'is', 'said', 'and', 'done', ',', 'more', 'is', 'said', 'than', 'done', '.']
from textwrap import fill

format = '%s(%d),'
pierces = [format % (word, len(word)) for word in saying]
output = ' '.join(pierces)  # 不换行
print(output)
wrapped = fill(output)
print(wrapped)   # 已换行

10 小结

  1. 在本书中,我们将文本作为一个词链表。“原始文本”是一个潜在的长字符串,其中包 含文字和用于设置格式的空白字符,也是我们通常存储和可视化文本的原料。
  2. 在 Python 中指定一个字符串使用单引号或双引号:‘Monty Python’,“Monty Python”。
  3. 字符串中的字符是使用索引来访问的,索引从零计数:‘Monty Python’[0]的值是 M。 求字符串的长度可以使用 len()。
  4. 子字符串使用切片符号来访问: ‘Monty Python’[1:5]的值是 onty。如果省略起始 索引,子字符串从字符串的开始处开始;如果省略结尾索引,切片会一直到字符串的结 尾处结束。
  5. 字符串可以被分割成链表:‘Monty Python’.split()得到[‘Monty’, ‘Python’]。链表 可以连接成字符串:’/’.join([‘Monty’, ‘Python’])得到’Monty/Python’。
  6. 我们可以使用 text = open(f).read()从一个文件 f 读取文本。可以使用 text = urlpen(u).read()从一个 URL u 读取文本。我们可以使用 for line in open(f)遍历一个文本文件的每一行。
  7. 在网上找到的文本可能包含不需要的内容(如页眉、页脚和标记),在我们做任何语言处理之前需要去除它们。
  8. 分词是将文本分割成基本单位或标记,例如词和标点符号等。基于空格符的分词对于许多应用程序都是不够的,因为它会捆绑标点符号和词。NLTK 提供了一个现成的分词器nltk.word_tokenize()。
  9. 词形归并是一个过程,将一个词的各种形式(如:appeared,appears)映射到这个词标准的或引用的形式,也称为词位或词元(如:appear)。
  10. 正则表达式是用来指定模式的一种强大而灵活的方法。只要导入了 re 模块,我们就可以使用 re.findall()来找到一个字符串中匹配一个模式的所有子字符串。
  11. 如果一个正则表达式字符串包含一个反斜杠,你应该使用原始字符串与一个 r 前缀:r’regexp’,告诉 Python 不要预处理这个字符串。
  12. 当某些字符前使用了反斜杠时,例如:\n,处理时会有特殊的含义(换行符);然而,当反斜杠用于正则表达式通配符和操作符时,如:.,|,$,这些字符失去其特殊的含义,只按字面表示匹配。
  13. 一个字符串格式化表达式 template % arg_tuple 包含一个格式字符串 template,它由如%-6s 和%0.2d 这样的转换标识符符组成。

11 习题

https://www.cnblogs.com/junzhi1989/archive/2012/07/02/2573142.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值