Efficient Estimation of Word Representations in Vector Space(Word2vec)阅读心得分享

转载 2019-11-13 20:35  阅读 32 次 评论 0 条

论文原文链接

Neural Machine Translation by Jointly Learning To Align and Translate

1.论文导读

机器翻译本质上是源语言和目标语言的一种等价信息转换,也成为自动翻译,是将一种语言(源语言)转化为另一种语言的过程。从历史上来看,机器翻译的发展历史经过了三个阶段。

在1980年代出现的基于规则的机器翻译、1990年代出现的基于统计的翻译和2013年左右出现的基于神经网络的翻译。基于统计的翻译和基于神经网络的翻译又被称作为基于数据驱动的机器翻译。

最古老的机器翻译方法就是基于规则的机器翻译,在翻译的时候,首先根据源语言的词的词性,然后将词语翻译成目标语言,接着,再使用语法规则,对翻译后的句子进行调整。根据翻译方式的不同,有基于词的方式、基于结构转换的翻译和基于中间语的翻译。举个例子来说,”We do achieve the target“就有可能被翻译为“我们 做 实现 目标”,而实际上“do”只是强调的作用。有限的语法无法覆盖各种各样的语言现象,并且这种实现方法非常依赖于专家的知识。

第二类方法就是基于统计的机器翻译。这种方法通过对语料进行统计分析,构建模型。它的本质是如何找到源语言被翻译成目标语言后,出现最大概率的那句话,也就是对概率进行建模。举个例子来说,“我 已经 看 了 这篇 博客”这句话,就会被翻译成“I have read this blog”。从直观上来说,在翻译的时候,我们可以枚举所有的英文句子来建立概率分布,但这种方法显然是不合适的,因为句子有“无限”多个。在这个时候,我们就引入了隐变量。主流的方法就是2002年提出的“隐变量对数线性模型”,它的方法是设计特征函数,也就是在隐式语言结构上设计特征。

第三类就是基于神经网络的机器翻译。它通过学习大量的成对的语料,让模型可以自己学习语言的特征,找到输入和输出之间的映射。它的核心理念就是建立一种端到端模型(end-to-end),让算法自动找到映射的关系,因此这类方法也被称为表示学习。主流的方法有2014年Kyunghyun等人提出的encoder-decoder模型和Sutskever等人提出的sequence-to-sequence模型。和第二类基于统计的机器翻译方法不一样的地方在于,它并没有引入隐变量,它利用马尔科夫性,将句子生成的概率分解成了各个条件概率的乘积。同时,不像基于统计的机器翻译方法那样是离散形式的建模,它可以对模型进行连续的建模。这也是深度学习带来的革命性的变化。

2.论文abstract和introduction

神经机器翻译是最近新兴的一种机器翻译的方法。大部分的神经机器翻译都是基于enoder-decoder框架的。并且它们都会将源语言的句子压缩成一个固定的向量,然后传递给decoder。这存在一些潜在的问题,比如,从我们直觉上来感受,如果句子长,那么强行的将句子中的有效信息全部压缩在一个固定的向量中的话,信息肯定会丢失。

于是,该篇论文的作者提出了一种新的方法,这个方法也是基于encoder-decoder的。与之前的encoder-decoder的模型不同之处在于,每次在翻译一个单词的时候,模型会自动的搜寻该单词与源语言哪些单词有关联,并将这种关联的强度进行数字化表示(在模型中就是权重)。并且,实验得出,这种方法可以解决长句子翻译不准的问题(相较于传统的encoder-decoder模型)。

3.背景-传统RNN Encoder-Decoder模型

传统的RNN Encoder-Decoder模型在训练阶段的时候,会使模型去最大化源语言翻译成目标语言的条件概率。当模型训练好后,当待翻译的源语言句子放入到模型中的时候,模型会自动的计算最大的目标语言句子的概率,并且将这个句子当做是翻译后的句子。下面具体讲一下传统的RNN Encoder-Decoder模型,直接上图,

如上图所示,假设我们有一句待翻译的话,“我/已经/阅读/了/这篇/博客",我们要将它翻译成英文。图中”C“的左侧是encoder,右侧是decoder,”C"是待翻译语句的语义信息。

