深度学习基础
介绍单层神经网络:线性回归和softmax回归
多层神经网络:多层感知机
1.线性回归
例如房价预测,特征为面积房龄,那么模型可以写作:
其中x1和x2是权重weight,b是偏差bias
损失函数选用MSE,即均方误差
由于难以产生解析解,所以我们需要一个优化算法进行迭代产生数值解,在这里我们使用小批量梯度下降作为优化算法(mini-batch stochastic gradient descent)
SGD
每次迭代过程中,均匀采样一个batch,计算出batch的平均损失关于模型参数的偏导数,用此结果乘以预先设定的一个正数,作为本次迭代的减小量。
其中,正数是学习率,是一种超参数
神经网络的表示
输入层的个数为2,输出层的个数为1,是一种单层神经网络,输出层又叫全连接层。
从0开始实现
首先应该先熟悉矢量运算:
import torch
a=torch.ones(10)
b=torch.ones(10)
c=a+b
生成随机数据
num_inputs=2
num_examples=1000
true_w=[2,-3.4]
true_b=4.2
features=torch.randn(num_examples,num_inputs,dtype=torch.float32)#x1 x2
labels=true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b#y
labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()),dtype=torch.float32)
导入数据
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
random.shuffle(indices) # 样本的读取顺序是随机的
for i in range(0, num_examples, batch_size):
j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)]) # 最后一次可能不足一个batch
yield features.index_select(0, j), labels.index_select(0, j)
初始化模型参数
w=torch.tensor(np.random.normal(0,0.01,(num_inputs,1)),dtype=torch.float32)
b = torch.zeros(1, dtype=torch.float32)
w.requires_grad_(requires_grad=True)
b.requires_grad_(requires_grad=True)
定义模型算法,损失函数,优化算法
def linreg(X,w,b):
return torch.mm(X,w)+b#矩阵相乘
def squared_loss(y_hat, y):#损失函数
return (y_hat - y.view(y_hat.size())) ** 2 / 2
def sgd(params, lr, batch_size):#优化算法
for param in params:
param.data -= lr * param.grad / batch_size
最后模型训练:
lr=0.03
num_epochs=3#训练周期,每个周期训练数据集中所有样本一次
net=linreg
loss=squared_loss
for epoch in range(num_epochs):
for X,y in data_iter(batch_size,features,labels):
l=loss(net(X,w,b),y).sum()
l.backward()#反向传播求梯度
sgd([w,b],lr,batch_size)#更新迭代参数
w.grad.data.zero_()
b.grad.data.zero_()#将梯度清零
train_l = loss(net(features, w, b), labels)
print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))
使用pytorch实现
#定义模型
class LinearNet(nn.Module):
def __init__(self,n_feature):
super(LinearNet,self).__init__()
self.linear=nn.Linear(n_feature,1)
def forward(self,x):#前向传播
y=self.linear(x)
return y
net = LinearNet(num_inputs)
print(net)
#若为多层,可以这样写
net = nn.Sequential()
net.add_module('linear', nn.Linear(num_inputs, 1))
print(net)
from torch.nn import init
import torch.optim as optim
#初始化参数
init.normal_(net[0].weight,mean=0.0,std=0.01)
init.constant_(net[0].bias,val=0.0)
#定义损失函数
loss = nn.MSELoss()
#定义优化算法
optimizer = optim.SGD(net.parameters(), lr=0.03)
num_epochs = 3
for epoch in range(1, num_epochs + 1):
for X, y in data_iter:
output = net(X)
l = loss(output, y.view(-1, 1))
optimizer.zero_grad() # 梯度清零,等价于net.zero_grad()
l.backward()
optimizer.step()
print('epoch %d, loss: %f' % (epoch, l.item()))
softmax回归
由于线性回归的输出没有范围,不适用于类别分类,所以引入了softmax回归,它使得所有预测标签的概率和为1。
其中
假设一个四像素的三分类问题:
设特征为
全连接的输出层为
预测的概率,即经过一个softmax层
计算表达式为:
交叉熵损失函数
由于对于类别分类均方差不太适用,考虑使用交叉熵作为测量两个概率分布差异的函数:
其中非0即1,为0到1间的概率值,所以就有:
它只关心预测正确的概率,交叉熵损失函数定义为:
其中为模型参数。
PyTorch实现
# import needed package
%matplotlib inline
from IPython import display
import matplotlib.pyplot as plt
from torch.nn import init
import torch
import torchvision
import torchvision.transforms as transforms
import time
import sys
sys.path.append("/home/kesci/input")
print(torch.__version__)
print(torchvision.__version__)
# 读取数据
batch_size = 256
num_workers = 4
train_data = torchvision.datasets.MNIST(
'./mnist', train=True, transform=torchvision.transforms.ToTensor(), download=True
)
test_data = torchvision.datasets.MNIST(
'./mnist', train=False, transform=torchvision.transforms.ToTensor()
)
train_iter = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True, num_workers=num_workers)
test_iter = torch.utils.data.DataLoader(test_data, batch_size=batch_size, shuffle=False, num_workers=num_workers)
num_inputs=784#28*28,共784个特征
num_outputs=10#10个类别
'''
class LinearNet(torch.nn.Module):
def __init__(self,num_inputs,num_outputs):
super(LinearNet,self).__init__()
self.linear=torch.nn.Linear(num_inputs,num_outputs)#线性模型,即全连接
def forward(self,x):
y=self.linear(x.view(x.shape[0],-1))
return y
'''
class FlattenLayer(torch.nn.Module):
def __init__(self):
super(FlattenLayer,self).__init__()
def forward(self,x):
return x.view(x.shape[0],-1)#将其按batch展开,因为是二维数据
net=torch.nn.Sequential()
net.add_module('flatten',FlattenLayer())
net.add_module('linear',torch.nn.Linear(num_inputs,num_outputs))
init.normal_(net[1].weight, mean=0, std=0.01)
init.constant_(net[1].bias, val=0)
loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.1)
def evaluate_accuracy(data_iter, net):
acc_sum, n = 0.0, 0
for X, y in data_iter:
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n
num_epochs = 10
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
for X, y in train_iter:
y_hat = net(X)
l = loss(y_hat, y).sum()
optimizer.zero_grad()#梯度清零
l.backward()#反向传播求梯度
optimizer.step()#前向传播
train_l_sum += l.item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
n += y.shape[0]
test_acc = evaluate_accuracy(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'
% (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))
3.多层感知机
多层神经网络包括输入层、输出层、隐藏层
由于隐藏层与输出层都为全连接层,向量运算表达式可为:
举例来说,若输入层批量大小为n,输入个数为d,即输入为nd,设隐藏层单元个数为h,则在隐藏层上则为nh,由于是全连接层,权重参数可为,
激活函数
可以看出,上述隐藏层只是输入层的映射,本质还是一个单层神经网络,所以这里引入激活函数,对输入层元素作非线性运算,再作为下一个全连接层的输入。
常用激活函数介绍:
ReLU
对于给定元素x,,即将负元素归零。
Sigmoid
将元素x的值映射到0与1之间:
它求导为:,越接近0导数值越大
tanh
将元素x的值映射到-1到1之间:
它的导数为:,也是一个二次函数形状,越接近0导数值越大。
激活函数的选择:一般使用通用的ReLU,分类问题一般使用sigmoid或其组合,并且层数较多时一般使用ReLU,因为计算量较小。
使用多层感知机解决mnist分类问题
import torch
from torch import nn
from torch.nn import init
import numpy as np
import torchvision
import torchvision.transforms as transforms
# 读取数据
batch_size = 256
num_workers = 4
train_data = torchvision.datasets.MNIST(
'./mnist', train=True, transform=torchvision.transforms.ToTensor(), download=True
)
test_data = torchvision.datasets.MNIST(
'./mnist', train=False, transform=torchvision.transforms.ToTensor()
)
train_iter = torch.utils.data.DataLoader(train_data, batch_size=batch_size, shuffle=True, num_workers=num_workers)
test_iter = torch.utils.data.DataLoader(test_data, batch_size=batch_size, shuffle=False, num_workers=num_workers)
class FlattenLayer(torch.nn.Module):
def __init__(self):
super(FlattenLayer,self).__init__()
def forward(self,x):
return x.view(x.shape[0],-1)#将其按batch展开
n_inputs,n_hiddens,n_outputs=784,256,10
net = nn.Sequential(
FlattenLayer(),
nn.Linear(num_inputs, num_hiddens),
nn.ReLU(),
nn.Linear(num_hiddens, num_outputs),
)
print(net)
for params in net.parameters():
init.normal_(params, mean=0, std=0.01)
optimizer=torch.optim.SGD(net.parameters(), lr=0.3)
loss=nn.CrossEntropyLoss()
def evaluate_accuracy(data_iter, net):
acc_sum, n = 0.0, 0
for X, y in data_iter:
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n
n_epochs=5
for epoch in range(n_epochs):
train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
for X,y in train_iter:
y_hat=net(X)
l=loss(y_hat,y).sum()
optimizer.zero_grad()
l.backward()#损失函数反向传播,更新梯度
optimizer.step()#前向传播
train_l_sum += l.item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().item()
n += y.shape[0]
test_acc = evaluate_accuracy(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'
% (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))
文本与自然语言处理入门
1.文本预处理
一般来说,先读入文本,将文本化为若干个词(token),转换为一个词序列。建立一个词的字典,将每个词映射到唯一的索引编号。现有的分词工具是spaCy和NLTK。
特殊token的含义:
:padding,对于一个batch矩阵来说,有的句子长有的句子短,我们需要加上一些空格使它们一样长
:begin of sentence:表示句子的开头
:end of sentence:表示句子的结束
:unknown,在语料库中没有的词,一般叫未登录词
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])
import nltk
tokens=nltk.word_tokenize(text)
tokens
输出:['Mr.', 'Chen', 'does', "n't", 'agree', 'with', 'my', 'suggestion', '.']
2.语言模型
对于一个长度为T的词的序列,评价该序列是否合理即评估该序列出现的概率,即,假设序列是按时间依次生成的,根据条件概率公式,有:
设训练数据集是一个大型文本语料库,每个词的词频可以近似为该词出现的概率,即:
在给定的情况下,的条件概率是:
由于当n过大时复杂度太大,于是n元语法通过马尔可夫链简化模型,
即一个词出现的概率只与前面出现的n个词有关,对于长度为4的序列
一元语法:
二元语法:
n元语法的缺陷:
1.参数空间过大
2.数据稀疏
时序数据采样
采用随机采样的方法,每次从数据里随机采样一个小批量,批量大小batch_size
是每个小批量的样本数,num_steps
是每个样本所包含的时间步数,在随机采样中,每个样本是原始序列中任意截取的一段时间序列,相邻两个随机小批量在原始序列中不一定相邻
数据读取:
jaychou=r"C:\Users\95390\Desktop\深度学习\Dive-into-DL-PyTorch-master\Dive-into-DL-PyTorch-master\data\jaychou_lyrics.txt\jaychou_lyrics.txt"
def load_data_jay_lyrics():
with open(jaychou,encoding="utf-8") as f:
corpus_chars = f.read()
corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')
corpus_chars = corpus_chars[0:10000]
idx_to_char = list(set(corpus_chars))
char_to_idx = dict([(char, i) for i, char in enumerate(idx_to_char)])
vocab_size = len(char_to_idx)
corpus_indices = [char_to_idx[char] for char in corpus_chars]
return corpus_indices, char_to_idx, idx_to_char, vocab_size
随机采样:
将数据分成batch_size组,每取一次数据,从每组中随机抽取一段序列。
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)
相邻采样:
如图,将一个序列分成三段,每种颜色代表一个batch
def data_iter_consecutive(corpus_indices, batch_size, num_steps, device=None):
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
3.循环神经网络
如图,是一个时间序列的预测,根据当前的输入与过去的序列,预测下一个字符,隐藏层的计算基于和
设是小批量输入,是隐藏层,那么:
其中有
由于的计算基于,是循环的,所以称为循环神经网络。最后输出层为:
剪裁梯度
循环神经网络中经常出现梯度衰减或者梯度爆炸,导致网络无法训练。所以我们把所有模型参数拼接成一个向量g,设剪裁的阈值时,剪裁后的梯度:
def grad_clipping(params, theta, device):
norm = torch.tensor([0.0], device=device)
for param in params:
norm += (param.grad.data ** 2).sum()
norm = norm.sqrt().item()
if norm > theta:
for param in params:
param.grad.data *= (theta / norm)
困惑度
在语言模型中,我们使用困惑度来评价一个模型,困惑度是对交叉熵损失函数做指数运算得到的值
当最佳情况:模型的预测概率为1,那么困惑度为1,如果预测概率为0,那么困惑度为正无穷
one-hot
就是将标签变为一个矩阵如2->[0,0,1]
def one_hot(x, n_class, dtype=torch.float32):
result = torch.zeros(x.shape[0], n_class, dtype=dtype, device=x.device) # shape: (n, n_class)
result.scatter_(1, x.long().view(-1, 1), 1) # result[i, x[i, 0]] = 1
return result
def to_onehot(X, n_class):
return [one_hot(X[:, i], n_class) for i in range(X.shape[1])]
PyTorch实现
在nn.RNN中,有几个构造函数参数:
input_size:输入的特征数目
hidden_size:隐藏层数目(状态数)
nonlinearity:'tanh'(default)或者'ReLU'
batch_first:If True, then the input and output tensors are provided as (batch_size, num_steps, input_size). Default: False
batch_first决定了输出的形状,默认是(num_steps, batch_size, input_size)
对于forward,参数为input和h_0,返回值为output和h_n
首先是predict函数,首先输入prefix个前缀,通过循环的预测,将下一次预测的序列作为输入继续预测,直到预测了num_chars个字符
def predict_rnn_pytorch(prefix, num_chars, model, vocab_size, device, idx_to_char,
char_to_idx):
state = None
output = [char_to_idx[prefix[0]]] # output记录prefix加上预测的num_chars个字符
for t in range(num_chars + len(prefix) - 1):
X = torch.tensor([output[-1]], device=device).view(1, 1)
(Y, state) = model(X, state) # 前向计算不需要传入模型参数
if t < len(prefix) - 1:
output.append(char_to_idx[prefix[t + 1]])
else:
output.append(Y.argmax(dim=1).item())
return ''.join([idx_to_char[i] for i in output])
取出周杰伦歌词
(corpus_indices, char_to_idx, idx_to_char, vocab_size) = load_data_jay_lyrics()
rnn_layer即rnn层,输入的大小为vocab_size,即语料库的大小
rnn_layer = nn.RNN(input_size=vocab_size, hidden_size=num_hiddens)
num_steps即每一个元素的步长,X为输入,开始时state(H)初始化为None
num_steps, batch_size = 35, 2
X = torch.rand(num_steps, batch_size, vocab_size)
state = None
RNN模型实现:
class RNNModel(nn.Module):
def __init__(self, rnn_layer, vocab_size):
super(RNNModel, self).__init__()
self.rnn = rnn_layer
self.hidden_size = rnn_layer.hidden_size * (2 if rnn_layer.bidirectional else 1)
self.vocab_size = vocab_size
self.dense = nn.Linear(self.hidden_size, vocab_size)
def forward(self, inputs, state):
# inputs.shape: (batch_size, num_steps)
X = to_onehot(inputs, vocab_size)
X = torch.stack(X) # X.shape: (num_steps, batch_size, vocab_size)
hiddens, state = self.rnn(X, state)
hiddens = hiddens.view(-1, hiddens.shape[-1]) # hiddens.shape: (num_steps * batch_size, hidden_size)
output = self.dense(hiddens)
return output, state
训练模型
model = RNNModel(rnn_layer, vocab_size).to(device)
def train_and_predict_rnn_pytorch(model, num_hiddens, vocab_size, device,
corpus_indices, idx_to_char, char_to_idx,
num_epochs, num_steps, lr, clipping_theta,
batch_size, pred_period, pred_len, prefixes):
loss = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
model.to(device)
for epoch in range(num_epochs):
l_sum, n, start = 0.0, 0, time.time()
data_iter = data_iter_consecutive(corpus_indices, batch_size, num_steps, device) # 相邻采样
state = None
for X, Y in data_iter:
if state is not None:
# 使用detach函数从计算图分离隐藏状态
if isinstance (state, tuple): # LSTM, state:(h, c)
state[0].detach_()
state[1].detach_()
else:
state.detach_()
(output, state) = model(X, state) # output.shape: (num_steps * batch_size, vocab_size)
y = torch.flatten(Y.T)
l = loss(output, y.long())
optimizer.zero_grad()
l.backward()
grad_clipping(model.parameters(), clipping_theta, device)
optimizer.step()
l_sum += l.item() * y.shape[0]
n += y.shape[0]
if (epoch + 1) % pred_period == 0:
print('epoch %d, perplexity %f, time %.2f sec' % (
epoch + 1, math.exp(l_sum / n), time.time() - start))
for prefix in prefixes:
print(' -', predict_rnn_pytorch(
prefix, pred_len, model, vocab_size, device, idx_to_char,
char_to_idx))
num_epochs, batch_size, lr, clipping_theta = 250, 32, 1e-3, 1e-2
pred_period, pred_len, prefixes = 50, 50, ['分开', '不分开']
train_and_predict_rnn_pytorch(model, num_hiddens, vocab_size, device,
corpus_indices, idx_to_char, char_to_idx,
num_epochs, num_steps, lr, clipping_theta,
batch_size, pred_period, pred_len, prefixes)
过拟合、欠拟合、梯度爆炸、梯度消失、循环神经网络进阶
1.模型选择,过拟合、欠拟合
训练误差指的是在训练集上表现的误差,泛化误差指的是在测试数据集上表现出的误差的期望。应该关注降低泛化误差。
K折交叉验证
将原始数据集分割成K个不重合的子数据集。每一轮,我们用K-1个数据集来训练模型,用1个数据集来验证模型。最后我们对K次的训练误差和验证误差求平均。
模型复杂度与误差的关系:
权重衰减
L2范数即欧式距离,权重衰减等价于L2范数正则化,通过损失函数添加惩罚项使模型参数较小,可以用来减少过拟合。
如图,l即为损失函数,后面的为惩罚项,当大于0时,权重平方和最小时,惩罚最小。权重衰减对模型参数的学习增加了限制,减小了过拟合。
L2范数:
def l2_penalty(w):
return (w**2).sum() / 2
高维线性回归实例:
num_epochs, loss = 100, torch.nn.MSELoss()
batch_size, lr = 1, 0.003
def fit(wd):
# 对权重参数衰减。权重名称一般是以weight结尾
net = nn.Linear(num_inputs, 1)
nn.init.normal_(net.weight, mean=0, std=1)
nn.init.normal_(net.bias, mean=0, std=1)
optimizer_w = torch.optim.SGD(params=[net.weight], lr=lr, weight_decay=wd) # 对权重参数衰减
optimizer_b = torch.optim.SGD(params=[net.bias], lr=lr) # 不对偏差参数衰减
for _ in range(num_epochs):
for X, y in train_iter:
l = loss(net(X), y).mean()
optimizer_w.zero_grad()
optimizer_b.zero_grad()
l.backward()
# 对两个optimizer实例分别调用step函数,从而分别更新权重和偏差
optimizer_w.step()
optimizer_b.step()
return net
丢弃法
设一个单隐藏层的多层感知机如图:
当对其使用丢弃法时,该隐藏单元有概率为p被丢弃,即有p的概率被清零,有1-p的概率会除以1-p做拉伸。所以丢弃法不改变输入的期望值。
一般直在训练模型中使用,不会在测试中使用。下图便是丢弃了h2和h5
PyTorch实现
net = nn.Sequential(
FlattenLayer(),
nn.Linear(num_inputs, num_hiddens1),
nn.ReLU(),
nn.Dropout(drop_prob1),
nn.Linear(num_hiddens1, num_hiddens2),
nn.ReLU(),
nn.Dropout(drop_prob2),
nn.Linear(num_hiddens2, 10)
)
for param in net.parameters():
nn.init.normal_(param, mean=0, std=0.01)