深度学习-1

深度学习基础

介绍单层神经网络:线性回归和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)

你可能感兴趣的:(深度学习-1)