首先,“我/已经/阅读/了/这篇/博客"这句话会经过encoder,encoder会将这句话进行编码,encoder用到的模型是RNN,RNN的原理可以见我的博客,RNN会通过一个时刻,一个时刻地对“我/已经/阅读/了/这篇/博客"这句话进行编码,当编码结束后,我们会将最后一个时刻的RNN的隐层的输出当做“我/已经/阅读/了/这篇/博客"这句话的语义压缩,在图中也就是C。

接着,解码器每次在产生一个翻译后的英文单词的时候,它都会利用这个编码器的语义压缩C。那具体是如何做到的呢?解码器的模型也是RNN,首先,在时刻为0的时候,RNN会利用语义压缩C,并且这个RNN同时还会接收输入为“”的token来作为这个时刻的输入,接着这个时刻的输出端就会产生第一个单词“I”(这里利用了softmax,输出层是一个词典大小维度的向量,哪个维度的值最大,就取那个维度所对应的单词作为所预测的单词),大家可以想的到,在训练阶段,解码器不可能可以立马产生“I”,而是产生其他的单词,如“tell”等等单词。因此,训练阶段的目的,就是让编码器和解码器的参数,随着训练次数的增加,往“正确”的方向改变,以至于让其产生单词“I”。换句话说,在时刻为0的时候,我们希望解码器产生单词“I”。接着,无论在时刻为0的时候产生什么单词,它都会被当做时刻为1(也就是s1)的输入。因此,如上图,在时刻为1的时候,共有3个输入,一个是C,一个是时刻为0时候产生的隐层结果,最后一个时刻为0的时候的输出。直到这句话产生结束(结束的标志是解码器产生了/s等特殊标志,这个可以在训练的时候指定)。至此,decoder的任务也就完成了。

4.本文提出的模型-加入了attenton机制的模型

本文提出的模型在文章中叫做RNNsearch模型,流程图如下图所示,

首先,右上角的图片是encoder,这个部分和RNNenc模型是一样的,接着,在decoder部分,就会有巨大的差别。我们以decoder时刻为0时举例子,在时刻为0时,decoder的BiLSTM会接受三个地方的输入,第1个输入的来源是时刻为0时的初始状态s0,这个状态是随机初始化的(无论在训练阶段还是在预测阶段,都是随机的,这个在代码里可以看到)。第2个输入是来源于""这个token的embedding后的向量。第3个输入就有些复杂了,并且,这个第3个输入也就是这篇论文提出的核心创新。第3个输入计算方法如下:

首先,将随机初始化的s0拿来,和encoder所输出的h1~h6,各自做一次余弦相似度的计算(这个计算方式可以自己定义),各自会得到一个e1 ~ e6的数值,然后将6个数值做一次softmax操作,会得到α1 ~ α6,那么我们可以知道,α1+α2+α3+α4+α5+α6=1,因此,我们可以将α1,α2,α3,α4,α5,α6中的每一个,都分别当作是s0和h1 ~ h6的相似度。接着,α1,α2,α3,α4,α5,α6和h1,h2,h3,h4,h5,h6这6个向量分别做一次元素乘积,所得的6个向量再做一次元素的相加,得到最终的向量。接着,就把这个向量当作时刻0时,BiLSTM的第3个输入。这里要注意,代码中的BahdanauAttention机制,实际上是做了一次卷积操作,这个操作等价于上述所讲的attention原理,具体大家可以去百度下BahdanauAttention的原理。

就这样,在时刻为0时,BiLSTM就产生了一个输出。那么此时,时刻就变为了1,接下去的过程就和时刻为0的过程是一样的。大家可以自己仔细理解理解。

5.代码复现、详细讲解及Github地址

完整代码地址:https://github.com/haitaifantuan/nlp_paper_understand

代码下载:code

数据下载:data

语料预处理

首先,我们要进行的是语料预处理部分。我们可以从我的github地址下载代码以及从腾讯云盘下载数据集,TODO,解压出来后,需要运行的是“data_preprocessing.py”这个模块,详细代码如下,代码主要包含tokenize语料部分、构建token dictionary部分以及将语料从token转换为id部分。tokenize语料部分就是代码中对应的tokenize_corpus(self)这个方法。构建token dictionary部分就是build_token_dictionary(self)这个方法。将语料从token转换为id部分就是convert_data_to_id_pad_eos(self)这个方法。

#coding=utf-8
'''
Author:Haitaifantuan
'''
import os
import nltk
import pickle
import train_args
import collections
 
