这一节以一个较小的IELST TED演讲数据集作为示例,讲解如何由文本数据集制作词汇表,并将文本中的字符转译为词汇表中对应词汇的编号,方便后续处理。
数据集的下载地址是:https://wit3.fbk.eu/mt.php?release=2015-01。选择英文-中文的数据集
解压以后要用到的是train.tags.en-zh.en、train.tags.en-zh.zh这两个文件。可以看到两个文件中,每一行都是中英文对应的。
接下来就是要把文件中每个单词和标点都用空格分割开。分词之前有个问题,原始文件是这样的
每个发言稿正文之前和结束之后有一些附加信息,要先把这些带标签的行都删掉,我用python处理了一下,删除带这些标签的行。
import codecs
RAW_DATA = "../../data/en-zh/train.tags.en-zh.en"
tags = ["", ' ', '', ' ', '', ' ', '', ' ',
'', ' ', '', ' ', '', ' ', '',
' ']
# 判断line里是否包含标签
def is_delete(tags, line):
flag = False
for tag in tags:
if tag in line:
flag = True
return flag
lines = []
with codecs.open(RAW_DATA, "r", "utf-8") as fin:
# codecs专门用作编码转换。用codecs提供的open方法来指定打开的文件的语言编码,它会在读取的时候自动转换为内部unicode
for line in fin.readlines():
if not is_delete(tags, line):lines.append(line)
with codecs.open(RAW_DATA, "w", "utf-8") as fout:
fout.writelines(lines)
两个文件都按这个方式处理一下,这样每个文件都能得到209940行纯句子。接下来就可以分词了。
英文分词用的是书里介绍的moses工具,下载代码https://github.com/moses-smt/mosesdecoder/blob/master/scripts/tokenizer/tokenizer.perl 。这里只下载这一个文件,处理train.tags.en-zh.en的时候会报错,缺少文件。
所以我把整个分词工具包都下载了,然后调用这个包里的perl文件。
$ perl mosesdecoder/scripts/tokenizer/tokenizer.perl -no-escape -l en < ../data/en-zh/train.tags.e-l en < ../data/en-zh/train.tags.en-zh.en > ../data/en-zh/train.txt.en
< 表示原始输入文件,> 表示输出文件名。-no-escape参数表示不把标点符号替换为HTML编码(如把引号替换为 ”"”) 。-l en 参数表示输入文件的语言是英文。
分词结果如下
可以看到,基本都分出来了,但是单引号没有分词成功。我后续又用python处理了一下,将单引号也分出来。
import codecs
RAW_DATA = "../../data/en-zh/t.txt.en"
lines = []
with codecs.open(RAW_DATA, "r", "utf-8") as fin:
# codecs专门用作编码转换。用codecs提供的open方法来指定打开的文件的语言编码,它会在读取的时候自动转换为内部unicode
for line in fin.readlines():
line = line.replace("'", "' ") # 处理en文件
lines.append(line)
with codecs.open(RAW_DATA, "w", "utf-8") as fout:
fout.writelines(lines)
接着按照书上的方法分离中文
$ sed 's/ //g;s/\B/ /g' ../data/en-zh/train.tags.en-zh.zh > ../data/en-zh/train.txt.zh
sed ’ s/ //g ’ 表示去除文本中已有的空格。 ’ s/\B/ /g ’将每个字之间的边界替换为空格。但是这个不能分离所有的标点。
然后我又在python里重新处理了一下
import codecs
RAW_DATA = "../../data/en-zh/t.txt.zh"
lines = []
with codecs.open(RAW_DATA, "r", "utf-8") as fin:
# codecs专门用作编码转换。用codecs提供的open方法来指定打开的文件的语言编码,它会在读取的时候自动转换为内部unicode
for line in fin.readlines():
line = line.replace(" ", "")
line = ' '.join(line) # 处理zh文件
lines.append(line)
with codecs.open(RAW_DATA, "w", "utf-8") as fout:
fout.writelines(lines)
这样两个文件都分词完成。
英文词汇表10000,中文词汇表4000,懒得写if了,处理不同文件时,改一下收集范围就行。
import codecs
import collections
from operator import itemgetter
RAW_DATA = "../../data/en-zh/train.txt.zh"
VOCAB_OUT = "zh.vocab"
# 统计单词出现频率
counter = collections.Counter()
with codecs.open(RAW_DATA, "r", "utf-8") as f:
# codecs专门用作编码转换。用codecs提供的open方法来指定打开的文件的语言编码,它会在读取的时候自动转换为内部unicode
for line in f:
for word in line.strip().split():
# strip: 用来去除头尾字符、空白符
counter[word] += 1
# 按词频顺序对单词进行排序
sorted_word_to_cnt = sorted(counter.items(), key=itemgetter(1), reverse=True) # reverse参数表示是否逆序
sorted_words = [x[0] for x in sorted_word_to_cnt]
# 之后需要在文本换行出加入句子结束符,先将这个符号加入词汇表
sorted_words = ["", "", ""] + sorted_words
if len(sorted_words) > 4000: # en文件是10000
sorted_words = sorted_words[:4000]
with codecs.open(VOCAB_OUT, "w", 'utf-8') as file_output:
for word in sorted_words:
file_output.write(word + "\n")
import codecs
import sys
RAW_DATA = "../../data/en-zh/t.txt.zh"
VOCAB = "zh.vocab"
OUTPUT_DATA = "zh.train"
# 读取词汇表,建立词汇到单词编号的映射
with codecs.open(VOCAB, 'r', 'utf-8') as f_vocab:
vocab = [w.strip() for w in f_vocab.readlines()]
word_to_id = {k: v for (k, v) in zip(vocab, range(len(vocab)))}
# 如果出现了被删除的低频单词,则替换成“”
def get_id(word):
return word_to_id[word] if word in word_to_id else word_to_id[""]
fin = codecs.open(RAW_DATA, 'r', 'utf-8')
fout = codecs.open(OUTPUT_DATA, 'w', 'utf-8')
for line in fin:
words = line.strip().split() + [""]
# print("当前所读行")
# print(words)
# 将每个单词替换为词汇表中的编号
out_line = ' '.join([str(get_id(w)) for w in words]) + '\n'
# print([get_id(w) for w in words])
fout.write(out_line)
fin.close()
fout.close()
这里出现了一个问题,中文集转编号以后多了一行,崩溃,明明之前所有的文件都是209940行没有出错,结果转编号以后中文文件zh多出了一行,也就是说在文档的某一处,多出了一个换行符,因为英文文件en的转编号没有出错,依旧是209940行,所以问题一定出在zh文件中。这么多行数据不可能一行一行看,我找了个别的大佬做好了的数据集和我自己的数据集用python对比了一下,发现在原zh文件的192165行有这么个特殊的符号(找了好久的原因,简直崩溃!!!)
这个符号是ASCII控制字符,代表换行符,unicode编码是“\u2028”,所以在写入文档时,自动换行了。在文档里把这个符号删掉就行了。也可以用python过滤一下整个文档
import codecs
# 删除文本中的ASCII控制字符
RAW_DATA = "../../data/en-zh/t.txt.zh"
lines = []
with codecs.open(RAW_DATA, "r", "utf-8") as fin:
# codecs专门用作编码转换。用codecs提供的open方法来指定打开的文件的语言编码,它会在读取的时候自动转换为内部unicode
for line in fin.readlines():
line = line.replace("\u2028", "")
lines.append(line)
print(lines)
with codecs.open("out", "w", "utf-8") as fout:
fout.writelines(lines)
import tensorflow as tf
MAX_LEN = 50
SOS_ID = 1
# 使用dataset从文件中读取一个语言的数据
def MakeDataset(filepath):
dataset = tf.data.TextLineDataset(filepath)
# 根据空格将单词编号切开并放入一个一维向量
dataset = dataset.map(lambda string: tf.string_split([string]).values)
# 将字符串形式的的单词编号转换为整数
dataset = dataset.map(lambda string: tf.string_to_number(string, tf.int32))
# 统计每个句子的单词数量,并与句子内容一起放入dataset中
dataset = dataset.map(lambda x: (x, tf.size(x)))
return dataset
# 从源语言文件和目标语言文件分别读取数据,并进行填充和batching操作
def MakeSrcTrgDataset(src_path, trg_path, batch_size):
src_data = MakeDataset(src_path)
trg_data = MakeDataset(trg_path)
# 通过zip操作将两个数据集合并为一个。数据集中的每项数据ds包含4个张量
# ds[0][0] 源句子
# ds[0][1] 源句子长度
# ds[1][0] 目标句子
# ds[1][1] 目标句子长度
dataset = tf.data.Dataset.zip((src_data, trg_data))
# 删除内容为空(只包含)的句子和长度过长的句子
def FilterLength(src_tuple, trg_tuple):
((src_input, src_len), (trg_label, trg_len)) = (src_tuple, trg_tuple)
src_len_ok = tf.logical_and(tf.greater(src_len, 1), tf.less_equal(src_len, MAX_LEN))
trg_len_ok = tf.logical_and(tf.greater(trg_len, 1), tf.less_equal(trg_len, MAX_LEN))
return tf.logical_and(src_len_ok, trg_len_ok)
dataset = dataset.filter(FilterLength)
# 解码器需要的目标句子起始应该是符号,把符号加入数据集中的目标句子的元组中
def MakeTrgInput(src_tuple, trg_tuple):
((src_input, src_len), (trg_label, trg_len)) = (src_tuple, trg_tuple)
trg_input = tf.concat([[SOS_ID], trg_label[:-1]], axis=0)
return ((src_input, src_len), (trg_input, trg_label, trg_len))
dataset = dataset.map(MakeTrgInput)
# 随机打乱数据集
dataset = dataset.shuffle(10000)
# 规定填充后输出的数据维度
padded_shapes = (
(
tf.TensorShape([None]), # 源句子是长度未知的向量
tf.TensorShape([]) # 源句子长度是单个数字
),
(
tf.TensorShape([None]), # 目标句子(解码器输入)是长度未知的向量
tf.TensorShape([None]), # 目标句子(解码器目标输出)是长度未知的向量
tf.TensorShape([]) # 目标句子长度是单个数字
)
)
# 调用padding_batch方法进行batching操作
batched_dataset = dataset.padded_batch(batch_size, padded_shapes)
return batched_dataset
with tf.Session() as sess:
tf.global_variables_initializer().run()
src_path = "e.train"
trg_path = "z.train"
batch_size = 4
dataset = MakeSrcTrgDataset(src_path, trg_path, batch_size)
iterator = dataset.make_initializable_iterator()
((src_input, src_len), (trg_input, trg_label, trg_len)) = iterator.get_next()
sess.run(iterator.initializer) # 用dataset.make_initializable_iterator()时必须初始化一下
print(sess.run(src_len))