- A+
0x01文本预处理
文本预处理步骤:
1.读入文本
2.分词
3.构建字典,将每个词映射到一个唯一的索引(index)
4. 将文本从词的序列转换为索引的序列,方便输入模型
读入文本
import re
with open('*.txt', 'r') as f:
lines = [re.sub('[^a-z]+', ' ', line.strip().lower()) for line in f]
#lines.strip()选择前缀后缀空白字符;lower()所有大写转化为小写
分词
对每个句子进行分词,也就是将一个句子划分成若干个词(token),转换为一个词的序列。
def tokenize(sentences, token='word'):
"""Split sentences into word or char tokens"""
if token == 'word':
return [sentence.split(' ') for sentence in sentences]
elif token == 'char':
return [list(sentence) for sentence in sentences]
else:
print('ERROR: unkown token type '+token)
tokens = tokenize(lines)
tokens[0:2]
out:
[['the', 'time', 'machine', 'by', 'h', 'g', 'wells', ''], ['']]
建立字典
为了方便模型处理,需要将字符串转换为数字。因此需要先构建一个字典(vocabulary),将每个词映射到一个唯一的索引编号
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) # 返回一个字典,记录每个词的出现次数
pad(padding):模型每次处理的都是一个batch,训练方法是直接梯度下降,std,那每次训练的数据每次都是一个batch,比如一个二维矩阵,矩阵的每一行都是一个句子,那么就有一个问题,同个batch中的各个句子长度不一定是一样的,那么采用的方法是在比较短的句子后面补上若干个特殊的token,使它的长度和最长句子的长度一样长。这里的特殊token为pad。
bos、eos(begin of sentence, end of sentence):有的时候在句子的开始处结尾处分别添加一个特殊的token来表示这个句子的开始和结尾 ,这个特殊的token就是bos、eos。
unk(unknown):构建字典的时候使用的是语料库的中的词,有的时候,在输入时,语料库中是没有的,这种词叫未登录词,把他们当作unk处理。
用现有工具进行分词
前面介绍的分词方式非常简单,它至少有以下几个缺点:
- 标点符号通常可以提供语义信息,但是我们的方法直接将其丢弃了
- 类似“shouldn't", "doesn't"这样的词会被错误地处理
- 类似"Mr.", "Dr."这样的词会被错误地处理
通过引入更复杂的规则来解决这些问题,但是事实上,有一些现有的工具可以很好地进行分词,这里简单介绍其中的两个:spaCy和NLTK。
#eg spaCy
text = "Mr. Chen doesn't agree with my suggestion."
import spacy
nlp = spacy.load('en_core_web_sm')
doc = nlp(text)
print([token.text for token in doc])
out:
['Mr.', 'Chen', 'does', "n't", 'agree', 'with', 'my', 'suggestion', '.']
#eg NLTK
from nltk.tokenize import word_tokenize
from nltk import data
data.path.append('/**/**/data')
print(word_tokenize(text))
out:
['Mr.', 'Chen', 'does', "n't", 'agree', 'with', 'my', 'suggestion', '.']
0x02语言模型
什么是语言模型
模型指的是对事物的数学抽象,那么语言模型指的就是对语言现象的数学抽象。准确的讲,给定一个句子 w,语言模型就是计算句子的出现概率 p(w) 的模型,而统计的对象就是人工标注而成的语料库 。
假设构建如下的小型语料库:
商品 和 服务
商品 和服 物美价廉
服务 和 货币
每个句子出现的概率都是 1/3,这就是语言模型。然而 p(w) 的计算非常难:句子数量无穷无尽,无法枚举。即便是大型语料库,也只能“枚举”有限的数百万个句子。实际遇到的句子大部分都在语料库之外,意味着它们的概率都被当作0,这种现象被称为数据稀疏。


n元语法

当n较小时,n元语法往往并不准确。例如,在一元语法中,由三个词组成的句子“你走先”和“你先走”的概率是一样的。然而,当n较大时,n元语法需要计算并存储大量的词频和多词相邻频率。
n元语法缺陷:
- 数据稀疏,指的是长度越大的句子越难出现,可能统计不到频次,导致 p(wk|w1w2...wk−1)=0p(wk|w1w2...wk−1)=0,比如 p(商品 和 货币)=0。
- 计算代价大,k 越大,需要存储的 p 就越多,即便用上字典树索引,依然代价不菲。
参考:
二元语法与中文分词:
https://www.cnblogs.com/mantch/p/12266271.html
概念积累:

0x03循环神经网络基础
循环神经网络
循环神经网络(Recurrent Neural Network, RNN)是一类以序列(sequence)数据为输入,在序列的演进方向进行递归(recursion)且所有节点(循环单元)按链式连接的递归神经网络(recursive neural network)

循环神经网络的构造

参考:
循环神经网络(rnn)讲解:
https://blog.csdn.net/javaisnotgood/article/details/80671086
循环神经网络RNN介绍:
https://blog.csdn.net/wangkun1340378/article/details/79147432