class Data_preprocess(object):
    def __init__(self):
        pass
 
    def tokenize_corpus(self):
        '''
        该函数的作用是:将英文语料和中文语料进行tokenize,然后保存到本地。
        '''
        # 将英文语料tokenize,保存下来。
        if not os.path.exists(train_args.raw_train_english_after_tokenization_data_path.replace('train.raw.en.after_tokenization.txt', '')):
            os.mkdir(train_args.raw_train_english_after_tokenization_data_path.replace('train.raw.en.after_tokenization.txt', ''))
        fwrite = open(train_args.raw_train_english_after_tokenization_data_path, 'w', encoding='utf-8')
        with open(train_args.raw_train_english_data_path, 'r', encoding='utf-8') as file:
            for line in file:
                line = line.strip()
                line = nltk.word_tokenize(line)
                # 将tokenization后的句子写入文件
                fwrite.write(' '.join(line) + '\n')
        fwrite.close()
 
        # 将中文语料tokenize,保存下来。
        if not os.path.exists(train_args.raw_train_chinese_after_tokenization_data_path.replace('train.raw.zh.after_tokenization.txt', '')):
            os.mkdir(train_args.raw_train_chinese_after_tokenization_data_path.replace('train.raw.zh.after_tokenization.txt', ''))
        fwrite = open(train_args.raw_train_chinese_after_tokenization_data_path, 'w', encoding='utf-8')
        with open(train_args.raw_train_chinese_data_path, 'r', encoding='utf-8') as file:
            for line in file:
                line = line.strip()
                line = list(line)
                # 将tokenization后的句子写入文件
                fwrite.write(' '.join(line) + '\n')
        fwrite.close()
 
        print('语料tokenization完成')
 
    def build_token_dictionary(self):
        '''
        该函数的作用是:根据英文语料和中文语料,建立各自的,以字为单位的token dictionary。
        '''
        # 生成英文的token_dictionary
        english_token_id_dictionary = {}
        # 我们定义unk的id是0,unk的意思是,
        # 当句子中碰到token dictionary里面没有的token的时候,就转换为这个
        english_token_id_dictionary[''] = 0  
        english_token_id_dictionary[''] = 1  # 我们定义sos的id是1
        english_token_id_dictionary[''] = 2  # 我们定义eos的id是1
        en_counter = collections.Counter(
        )  # 创建一个英文token的计数器,专门拿来计算每个token出现了多少次
        with open(train_args.raw_train_english_after_tokenization_data_path, 'r', encoding='utf-8') as file:
            for line in file:
                line = line.strip().split(' ')
                for token in line:
                    en_counter[token] += 1
        most_common_en_token_list = en_counter.most_common(train_args.Source_vocab_size - 3)  # 找出最常见的Source_vocab_size-3的token
        for token_tuple in most_common_en_token_list:
            english_token_id_dictionary[token_tuple[0]] = len(english_token_id_dictionary)
        # 保存english_token_id_dictionary
        if not os.path.exists(train_args.english_token_id_dictionary_pickle_path.replace('english_token_id_dictionary.pickle', '')):
            os.mkdir(train_args.english_token_id_dictionary_pickle_path.replace('english_token_id_dictionary.pickle', ''))
        with open(train_args.english_token_id_dictionary_pickle_path, 'wb') as file:
            pickle.dump(english_token_id_dictionary, file)
 
        # 生成中文的token_dictionary 以及把 tokenization后的结果保存下来
        chinese_token_id_dictionary = {}
        # 我们定义unk的id是0,unk的意思是,
        # 当句子中碰到token dictionary里面没有的token的时候,就转换为这个
        chinese_token_id_dictionary[''] = 0  
        chinese_token_id_dictionary[''] = 1  # 我们定义sos的id是1
        chinese_token_id_dictionary[''] = 2  # 我们定义eos的id是1
        # 创建一个中文token的计数器,专门拿来计算每个token出现了多少次
        zh_counter = collections.Counter()
        with open(train_args.raw_train_chinese_after_tokenization_data_path, 'r', encoding='utf-8') as file:
            for line in file:
                line = line.strip().split(' ')
                for token in line:
                    zh_counter[token] += 1
        most_common_zh_token_list = zh_counter.most_common(train_args.Target_vocab_size - 3)  # 找出最常见的Target_vocab_size-3的token
        for token_tuple in most_common_zh_token_list:
            chinese_token_id_dictionary[token_tuple[0]] = len(chinese_token_id_dictionary)
        # 保存token_dictionary
        if not os.path.exists(train_args.chinese_token_id_dictionary_pickle_path.replace('chinese_token_id_dictionary.pickle', '')):
            os.mkdir(train_args.chinese_token_id_dictionary_pickle_path.replace('chinese_token_id_dictionary.pickle', ''))
        with open(train_args.chinese_token_id_dictionary_pickle_path, 'wb') as file:
            pickle.dump(chinese_token_id_dictionary, file)
        print('英文token_dictionary和中文token_dictionary创建完毕')
 
    def convert_data_to_id_pad_eos(self):
        '''
        该函数的作用是:
        将英文语料转换成id形式,并在末尾添加[EOS]
        将中文语料转换成id形式,并在句子开头添加[SOS]
        '''
        # 读取英文的token_dictionary
        with open(train_args.english_token_id_dictionary_pickle_path, 'rb') as file:
            english_token_id_dictionary = pickle.load(file)
 
        if not os.path.exists(train_args.train_en_converted_to_id_path.replace('train.en.converted_to_id.txt', '')):
            os.mkdir(train_args.train_en_converted_to_id_path.replace('train.en.converted_to_id.txt', ''))
        fwrite = open(train_args.train_en_converted_to_id_path, 'w', encoding='utf-8')
        # 读取tokenization后的英文语料,并将其转换为id形式。
        with open(train_args.raw_train_english_after_tokenization_data_path, 'r', encoding='utf-8') as file:
            for line in file:
                line_converted_to_id = []
                line = line.strip().split(' ')
                for token in line:
                    # 将token转换成id
                    token_id = english_token_id_dictionary.get(
                        token, english_token_id_dictionary[''])
                    line_converted_to_id.append(str(token_id))
                # 在英文语料最后加上EOS
                line_converted_to_id.append(
                    str(english_token_id_dictionary['']))
                # 写入本地文件
                fwrite.write(' '.join(line_converted_to_id) + '\n')
        fwrite.close()
 
        # 读取中文的token_dictionary
        with open(train_args.chinese_token_id_dictionary_pickle_path, 'rb') as file:
            chinese_token_id_dictionary = pickle.load(file)
 
        if not os.path.exists(train_args.train_zh_converted_to_id_path.replace('train.zh.converted_to_id.txt', '')):
            os.mkdir(train_args.train_zh_converted_to_id_path.replace('train.zh.converted_to_id.txt', ''))
        fwrite = open(train_args.train_zh_converted_to_id_path, 'w', encoding='utf-8')
        # 读取tokenization后的中语料,并将其转换为id形式。
        with open(train_args.raw_train_chinese_after_tokenization_data_path, 'r', encoding='utf-8') as file:
            for line in file:
                line_converted_to_id = []
                line = line.strip().split(' ')
                for token in line:
                    # 将token转换成id
                    token_id = chinese_token_id_dictionary.get(
                        token, english_token_id_dictionary[''])
                    line_converted_to_id.append(str(token_id))
                # 因为这个中文语料是当做目标词的,因此也需要在中文语料最后面加上EOS
                # decoder的输入的最开始的BOS,会在train.py里面添加。
                line_converted_to_id.append(
                    str(chinese_token_id_dictionary['']))
                # 写入本地文件
                fwrite.write(' '.join(line_converted_to_id) + '\n')
        fwrite.close()
 
        print('英文语料转换为id并且添加[EOS]标致完毕')
        print('中文语料转换为id并且添加[EOS]标致完毕')
 
# 创建预处理data对象
data_obj = Data_preprocess()
# 将英文语料和中文语料进行tokenization
data_obj.tokenize_corpus()
# 创建英文语料和中文语料的token_dictionary
data_obj.build_token_dictionary()
# 根据token_dictionary将英文语料和中文语料转换为id形式
# 并且在英文语料的最后添加[EOS]标致,在中文语料的最开始添加[SOS]标致
# 并将转化后的语料保存下来
data_obj.convert_data_to_id_pad_eos()
本文地址:http://51blog.com/?p=5968
关注我们:请关注一下我们的微信公众号:扫描二维码广东高校数据家园_51博客的公众号,公众号:数博联盟
温馨提示:文章内容系作者个人观点,不代表广东高校数据家园_51博客对观点赞同或支持。
版权声明:本文为转载文章,来源于 张小李 ,版权归原作者所有,欢迎分享本文,转载请保留出处!

发表评论


表情