文章目录
1. DNN
深度神经网络是稠密网络,Dense连接就是指全连接。有很多线性层对输入数据进行空间上的变换,又叫DNN。输入x1,x2,…,x8是数据样本的不同特征。
考虑这样一个场景:比如预测天天气,就需要知道之前几天的数据,每一天的数据都包含若个特征(温度、气压、雨天),如果你已知今天的温度、气压等特征去预测有没有雨,这是没有用的,需提前预测,需要之前若干天的数据作为输入。
假设现在取前3天,每一天有3个特征(温度、气压、雨天),如何预测第4天是否有雨?
第一种方法:把x1,x2,x3拼成有9个维度的长向量,然后去训练最后一天是否有雨。用全连接稠密网络进行预测,如果输入序列很长,而且每一个序列维度很高的话,对网络训练有很大挑战,因为稠密网络(全连接网络)实际上权重是最多的。
全连接网络的权重是最多的。
对比CNN网络和DNN网络的权重数量:
对于卷积层:比如输入通道是128个,输出通道是64个,如果用5x5的卷积,权重数就是 2564188=204800,卷积层的输入输出只与通道数和卷积核的大小有关,全连接层和变换之后的数据大小有关,比如3阶张量经过一系列的卷积变换还剩下4096个元素,4096我们很少直接降成1维或者10维,而是先降成1024维,全连接层的权重为4096*1024=4194304,所以相比起来,卷积层的权重并不多,而全连接层的权重较多。全连接层是在网络的全部参数中占大头的。
CNN在做卷积的时候,它的卷积核是共享的。所以它的权重数量比较少,它并不是图像上的每一个像素要和下一层的featureMap建立连接,权重数量就少。处理视频的时候,每一帧就少一张图像,我们需要把一组图像做成一个集合,如果用全连接网络的话,使用到的权重的数量就是一个天文数字,极大可能难以处理。
RNN是用来专门处理带有序列的数据,也会使用权重共享来减少权重的数量。它把x1,x2,x3看成是一个序列,不仅考虑x1,x2之间的连接关系,还考虑x1,x2的时间上的先后顺序(x2依赖于x1,x3依赖于x2),下一天的天气状况部分依赖于前一天的天气状况,RNN主要处理这种具有序列连接的数据。
数据是有先后顺序进行连接的,也就是当天数据会依赖之前数据。
哪些数据具有序列:股市、金融数据、自然语言(我爱北京天安门)
2. CNN
3. RNN
3.1 RNN的结构分析
RNN是循环神经网络。RNN其实是对线性层的复用。
RNN Cell本质是一个线性层(linear),把一个维度映射到另一个维度(比如把输入的3维向量xt变成输出5维向量ht)。 这个线性层与普通的线性层的区别是这个线性层是共享的。
右侧的结构是对左侧结构的展开。h0是一种先验知识,如果我们知道输出的维度,我们可以选择将其每一个维度都设置为0。也可以使用CNN+Fc的网络去生成先验知识,实现图像到文本的转换。
RNN相当于一个形成层,所以我们可以简单的用代码进行描述:
linear = Linear()
h = 0
for x in X:
h = linear(x,h)
相当于
h1 = linear(x1,h0)
h2 = linear(x2,h1)
h3 = linear(x3,h2)
......
RNN的具体计算过程:
输入xt先做线性变换,ht-1也是,xt的维度是input_size,ht-1的维度是hidden_size,输出ht的维度是hidden_size。我们需要先把xt的维度变成hidden_size,所以Wih应该是一个 hidden_sizeinput_size的矩阵,Wihxt得到一个 hidden_size的矩阵(就是维度为hidden_size的向量),bih是偏置。输入权重矩阵Whh是一个hidden_size*hidden_size的矩阵。
Whhht-1+bhh和Wihxt+bih都是维度为hidden_size的向量,然后两个向量相加,就把信息融合起来了,融合之后用tanh做激活,循环神经网络的激活函数用的是tanh(为什么呢?因为tanh的取值在-1到+1之间),算出结果得到这一层的隐藏层输出ht。
补充:Tanh的诞生比Sigmoid晚一些,sigmoid函数我们提到过有一个缺点就是输出不以0为中心,使得收敛变慢的问题。而Tanh则就是解决了这个问题。Tanh就是双曲正切函数。等于双曲余弦除双曲正弦。函数表达式和图像见下图。这个函数是一个奇函数。
完整的定义RNN:将RNN cell以循环的形式一个一个送进去,然后依次算出隐藏层的过程,我们称之为循环神经网络。
3.2 RNN的使用分析
构造RNN的方式:
- 自己写处理序列的循环
- 直接使用RNN
3.2.1 RNN_cell的实现
主要是确定输入的维度和隐层的维度。
如上图,RNN本质上还是一个线性层,要弄清楚纬度。代码如下:
# pytorch实现
cell = torch.nn.RNNcell(input_size=input_size,hidden_size=hidden_size)
hidden = cell(input,hidden) # h1 = cell(x1,h0)
举例使用RNN Cell:
输入有三个特征,每个特征是4维的。隐藏是一个2维的向量。
- batchSize表示批量大小
- seqLen=3表示每一个样本都有x1,x2,x3这些特征
- inputSize=4表示每一个特征都是4维的
- hiddenSize=2表示每一个隐藏层是2维的
可以发现,RNN相比之前的网络,多了一个序列的维度。
import torch
batch_size = 1
seq_len = 3
input_size = 4
hidden_size = 2
cell = torch.nn.RNNCell(input_size=input_size,hidden_size=hidden_size)
# (seq,batch,features)
dataset = torch.randn(seq_len,batch_size,input_size)
hidden = torch.zeros(batch_size,hidden_size)
for idx,input in enumerate(dataset):
print("="*20,idx,"="*20)
print("Input size:",input.shape)
hidden = cell(input,hidden)
print("Output size:",hidden.shape)
print(hidden)
3.2.2 RNN的实现
使用RNN:
直接使用torch.nn.RNN()需要知道input_size、hidden_size和num_layers(RNN有多少层,默认为1)。cell(inputs,hidden)中 inputs指包含整个输入序列(x1,x2,x3,…xN),hidden指h0。
用RNN不用自己写循环,它自动循环,所以输入的时候要把所有的序列都送进去,然后给定h0,然后我们就会得到所有的隐层输出以及最后一层的输出。
当RNN有多层,同样颜色的RNNCell是同一个,所以上图是有3个线性层(一个RNNCell是一个线性层)。这样就能解释为什么隐藏层h的维度需要numLayers参数,因为每一层都需要。
输出:
输出与输入的区别就在于input_size变成了hidden_size
import torch
input_size = 4
hidden_size = 2
seq_len = 3
batch_size = 1
num_layers = 1
cell = torch.nn.RNN(hidden_size=hidden_size,input_size=input_size,num_layers=num_layers)
#(seq_len,batchsize,inputsize)
inputs = torch.randn(seq_len,batch_size,input_size)
hidden = torch.zeros(num_layers,batch_size,hidden_size)
out,hidden = cell(inputs,hidden)
print("output size:",out.shape)
print("output:",out)
print("Hidden size:",hidden.shape)
print("hidden:",hidden)
输出:
初次之外,还有一个batch_first参数可以设置:
4. Example
4.1 Use RNN_cell
举例:seq到seq。训练一个模型:输入hello,输出ohlol。
RNN Cell 的输入是向量,第一步先把字符转成向量。
在NLP中,先根据字符构造一个词典(Dictionary),然后根据indeces转换成相应的one-hot向量。这里inputsize=4,因为输入有4个字符(e h l o)这相当于一个多分类问题,输出就是一个4维的向量,每一维代表是某一个字符的概率,接交叉熵就能输出概率了。
输入和输出所需要的结构:
4.1.1 准备数据
# 准备数据
idx2char = ['e','h','l','o']
x_data = [1,0,2,2,3] # hello
y_data = [3,1,2,3,2] # ohlol
one_hot_lookup = [[1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[0,0,0,1]] # ont_hot查询
x_one_hot = [one_hot_lookup[x] for x in x_data] # seq * input_Size
print(x_one_hot)
# (seqlen,batchsize,inputsize)
inputs = torch.tensor(x_one_hot).view(-1,batch_size,input_size)
# label (seqlen,1)
labels = torch.LongTensor(y_data).view(-1,1)
print(labels.shape)
print(labels)
4.1.2 准备模型
# 构造模型
class Model(torch.nn.Module):
def __init__(self,batch_size,input_size,hidden_size):
super(Model, self).__init__()
self.batch_size = batch_size
self.input_size = input_size
self.hidden_size = hidden_size
self.rnncell = torch.nn.RNNCell(input_size=input_size,hidden_size=hidden_size)
def forward(self,input,hidden):
hidden = self.rnncell(input,hidden)
return hidden
def init_hidden(self):
return torch.zeros(self.batch_size,self.hidden_size)
model = Model(input_size=input_size,hidden_size=hidden_size,batch_size=batch_size)
4.1.3 准备损失函数和优化器
# 损失函数和优化器
creation = torch.nn.CrossEntropyLoss(reduction="mean")
optimizer = torch.optim.SGD(model.parameters(),lr=0.1)
4.1.4 进行训练
# 训练
for epoch in range(15):
loss = 0
optimizer.zero_grad()
hidden = model.init_hidden() # 计算h0
print("Predicted string:",end="") #end=’ ‘意思是末尾不换行,加空格。
for input,label in zip(inputs,labels): # 依次取x1,x2,x3,x4,x5
# inputs : (seqlen,batchsize,inputsize)
hidden = model(input,hidden)
loss += creation(hidden,label) # loss这里不用item,因为序列的loss需要构造计算图
_,idx = hidden.max(dim=1)
print(idx2char[idx.item()],end="")
loss.backward()
optimizer.step()
print(",Epoch {}/15 loss={:.4f}".format(epoch+1,loss.item()))
输出结果:
补充知识:torch.Tensor和torch.tensor的区别
4.2 Use RNN
4.2.1 训练的改变
# 训练
for epoch in range(15):
optimizer.zero_grad()
outputs = model(inputs) # inputs是(seq,Batchsize,Inputsize) outputs是(seq,Batchsize,Hiddensize)
loss = creation(outputs,labels) # labels是(seq,batchsize,1)
loss.backward()
optimizer.step()
_,idx = outputs.max(dim=1)
idx = idx.data.numpy()
print("Predicted:",''.join([idx2char[x] for x in idx]),end='')
print(",Epoch {}/15 loss={:.3f}".format(epoch+1,loss.item()))
4.2.2 模型的改变
# 构造模型
class Model(torch.nn.Module):
def __init__(self,batch_size,input_size,hidden_size,num_layers = 1):
super(Model, self).__init__()
self.batch_size = batch_size
self.input_size = input_size
self.hidden_size = hidden_size
self.num_layers = num_layers
# input维度(batchsize,inputsize)
# hidden维度(batchsize,hiddensize)
self.rnn = torch.nn.RNN(input_size=self.input_size,hidden_size=self.hidden_size,num_layers=self.num_layers)
# def forward(self,input,hidden):
# hidden = self.rnncell(input,hidden)
# return hidden
def forward(self,input):
hidden = torch.zeros(self.num_layers,self.batch_size,self.hidden_size)
out,_ = self.rnn(input,hidden)
return out.view(-1,self.hidden_size) # 转变维2维矩阵,seq*batchsize*1 -》((seq*batchsize),1)
# def init_hidden(self):
# return torch.zeros(self.batch_size,self.hidden_size)
model = Model(input_size=input_size,hidden_size=hidden_size,batch_size=batch_size,num_layers=1)
4.2.3 数据的改变
# 准备数据
idx2char = ['e','h','l','o'] # 字典
x_data = [1,0,2,2,3] # hello
y_data = [3,1,2,3,2] # ohlol
one_hot_lookup = [[1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[0,0,0,1]] # ont_hot查询
x_one_hot = [one_hot_lookup[x] for x in x_data] # seq * input_Size
# print(x_one_hot)
# (seqlen,batchsize,inputsize)
inputs = torch.Tensor(x_one_hot).view(seq_len,batch_size,input_size)
# label (seqlen,1)
labels = torch.LongTensor(y_data)
# print(labels.shape)
# print(labels)
4.2.4 完整代码
import torch
# 使用RNN
input_size = 4
hidden_size = 4
batch_size = 1
num_layers = 1
seq_len = 5
# 准备数据
idx2char = ['e','h','l','o'] # 字典
x_data = [1,0,2,2,3] # hello
y_data = [3,1,2,3,2] # ohlol
one_hot_lookup = [[1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[0,0,0,1]] # ont_hot查询
x_one_hot = [one_hot_lookup[x] for x in x_data] # seq * input_Size
# print(x_one_hot)
# (seqlen,batchsize,inputsize)
inputs = torch.Tensor(x_one_hot).view(seq_len,batch_size,input_size)
# label (seqlen,1)
labels = torch.LongTensor(y_data)
# print(labels.shape)
# print(labels)
# 构造模型
class Model(torch.nn.Module):
def __init__(self,batch_size,input_size,hidden_size,num_layers = 1):
super(Model, self).__init__()
self.batch_size = batch_size
self.input_size = input_size
self.hidden_size = hidden_size
self.num_layers = num_layers
# input维度(batchsize,inputsize)
# hidden维度(batchsize,hiddensize)
self.rnn = torch.nn.RNN(input_size=self.input_size,hidden_size=self.hidden_size,num_layers=self.num_layers)
# def forward(self,input,hidden):
# hidden = self.rnncell(input,hidden)
# return hidden
def forward(self,input):
hidden = torch.zeros(self.num_layers,self.batch_size,self.hidden_size)
out,_ = self.rnn(input,hidden)
return out.view(-1,self.hidden_size) # 转变维2维矩阵,seq*batchsize*1 -》((seq*batchsize),1)
# def init_hidden(self):
# return torch.zeros(self.batch_size,self.hidden_size)
model = Model(input_size=input_size,hidden_size=hidden_size,batch_size=batch_size,num_layers=1)
# 损失函数和优化器
creation = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(),lr=0.05) # lr = 0.01学习的太慢
# 训练
for epoch in range(15):
optimizer.zero_grad()
outputs = model(inputs) # inputs是(seq,Batchsize,Inputsize) outputs是(seq,Batchsize,Hiddensize)
loss = creation(outputs,labels) # labels是(seq,batchsize,1)
loss.backward()
optimizer.step()
_,idx = outputs.max(dim=1)
idx = idx.data.numpy()
print("Predicted:",''.join([idx2char[x] for x in idx]),end='')
print(",Epoch {}/15 loss={:.3f}".format(epoch+1,loss.item()))
输出结果:
4.3 独热向量
4.3.1 one-hot缺点
缺点:
- 维度太高
- 向量稀疏
- 硬编码,不是学习出来的
embedding将高维离散的数据映射到加低维稠密的空间,就是常说的数据降维。在输入层和RNN层之中。
4.3.2 改进
Embedding:
输入是2,就去表里查找2的位置,输出向量。
取第2行的向量,我们可以通过矩阵乘法来获得。
将上述4x5行的矩阵进行转置变成5x4,然后乘以一个[0 0 1 0]就可以取出第2行的向量。
然后我们求反向传播导数的话,我们就可以通过矩阵乘法的导数来进行计算。
我们之前的RNN cell输出的隐藏是和分类的数量是一致的, 如果不一致的话,我们可以再接一个线性层。
torch.nn.Embedding:
第一个参数:独热向量是几维的。
输入是(seq,batchsize)输出是(seq,batchsize,embedding_dim)
torch.nn.Linear:
torch.nn.CrossEntropyLoss:
d1,d2…代表交叉熵可以加维度的,可以满足RNN的计算。
4.3.3 网络结构
网络结构:
添加嵌入层。
将输入的张量转变为嵌入曾稠密的张量表示。
batch_first设为true的话,那么batchsize维度在前,seqlen维度在后。
线性层,将hiddensize变成numclass。
最后返回成二维矩阵的形式,方便loss的计算。
4.3.4 完整代码
import torch
# 使用RNN 有嵌入层和线性层
num_class = 4 # 4个类别
input_size = 4 # 输入维度是4
hidden_size = 8 # 隐层是8个维度
embedding_size = 10 # 嵌入到10维空间
batch_size = 1
num_layers = 2 # 两层的RNN
seq_len = 5 # 序列长度是5
# 准备数据
idx2char = ['e','h','l','o'] # 字典
x_data = [[1,0,2,2,3]] # hello 维度(batch,seqlen)
y_data = [3,1,2,3,2] # ohlol 维度 (batch*seqlen)
# (batchsize,seqlen)
inputs = torch.LongTensor(x_data)
# label (batchsize*seqlen)
labels = torch.LongTensor(y_data)
# print(labels.shape)
# print(labels)
# 构造模型
class Model(torch.nn.Module):
def __init__(self):
super(Model, self).__init__()
self.emb = torch.nn.Embedding(input_size,embedding_size)
self.rnn = torch.nn.RNN(input_size=embedding_size,hidden_size=hidden_size,num_layers=num_layers,batch_first=True)
self.fc = torch.nn.Linear(hidden_size,num_class)
def forward(self,x):
hidden = torch.zeros(num_layers,x.size(0),hidden_size)
x = self.emb(x) # (batch,seqlen,embeddingsize)
x,_ = self.rnn(x,hidden)
x = self.fc(x)
return x.view(-1,num_class) # 转变维2维矩阵,seq*batchsize*numclass -》((seq*batchsize),numclass)
model = Model()
# 损失函数和优化器
creation = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(),lr=0.05) # lr = 0.01学习的太慢
# 训练
for epoch in range(15):
optimizer.zero_grad()
outputs = model(inputs) # inputs是(seq,Batchsize,Inputsize) outputs是(seq,Batchsize,Hiddensize)
loss = creation(outputs,labels) # labels是(seq,batchsize,1)
loss.backward()
optimizer.step()
_,idx = outputs.max(dim=1)
idx = idx.data.numpy()
print("Predicted:",''.join([idx2char[x] for x in idx]),end='')
print(",Epoch {}/15 loss={:.3f}".format(epoch+1,loss.item()))
输出结果:
5. Exercise
5.1 Use LSTM
torch.nn.LSTM():
多了上面的一条通道,实现了梯度传播的通道,能够避免梯度消失问题。
输入和输出:
相比RNN,LSTM性能好,但是时间复杂度肯定变高。
5.2 Use GRU
RNN和LSTM的折中方案:GRU
理解序列data的维度概念,理解循环过程权重共享的机制。
课后练习:
对比RNN
1.LSTM实现之前的模型
2.GRU实现之前的模型