1. 我们如何设计一种新的语言资源,并确保它的覆盖面、平衡以及支持广泛用途的文 档?
2. 现有数据对某些分析工具格式不兼容,我们如何才能将其转换成合适的格式?
原始数据需要进行 收集、清理、记录并以系统化的结构存储。
标注可分为各种层次,一些需要语言的形态或句 法的专门知识。
要在这个阶段成功取决于建立一个高效的工作流程,包括适当的工具和格式 转换器。
质量控制程序可以将寻找标注中的不一致落实到位,确保尽最大可能在标注者之间 达成一致。
它有时被用于表示已标注的文本和词汇资源。
不同于 HTML 的标签是预 定义的,XML 允许我们组建自己的标签。
不同于数据库,XML 允许我们创建的数据而不必 事先指定其结构,它允许我们有可选的、可重复的元素。
2. 现有数据对某些分析工具格式不兼容,我们如何才能将其转换成合适的格式?
3. 有什么好的方法来记录我们已经创建的资源的存在,让其他人可以很容易地找到它?
11.1 语料库结构:一个案例研究
TIMIT 语料库是第一个广泛发布的已标注语音数据库,它有一个特别清晰的组织结构。
TIMIT 由一个包括克萨斯仪器公司和麻省理工学院的财团开发,它也由此得名。
它被设计用 来为声学-语音知识的获取提供数据,并支持自动语音识别系统的开发和评估。
TIMIT 的结构
#与布朗语料库显示文章风格和来源的平衡选集一样,TIMIT 包括方言、说话者和材料的 平衡选集。
#对 8 个方言区中的每一种方言,具有一定年龄范围和教育背景的 50 个男性和女 性的说话者每人读 10 个精心挑选的句子。
#NLTK 包括 TIMIT 语料库的一个样本。
import nltk
#print(help(nltk.corpus.timit))
print(nltk.corpus.timit.fileids()[:5])
#TIMIT 标识符的结构:每个记录使用说话者的方言区、性别、说话者标识符、句子 类型、句子标识符组成的一个字符串作为标签。
#每个项目都有音标,可以使用 phones()方法访问。
#我们可以按习惯的方式访问相应的 词标识符。
#两种访问方法都允许一个可选的参数 offse t= T rue ,其中包括音频文件的相应 跨度的开始和结尾偏移。
phonetic = nltk.corpus.timit.phones('dr1-fvmh0/sa1')
print(phonetic)
print(nltk.corpus.timit.word_times('dr1-fvmh0/sa1'))
#除了这种文本数据,TIMIT 还包括一个词典,提供每一个词的可与一个特定的话语比较的规范的发音:
timitdict = nltk.corpus.timit.transcription_dict()
print(timitdict['greasy'] + timitdict['wash'] + timitdict['water'])
print(phonetic[17:30])
#最后,TIMIT 包括说话人的人口学统计,允许细粒度的研究声音、社会和性 别特征。
print(nltk.corpus.timit.spkrinfo('dr1-fvmh0'))
主要设计特点
#TIMIT 演示了语料库设计中的几个主要特点。
#首先,语料库包含语音和字形两个标注层。
#一般情况下,文字或语音语料库可能在多个不同的语言学层次标注,包括形态、句法和段落 层次。
#此外,即使在给定的层次仍然有不同的标注策略,甚至标注者之间也会有分歧,因此 我们要表示多个版本。
#TIMIT 的第二个特点是:它在多个维度的变化与方言地区和二元音覆 盖范围之间取得平衡。
#人口学统计的加入带来了许多更独立的变量,这可能有助于解释数据 中的变化,
#便于以后出于在建立语料库时没有想到的目的使用语料库,例如社会语言学。
#第 三个特点是:将原始语言学事件作为录音来捕捉和作为标注来捕捉之间有明显的区分。
#两者 一致表示文本语料库正确,原始文本通常有被认为是不可改变的作品的外部来源。
#那个作品 的任何包含人的判断的转换——即使如分词一样简单——也是后来的修订版;
#因此以尽可能 接近原始的形式保留源材料是十分重要的。
#TIMIT 的第四个特点是语料库的层次结构。
#每个句子 4 个文件,500 个说话者每人 10 个句子,有 20,000 个文件。这些被组织成一个树状结构,
#最后,请注意虽然 TIMIT 是语音语料库,它的录音文本和相关数据只是文本,
#可以使 用程序处理了,就像任何其他的文本语料库那样。
基本数据类型
#TIMIT 语料库只包含两种基本数据类型:词典和文本。
#大多数词典资源都可以使用记录结构表示,即一个关键字加一个或多个字段
11.2 语料库生命周期
语料库并不是从天而降的,需要精心的准备和许多人长时期的输入。原始数据需要进行 收集、清理、记录并以系统化的结构存储。
标注可分为各种层次,一些需要语言的形态或句 法的专门知识。
要在这个阶段成功取决于建立一个高效的工作流程,包括适当的工具和格式 转换器。
质量控制程序可以将寻找标注中的不一致落实到位,确保尽最大可能在标注者之间 达成一致。
由于任务的规模和复杂性,大型语料库可能需要几年的准备,包括几十或上百人 多年的努力。
语料库创建的三种方案
#语料库的一种类型是设计在创作者的探索过程中逐步展现。
#这是典型的传统 “ 领域语言 学”模式,即来自会话的材料 在它被收集的时候就被分析,
#明天的想法往往基于今天的分析 中产生的问题。
#另一种语料库创建方案是典型的实验研究,其中一些精心设计的材料被从一定范围的人 类受试者中收集,
#然后进行分析来评估一个假设或开发一种技术
#最后,还有努力为一个特定的语言收集“参考语料”,
#如美国国家语料库(American Nat ional Corpus,ANC)和英国国家语料库(British National Corpus,BNC)。
质量控制
#自动和手动的数据准备的好的工具是必不可少的。
#然而,一个高质量的语料库的建立很 大程度取决于文档、培训和工作流程等平凡的东西。
#windowdiff 是评 估两个分割一致性的一个简单的算法,通过在数据上移动一个滑动窗口计算近似差错的部分 得分。
#如果我们将标识符预处理成 0 和 1 的序列,当标识符后面跟着边界符号时记录下来,
#我们就可以用字符串表示分割,应用 windowdiff 打分器。
s1 = "00000010000000001000000"
s2 = "00000001000000010000000"
s3 = "00010000000000000001000"
print(nltk.windowdiff(s1, s1, 3))
print(nltk.windowdiff(s1, s2, 3))
print(nltk.windowdiff(s2, s3, 3))
#,窗口大小为 3。windowdiff 计算在一对字符串上滑动这个窗口。
#在每个 位置它计算两个字符串在这个窗口内的边界的总数,然后计算差异,最后累加这些差异。
#我 们可以增加或缩小窗口的大小来控制测量的敏感度。
维护与演变
#随着大型语料库的发布,研究人员立足于均衡的从为完全不同的目的而创建的语料库中派生出的子集进行调查的可能性越来越大。
#语料库随着时间的推移而演变:语料库发布后,研究小组将独立的使用它,选择和 丰富不同的部分;
#然后研究努力整合单独的标注,面临校准注释的艰巨的挑战。
11.3 数据采集
从网上获取数据
#网络是语言分析的一个丰富的数据源。
#最简单的方法是获得出版的网页文本的文集。
#使用定义好的 Web 语料库的优点是它们有文档、稳定并允许重复性实验。
从字处理器文件获取数据
#文字处理软件通常用来在具有有限的可计算基础设施的项目中手工编制文本和词汇。
import re
legal_pos = set(['n', 'v.t.', 'v.i.', 'adj', 'det'])
pattern = re.compile(r"'font-size:11.0pt'>([a-z.]+)<")
document = open("dict.htm", encoding="windows-1252").read()
used_pos = set(re.findall(pattern, document))
illegal_pos = used_pos.difference(legal_pos)
print(list(illegal_pos))
#将 Microsoft Word创建的 HTML 转换成 CSV
from bs4 import BeautifulSoup as bs
def lexical_data(html_file, encoding="utf-8"):
SEP = '_ENTRY'
html = open(html_file, encoding=encoding).read()
html = re.sub(r'<p', SEP + '<p', html)
text = bs(html).get_text()
text = ' '.join(text.split())
for entry in text.split(SEP):
if entry.count(' ') > 2:
yield entry.split(' ', 3)
import csv
writer = csv.writer(open("dict1.csv", "w", encoding="utf-8"))
writer.writerows(lexical_data("dict.htm", encoding="windows-1252"))
从电子表格和数据库中获取数据
#电子表格通常用于获取词表或范式。
#大多数电子表格软件可以将数据导出为 CSV 格式。
转换数据格式
#已标注语言数据很少以最方便的格式保存,往往需要进行各种格式转换。
#最简单的情况,输入和输出格式是同构的
#另一种常见的情况,输出是输入的摘要形式,如:一个倒置的文件索引。有必要在内存 中建立索引结构,然后把它以所需的格式写入一个文件。
#构造一个索引,映射字典定义的词汇到相应的每个词条的语意,已经对定义文本分词,并丢弃短词。
#一旦该索引建成,我们打开一个文件,然后遍历索引项,以所需的格式输出行。
idx = nltk.Index((defn_word, lexeme)
for (lexeme, defn) in pairs
for defn_word in nltk.word_tokenize(defn)
if len(defn_word) > 3)
with open("dict.idx", 'w') as idx_file:
for word in sorted(idx):
idx_words = ','.join(idx[word])
idx_line = "{}: {}".format(word, idx_words)
print(idx_line, file=idx_file)
#在某些情况下,输入和输出数据都包括两个或两个以上的维度。
#例如:输入可能是一组文件,每个都包含单词频率数据的单独列。
#所需的输出可能是一个两维表,其中原来的列以 行出现。在这种情况下,我们一次填补一列填充内部的数据结构,然后在写数据到输出文件 中时一次读取一行。
决定要包含的标注层
#一个语料库可以包含大量的信息,如:句法结构、 形态、韵律、每个句子的语义、加上段落关系或对话行为的标注。
#标注的这些额外的层可能 正是有人执行一个特定的数据分析任务所需要的。
# 分词:文本的书写形式不能明确地识别它的标识符。分词和规范化的版本作为常规的正 式版本的补充可能是一个非常方便的资源。
# 断句:正如我们在第 3 章中看到的,断句比它看上去的似乎更加困难。因此,一些语料 库使用明确的标注来断句。
# 分段:段和其他结构元素(标题,章节等)可能会明确注明。
# 词性:文档中的每个单词的词类。
# 句法结构:一个树状结构,显示一个句子的组成结构。
# 浅层语义:命名实体和共指标注,语义角色标签。
# 对话与段落:对话行为标记,修辞结构。
标准和工具
#一个用途广泛的语料库需要支持广泛的格式。
处理濒危语言时特别注意事项
#语言对科学和艺术的重要性体现在文化宝库包含在语言中。
#然而,世界上大多数语言面临灭绝。对此,许多语言学家都在努力工作,记录语言,构建这个世界语言遗产的重要方面的丰富记录。
11.4 使用 XML
可扩展标记语言(The Extensible Markup Language,XML)为设计特定领域的标记语言 提供了一个框架。它有时被用于表示已标注的文本和词汇资源。
不同于 HTML 的标签是预 定义的,XML 允许我们组建自己的标签。
不同于数据库,XML 允许我们创建的数据而不必 事先指定其结构,它允许我们有可选的、可重复的元素。
语言结构中使用 XML
#由于其灵活性和可扩展性,XML 是表示语言结构的自然选择。
# <entry>
# <headword> whal e</headword>
# <pos>noun</pos>
# <gloss>any of the larger cetacean mammals having a streamlined
# body and breathing through a blowhole on the head</gloss>
# </entry>
#XML 允许使用重复的元素
XML 的作用
ElementTree 接口
#Python 的 ElementTree模块提供了一种方便的方式访问存储在 XML 文件中的数据。
#使用 XML 格式的莎士比亚戏剧集来说明 ElementTree的使用方法。
merchant_file = nltk.data.find('corpora/shakespeare/merchant.xml')
raw = open(merchant_file).read()
print(raw[0:168])
#下一步是作为结构化的 XML 数据使用 ElementTree 处理文件的内容。
from xml.etree.ElementTree import ElementTree
merchant = ElementTree().parse(merchant_file)
print(merchant)
print(merchant[0])
print(merchant[0].text)
print(merchant.getchildren())#要得到所有的子元素的列表,我们使用 getchildren()方法
使用 ElementTree 访问 Toolbox 数据
# Toolbox 数据是语言学家用来管理数据的一种流行和行之有效的格式。
#可以用的 toolbox.xml()方法来访问 Toolbox文件,将它加载到一个 ElementTre e 对象中。此文件包含一个巴布亚新几内亚罗托卡特语的词典。
from nltk.corpus import toolbox
lexicon = toolbox.xml('rotokas.dic')
#有两种方法可以访问 lexicon 对象的内容:通过索引和通过路径
#lexicon[3][0] 返回它的第 一个字段:
print(lexicon[3][0])
print(lexicon[3][0].tag)
print(lexicon[3][0].text)
#第二种方式访问 lexicon 对象的内容是使用路径
# lexicon 是一系列 record 对象,其中
#每个都包含一系列字段对象,如 lx 和 ps。
#使用的路径 record/lx,我们可以很方便地解决 所有的语意。
#在这里,我们使用 FindAll()函数来搜索路径 record/lx 的所有匹配,并且访 问该元素的文本内容,将其规范化为小写:
print([lexeme.text.lower() for lexeme in lexicon.findall('record/lx')][:5])
#查看 XML 格式的 Toolbox数据。
#ElementTree的 write()方法需要一个文件对象。我们通常使用 Python 的内置在 open()函数创建。
import sys
from nltk.util import elementtree_indent
from xml.etree.ElementTree import ElementTree
print(elementtree_indent(lexicon))
tree = ElementTree(lexicon[3])
print(tree.write(sys.stdout, encoding='unicode'))
格式化条目
html = "<table>\n"
for entry in lexicon[70:80]:
lx = entry.findtext('lx')
ps = entry.findtext('ps')
ge = entry.findtext('ge')
html += " <tr><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (lx, ps, ge)
html += "</table>"
print(html)
11.5 使用 Toolbox 数据
#可以为每个条目计算字段的平均个数:
from nltk.corpus import toolbox
lexicon = toolbox.xml('rotokas.dic')
print(sum(len(entry) for entry in lexicon) / len(lexicon))
为每个条目添加一个字段
#为词汇条目添加新的 cv 字段
#将辅音和元音的字符串映射到相应 的 CV 序列
from xml.etree.ElementTree import SubElement
def cv(s):
#首先,将字符串转换为小写
s = s.lower()
#将所有非字母字符[^a-z]用下划线代替。
s = re.sub(r'[^a-z]', r'_', s)
#将所有元音替换为 V
s = re.sub(r'[aeiou]', r'V', s)
#所有不是 V 或下划线的必定是一个辅音,所以我们将它替换为 C
s = re.sub(r'[^V_]', r'C', s)
return (s)
def add_cv_field(entry):
for field in entry:
if field.tag == 'lx':
cv_field = SubElement(entry, 'cv')
cv_field.text = cv(field.text)
lexicon = toolbox.xml('rotokas.dic')
add_cv_field(lexicon[53])
print(nltk.toolbox.to_sfm_string(lexicon[53]))
验证 Toolbox 词汇
#Toolbox格式的许多词汇不符合任何特定的模式。有些条目可能包括额外的字段,或以一种新的方式排序现有字段。
#手动检查成千上万的词汇条目是不可行的。我们可以在 FreqDist 的帮助下很容易地找出频率异常的字段序列:
from collections import Counter
field_sequences = Counter(':'.join(field.tag for field in entry) for entry in lexicon)
print(field_sequences.most_common()[:5])
#使用上下文无关文法验证 Toolbox中的条目。
grammar = nltk.CFG.fromstring('''
S -> Head PS Glosses Comment Date Sem_Field Examples
Head -> Lexeme Root
Lexeme -> "lx"
Root -> "rt" |
PS -> "ps"
Glosses -> Gloss Glosses |
Gloss -> "ge" | "tkp" | "eng"
Date -> "dt"
Sem_Field -> "sf"
Examples -> Example Ex_Pidgin Ex_English Examples |
Example -> "ex"
Ex_Pidgin -> "xp"
Ex_English -> "xe"
Comment -> "cmt" | "nt" |
''')
def validate_lexicon(grammar, lexicon, ignored_tags):
rd_parser = nltk.RecursiveDescentParser(grammar)
for entry in lexicon:
marker_list = [field.tag for field in entry if field.tag not in ignored_tags]
if list(rd_parser.parse(marker_list)):
print("+", ':'.join(marker_list))
else:
print("-", ':'.join(marker_list))
lexicon = toolbox.xml('rotokas.dic')[10:20]
ignored_tags = ['arg', 'dcsv', 'pt', 'vx']
print(validate_lexicon(grammar, lexicon, ignored_tags))
11.6 使用 OLAC 元数据描述语言资源
元数据是什么?
#元数据最简单的定义是“关于数据的结构化数据”。元数据是对象或资源的描述信息, 无论是物理的还是电子的。
#都柏林核心元数据倡议(Dublin Core Metadata Initiative)于 1995 年开始开发的信息发 现、共享和管理的约定。
#都柏林核心由 15 个元数据元素组成, 其中每个元素都是可选的和可重复的,它们是:标题,创建者,主题,描述,发布者,参与 者,日期,类型,格式,标识符,来源,语言,关系,覆盖范围和版权。
#此元数据集可以用 来描述数字或传统的格式中存放的资源。
OLAC :开放语言档案社区
#开放语言档案社区(Open Language Archives Community,OLAC)是正在创建的一个 世界性语言资源的虚拟图书馆的机构和个人的国际伙伴关系:
#(i)制订目前最好的关于语言 资源的数字归档实施的共识,
#(ii)开发存储和访问这些资源的互操作信息库和服务的网络。
#OLAC 的主页在 http://www.language-archives.org/。
11.7 小结
大多数语料库中基本数据类型是已标注的文本和词汇。文本有时间结构,而词汇有记录 结构。
语料库的生命周期,包括数据收集、标注、质量控制以及发布。发布后生命周期仍然继 续,因为语料库会在研究过程中被修改和丰富。
语料库开发包括捕捉语言使用的代表性的样本与使用任何一个来源或文体都有足够的 材料之间的平衡;增加变量的维度通常由于资源的限制而不可行。
XML 提供了一种有用的语言数据的存储和交换格式,但解决普遍存在的数据建模问题 没有捷径。
Toolbox格式被广泛使用在语言记录项目中,我们可以编写程序来支持 Toolbox 文件的 维护,将它们转换成 XML。
开放语言档案社区(OLAC)提供了一个用于记录和发现语言资源的基础设施。