文章目录
一、机器翻译
机器翻译(MT)
:将一段文本从一种语言自动翻译为另一种语言,用神经网络解决这个问题通常称为神经机器翻译(NMT)。 主要特征:输出是单词序列而不是单个单词。 输出序列的长度可能与源序列的长度不同。
1.1 数据预处理和清洗
将数据集清洗、转化为神经网络的输入minbatch,这是任何一个神经网络应用的首要步骤。
字符在计算机里是以编码的形式存在,我们通常所用的空格是 \x20
,是在标准ASCII可见字符 0x20~0x7e
范围内。 而 \xa0
属于 latin1 (ISO/IEC_8859-1)中的扩展字符集字符,代表不间断空白符nbsp
(non-breaking space),超出gbk编码范围,是需要去除的特殊字符。再数据预处理的过程中,我们首先需要对数据进行清洗。
def preprocess_raw(text):
text = text.replace('\u202f', ' ').replace('\xa0', ' ')
out = ''
for i, char in enumerate(text.lower()):
if char in (',', '!', '.') and i > 0 and text[i-1] != ' ':
out += ' '
out += char
return out
1.2 文本预处理
1.2.1 分词
字符串—单词组成的列表,属于文本预处理环节的一步。我们对每个句子进行分词,也就是将一个句子划分成若干个词(token),转换为一个词的序列。同样已经有很多工具可以直接用于分词。
用现有工具进行分词
我们前面介绍的分词方式非常简单,它至少有以下几个缺点:
- 标点符号通常可以提供语义信息,但是我们的方法直接将其丢弃了
- 类似“shouldn’t", "doesn’t"这样的词会被错误地处理
- 类似"Mr.", "Dr."这样的词会被错误地处理
我们可以通过引入更复杂的规则来解决这些问题,但是事实上,有一些现有的工具可以很好地进行分词,我们在这里简单介绍其中的两个:spaCy和NLTK。
举例如下:
# spaCy
import spacy
nlp = spacy.load('en_core_web_sm')
doc = nlp(text)
print([token.text for token in doc])
# NLTK
from nltk.tokenize import word_tokenize
from nltk import data
data.path.append('/home/kesci/input/nltk_data3784/nltk_data')
print(word_tokenize(text))
1.2.2 建立字典
为了方便模型处理,我们需要将字符串转换为数字。因此我们需要先构建一个字典(vocabulary),将每个词映射到一个唯一的索引编号。建立字典的过程会得到如下信息:
- 去重后词典,及其中单词对应的索引列表
- 还可以得到给定索引找到其对应的单词的列表,以及给定单词得到对应索引的字典。
- 原始语料所有词对应的词典索引的列表
注意:在构建字典时我们要对每个句子的开头,结尾,以及未知词汇特设元素对应。另外我们数据集中的句子并非对齐的,我们也要设置padding
特殊词来对短句子进行补全长度。同样对于过长的句子我们也要进行截断操作,节省计算开支。
class Vocab(object):
def __init__(self, tokens, min_freq=0, use_special_tokens=False):
counter = count_corpus(tokens) # :
self.token_freqs = list(counter.items())
self.idx_to_token = []
if use_special_tokens:
# padding, begin of sentence, end of sentence, unknown
self.pad, self.bos, self.eos, self.unk = (0, 1, 2, 3)
self.idx_to_token += ['<pad>', '<bos>', '<eos>', '<unk>']
else:
self.unk = 0
self.idx_to_token += ['<unk>']
self.idx_to_token += [token for token, freq in self.token_freqs
if freq >= min_freq and token not in self.idx_to_token]
self.token_to_idx = dict()
for idx, token in enumerate(self.idx_to_token):
self.token_to_idx[token] = idx
def __len__(self):
return len(self.idx_to_token)
def __getitem__(self, tokens):
if not isinstance(tokens, (list, tuple)):
return self.token_to_idx.get(tokens, self.unk)
return [self.__getitem__(token) for token in tokens]
def to_tokens(self, indices):
if not isinstance(indices, (list, tuple)):
return self.idx_to_token[indices]
return [self.idx_to_token[index] for index in indices]
def count_corpus(sentences):
tokens = [tk for st in sentences for tk in st]
return collections.Counter(tokens) # 返回一个字典,记录每个词的出现次数
在生成字典时,我们会通常把高频出现的单词放在字典开始的部分,这样可以减少查询次数(相对于随机编码),训练word2vec中有个HUffman树,也是这个思想。
token_freqs = sorted(counter.items(), key=lambda x:x[0])
token_freqs.sort(key=lambda x:x[1], reverse=True)
1.2.3 将词转为索引
使用字典,我们可以将原文本中的句子从单词序列转换为索引序列
1.3 语言模型
一段自然语言文本可以看作是一个离散时间序列,给定一个长度 T T T为的词的序列 w 1 , w 2 , … , w T w_1, w_2, \ldots, w_T w1,w2,…,wT,语言模型的目标就是评估该序列是否合理,即计算该序列的概率:
P ( w 1 , w 2 , … , w T ) . P(w_1, w_2, \ldots, w_T). P(w1,w2,…,wT).
本节我们介绍基于统计的语言模型,主要是 n n n元语法( n n n-gram)。在后续内容中,我们将会介绍基于神经网络的语言模型。
假设序列 w 1 , w 2 , … , w T w_1, w_2, \ldots, w_T w1,w2,…,wT中的每个词是依次生成的,我们有
P ( w 1 , w 2 , … , w T ) = ∏ t = 1 T P ( w t ∣ w 1 , … , w t − 1 ) = P ( w 1 ) P ( w 2 ∣ w 1 ) ⋯ P ( w T ∣ w 1 w 2 ⋯ w T − 1 ) \begin{aligned} P(w_1, w_2, \ldots, w_T) &= \prod_{t=1}^T P(w_t \mid w_1, \ldots, w_{t-1})\\ &= P(w_1)P(w_2 \mid w_1) \cdots P(w_T \mid w_1w_2\cdots w_{T-1}) \end{aligned} P(w1,w2,…,wT)=