《tensorflow:实战google深度学习框架》9.3.2机器翻译文本数据的预处理

       这一节以一个较小的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 = ["<url>", '</url>', '<keywords>', '</keywords>', '<speaker>', '</speaker>', '<talkid>', '</talkid>',
        '<title>', '</title>', '<reviewer>', '</reviewer>', '<translator>', '</translator>', '<description>',
        '</description>']


# 判断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编码(如把引号替换为 ”&quot;”) 。-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]

# 之后需要在文本换行出加入句子结束符<eos>,先将这个符号加入词汇表
sorted_words = ["<sos>", "<eos>", "<unk>"] + 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)))}


# 如果出现了被删除的低频单词,则替换成“<unk>”
def get_id(word):
    return word_to_id[word] if word in word_to_id else word_to_id["<unk>"]


fin = codecs.open(RAW_DATA, 'r', 'utf-8')
fout = codecs.open(OUTPUT_DATA, 'w', 'utf-8')
for line in fin:
    words = line.strip().split() + ["<eos>"]
    # 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)

五、填充和batching

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))

    # 删除内容为空(只包含<eos>)的句子和长度过长的句子
    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)

    # 解码器需要的目标句子起始应该是<sos>符号,把<sos>符号加入数据集中的目标句子的元组中
    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))

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值