写在前面:
作为一个程序媛,基本业务能力却很菜,想想不能这样下去了,决定好好学习,写点代码!!
寒假竟然长达2个月之久,机缘巧合,参加了伯禹教育举办的14天动手学习深度学习PyTorch版的训练营,于是就跟着各位dalao一起学习,大家都好厉害,深深感觉自己的水平不行呀,所以要加油啦~~ 希望这14天比之前的自己进步了就好~
半月阶段计划:
Python 模拟病毒传播 : 代码连接 https://github.com/helen9977/Virus_
[ ing ] PyTorch 入门 : 学习资料 https://pytorch.apachecn.org/docs/1.0/blitz_tensor_tutorial.html中文文档
[ ing ] 动手学习深度学习PyTorch版 PDF
[ ing ] 训练营学习手册: https://shimo.im/docs/9CvKXJxP3xqGHwgh/read
文本预处理
预处理通常包括四个步骤:
1.读入文本
2.分词 工具: spaCy NLTK
3.建立字典,将每个词映射到一个唯一的索引(index)
4.将文本从词的序列转换为索引的序列,方便输入模型
建立字典:
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 += ['', '', '', '']
else:
self.unk = 0
self.idx_to_token += ['']
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) # 返回一个字典,记录每个词的出现次数
count_corpus( ) : 统计词频,清洗滴词频.
use_special_tokens : 特殊token, unk :未登录token
idx_to_token: list , 索引到token的映射
token_to_idx: dict, token到索引的映射
分词:
import spacy
nlp = spacy.load('en_core_web_sm')
doc = nlp(text)
print([token.text for token in doc])
# ['Mr.', 'Chen', 'does', "n't", 'agree', 'with', 'my', 'suggestion', '.']
from nltk.tokenize import word_tokenize
from nltk import data
data.path.append('/home/kesci/input/nltk_data3784/nltk_data')
print(word_tokenize(text))
# ['Mr.', 'Chen', 'does', "n't", 'agree', 'with', 'my', 'suggestion', '.']
语言模型
n元语法
𝑛 元语法通过马尔可夫假设简化模型,马尔科夫假设是指一个词的出现只与前面 𝑛 个词相关,即 𝑛 阶马尔可夫链(Markov chain of order 𝑛 )
𝑛 元语法( 𝑛 -grams)它基于 𝑛−1 阶马尔可夫链的概率语言模型.当n=2,有
𝑃(𝑤1,𝑤2,𝑤3,𝑤4)
=𝑃(𝑤1)𝑃(𝑤2∣𝑤1)𝑃(𝑤3∣𝑤1,𝑤2)𝑃(𝑤4∣𝑤1,𝑤2,𝑤3)
=𝑃(𝑤1)𝑃(𝑤2∣𝑤1)𝑃(𝑤3∣𝑤2)𝑃(𝑤4∣𝑤3)
时序数据的采样
- 随机采样
在随机采样中,每个样本是原始序列上任意截取的一段序列,相邻的两个随机小批量在原始序列上的位置不一定相毗邻。
import torch
import random
def data_iter_random(corpus_indices, batch_size, num_steps, device=None):
# 减1是因为对于长度为n的序列,X最多只有包含其中的前n - 1个字符
num_examples = (len(corpus_indices) - 1) // num_steps # 下取整,得到不重叠情况下的样本个数
example_indices = [i * num_steps for i in range(num_examples)] # 每个样本的第一个字符在corpus_indices中的下标
random.shuffle(example_indices)
def _data(i):
# 返回从i开始的长为num_steps的序列
return corpus_indices[i: i + num_steps]
if device is None:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
for i in range(0, num_examples, batch_size):
# 每次选出batch_size个随机样本
batch_indices = example_indices[i: i + batch_size] # 当前batch的各个样本的首字符的下标
X = [_data(j) for j in batch_indices]
Y = [_data(j + 1) for j in batch_indices]
yield torch.tensor(X, device=device), torch.tensor(Y, device=device)
- 相邻采样
相邻的两个随机小批量在原始序列上的位置相毗邻。
if device is None:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
corpus_len = len(corpus_indices) // batch_size * batch_size # 保留下来的序列的长度
corpus_indices = corpus_indices[: corpus_len] # 仅保留前corpus_len个字符
indices = torch.tensor(corpus_indices, device=device)
indices = indices.view(batch_size, -1) # resize成(batch_size, )
batch_num = (indices.shape[1] - 1) // num_steps
for i in range(batch_num):
i = i * num_steps
X = indices[:, i: i + num_steps]
Y = indices[:, i + 1: i + num_steps + 1]
yield X, Y
这里记一下自己经常搞错的知识点: iteration, batch, batch_size, epoch
迭代是 batch 需要完成一个 epoch 的次数。在一个 epoch 中,batch 数和迭代数是相等的。
比如对于一个有 2000 个训练样本的数据集。将 2000 个样本分成大小为 500 的 batch,那么完成一个 epoch 需要 4 个 iteration。
循环神经网络:
概念
我们的目的是基于当前的输入与过去的输入序列,预测序列的下一个字符。
Ht的计算依赖于Ht-1和Xt.所以记录了之前的所有信息
梯度剪裁
深度神经网络训练的时候,采用的是反向传播方式,该方式背后其实是链式求导,计算每层梯度的时候会涉及一些连乘操作,因此如果网络过深,那么如果连乘的因子大部分小于1,最后乘积的结果可能趋于0,也就是梯度消失,后面的网络层的参数不发生变化,后面的层学不到东西,那么如果连乘的因子大部分大于1,最后乘积可能趋于无穷,这就是梯度爆炸.
- 解决梯度爆炸的方法
- 梯度裁剪之后的梯度小于或者等于原梯度
- 裁剪之后的梯度L2范数小于阈值θ
困惑度
- 困惑度用来评价语言模型的好坏
- 困惑度越低语言模型越好
- 有效模型的困惑度应该小于类别个数
隐藏状态
RNN 是包含循环的网络,解决了传统的神经网络不能捕捉序列化数据中动态信息这个问题。RNN可以保存一种上下文的状态,允许信息的持久化。
通过隐藏层节点周期性的循环连接,可以使得信息从当前步传递到下一步。广泛使用成熟算法LSTM与BRNN等,都会通过将隐藏层的状态参数传入下一次网络中再运算,实现时序信息的传递。
- 采用的采样方法不同会导致隐藏状态初始化方式发生变化
- 采用相邻采样仅在每个训练周期开始的时候初始化隐藏状态是因为相邻的两个批量在原始数据上是连续的
- 采用随机采样需要在每个小批量更新前初始化隐藏状态是因为样本不完整所以每个批量需要重新初始化隐藏状态
for epoch in range(num_epochs):
if not is_random_iter: # 如使用相邻采样,在epoch开始时初始化隐藏状态
state = init_rnn_state(batch_size, num_hiddens, device)
l_sum, n, start = 0.0, 0, time.time()
data_iter = data_iter_fn(corpus_indices, batch_size, num_steps, device)
for X, Y in data_iter:
if is_random_iter: # 如使用随机采样,在每个小批量更新前初始化隐藏状态
state = init_rnn_state(batch_size, num_hiddens, device)
else: # 否则需要使用detach函数从计算图分离隐藏状态
for s in state:
s.detach_()
# inputs是num_steps个形状为(batch_size, vocab_size)的矩阵
inputs = to_onehot(X, vocab_size)
# outputs有num_steps个形状为(batch_size, vocab_size)的矩阵
(outputs, state) = rnn(inputs, state, params)
# 拼接之后形状为(num_steps * batch_size, vocab_size)
outputs = torch.cat(outputs, dim=0)
# Y的形状是(batch_size, num_steps),转置后再变成形状为
# (num_steps * batch_size,)的向量,这样跟输出的行一一对应
y = torch.flatten(Y.T)
# 使用交叉熵损失计算平均分类误差
l = loss(outputs, y.long())
# 梯度清0
if params[0].grad is not None:
for param in params:
param.grad.data.zero_()
l.backward()
grad_clipping(params, clipping_theta, device) # 裁剪梯度
d2l.sgd(params, lr, 1) # 因为误差已经取过均值,梯度不用再做平均
l_sum += l.item() * y.shape[0]
n += y.shape[0]