我是一个非计算机专业的学生,我的毕设需要用ConvLSTM和ConvGRU对研究区域的山洪进行时空序列预测,在跑实际的数据前,我想先用MovingMNIST数据集跑一遍模型,但出现了问题,每个实例都预测不出,我的进行过程如下:
首先,我按点击率最高的基于pytorch的ConvLSTM网络搭建搭建了ConvLSTM(请麻烦见我代码)。
接着将movingmnist数据集载入,未划分训练集、验证集、测试集(只想先看方法对否)。希望模型输入前十个序列,预测后十个序列,故将数据输入和数据标签分别改为每条数据的前十个序列和后十个序列。
由于我想先观察模型是否有良好的学习和输出,所以一开始模型我设置的很简单,仅为[64,10],第二层为10对应输出的10个序列,虽然很简单(其它参数请麻烦见我后续完整代码),但是在模型训练学习后,按道理来说应该至少预测会有点形状,但是预测的结果却什么都没有,后10个序列一片空白(见图片)。
我尝试过将模型变复杂,但却总是会out of memory,我的电脑gpu为NVIDIA GeForce GTX 1650,专用内存4G,我想先用这台电脑检验我的网络方法是否正确,后续会用更好的设备进行实际研究问题的训练。不过我还想问一下,我这样的设备为什么很简单的神经网络都会out of memory呀,是我搭建出了问题么还是确实是设备太拉跨了。
所以我的问题是,我的convlstm为什么预测出来的全是黑的,如果是模糊的我可以认为是训练次数不够,网络结构太简单。但全是黑的,我尝试了许多次,仍是这样,我不知道问题出在了哪里。
我猜想我的问题出在convlstm的输出上,我取的是最后一层的最后一个单元h为输出,我认为这就是convlstm的输出,但不确定。或许又是其它什么问题。
希望有人能够帮助我,我已经困在这里很久了,谢谢!
如果我的问题没有表述清楚,请评论我,谢谢!
我的完整代码如下,如果您有时间的话看一看我代码中的问题吧:
import os
import numpy as np
import torch
import matplotlib
import torch.utils.data as Data
from torch.utils.data import DataLoader
import torch.optim as optim
os.environ['KMP_DUPLICATE_LIB_OK']='True'
device = torch.device('cuda' if torch.cuda.is_available else 'cpu')
#cuda是否可用;
torch.cuda.is_available()
import torch.nn as nn
"""
定义ConvLSTM每个时间序列点的模型单元,及其计算。
"""
class ConvLSTMCell(nn.Module):
def __init__(self, input_dim, hidden_dim, kernel_size, bias):
"""
单元输入参数如下:
input_dim: 输入张量对应的通道数,对于彩图为3,灰图为1。
hidden_dim: 隐藏状态的神经单元个数,也就是隐藏层的节点数,应该可以按计算需要“随意”设置。
kernel_size: (int, int),卷积核,并且卷积核通常都需要为奇数。
bias: bool,单元计算时,是否加偏置,通常都要加,也就是True。
"""
super(ConvLSTMCell, self).__init__() #self:实例化对象,__init__()定义时该函数就自动运行,
#super()是实例self把ConvLSTMCell的父类nn.Modele的__init__()里的东西传到自己的__init__()里
#总之,这句是搭建神经网络结构必不可少的。
self.input_dim = input_dim
self.hidden_dim = hidden_dim
self.kernel_size = kernel_size
self.padding = kernel_size[0] // 2, kernel_size[1] // 2 #//表示除法后取整数,为使池化后图片依然对称,故这样操作。
self.bias = bias
"""
nn.Conv2D(in_channels,out_channels,kernel_size,stride,padding,dilation=1,groups=1,bias)
二维的卷积神经网络
"""
self.conv = nn.Conv2d(in_channels=self.input_dim + self.hidden_dim, #每个单元的输入为上个单元的h和这个单元的x,
#所以h和x要连接在一起,在x的通道数上与h的维度上相连。
out_channels=4 * self.hidden_dim, #输入门,遗忘门,输出门,激活门是LSTM的体现,
#每个门的维度和隐藏层维度一样,这样才便于进行+和*的操作
#输出了四个门,连接在一起,后面会想办法把门的输出单独分开,只要想要的。
kernel_size=self.kernel_size,
padding=self.padding,
bias=self.bias)
def forward(self, input_tensor, cur_state):
"""
input_tensor:此时是四维张量,未考虑len_seq,[batch_size,channels,h,w],[b,c,h,w]。
cur_state:每个时间点单元内,包含两个状态张量:h和c。
"""
h_cur, c_cur = cur_state #h_cur的size为[batch_size,hidden_dim,height,width],c_cur的size相同,也就是h和c的size与input_tensor相同
combined = torch.cat([input_tensor, h_cur], dim=1) #把input_tensor与状态张量h,沿input_tensor通道维度(h的节点个数),串联。
#combined:[batch_size,input_dim+hidden_dim,height,weight]
combined_conv = self.conv(combined) #Conv2d的输入,[batch_size,channels,height,width]
#Conv2d的输出,[batch_size,output_dim,height,width],这里output_dim=out_channels=4 * self.hidden_dim
cc_i, cc_f, cc_o, cc_g = torch.split(combined_conv, self.hidden_dim, dim=1) #将conv的输出combined_conv([batch_size,output_dim,height,width])
#分成output_dim这个维度去分块,每个块包含hidden_dim个节点信息
#四个块分别对于i,f,o,g四道门,每道门的size为[b,hidden_dim,h,w]
i = torch.sigmoid(cc_i) # 输入门
f = torch.sigmoid(cc_f) # 遗忘门
o = torch.sigmoid(cc_o) # 输出门
g = torch.tanh(cc_g) #激活门
c_next = f * c_cur + i * g #主线,遗忘门选择遗忘的+被激活一次的输入,更新长期记忆。
h_next = o * torch.tanh(c_next) #短期记忆,通过主线的激活和输出门后,更新短期记忆(即每个单元的输出)。
return h_next, c_next #输出当前时间点输出给下一个单元的,两个状态张量。
def init_hidden(self, batch_size, image_size):
"""
初始状态张量的定义,也就是说定义还未开始时输入给单元的h和c。
"""
height, width = image_size
init_h = torch.zeros(batch_size, self.hidden_dim, height, width, device=self.conv.weight.device) #初始输入0张量
init_c = torch.zeros(batch_size, self.hidden_dim, height, width, device=self.conv.weight.device) #[b,hidden_dim,h,w]
#self.conv.weight.device表示创建tensor存放的设备
#和conv2d进行的设备相同
return (init_h,init_c)
"""
定义整个ConvLSTM按序列和按层数的结构和计算。
输入介绍:
五维数据,[batch_size,len_seq,channels,height,width] or [l,b,c,h,w]。
输出介绍:
输出两个列表:layer_output_list和last_state_list。
列表0:layer_output_list--单层列表,每个元素表示一层LSTM层的输出h状态,每个元素的size=[b,l,hidden_dim,h,w]。
列表1:last_state_list--双层列表,每个元素是一个二元列表[h,c],表示每一层的最后一个时间单元的输出状态[h,c],
h.size=c.size=[b,hidden_dim,h,w]
使用示例:
>> x = torch.rand((64, 20, 1, 64, 64))
>> convlstm = ConvLSTM(1, 30, (3,3), 1, True, True, False)
>> _,last_states = convlstm(x)
>> h = last_states[0][0] #第一个0表示要第1层的列表,第二个0表示要h的张量。
"""
class ConvLSTM(nn.Module):
"""
输入参数如下:
input_dim:输入张量对应的通道数,对于彩图为3,灰图为1。
hidden_dim:h,c两个状态张量的节点数,当多层的时候,可以是一个列表,表示每一层中状态张量的节点数。
kernel_size:卷积核的尺寸,默认所有层的卷积核尺寸都是一样的,也可以设定不同的lstm层的卷积核尺寸不同。
num_layers:lstm的层数,需要与len(hidden_dim)相等(当hidden_dim为列表时)。
batch_first:dimension 0位置是否是batch,是则True。
bias:是否加偏置,通常都要加,也就是True。
return_all_layers:是否返回所有lstm层的h状态。
"""
def __init__(self, input_dim, hidden_dim, kernel_size, num_layers,
batch_first=True, bias=True, return_all_layers=False):
super(ConvLSTM, self).__init__()
self._check_kernel_size_consistency(kernel_size) #后面def了的,检查卷积核是不是列表或元组。
kernel_size = self._extend_for_multilayer(kernel_size, num_layers) # 如果为多层,将卷积核以列表的形式分入多层,每层卷积核相同。
hidden_dim = self._extend_for_multilayer(hidden_dim, num_layers) # 如果为多层,将隐藏节点数以列表的形式分入多层,每层卷积核相同。
if not len(kernel_size) == len(hidden_dim) == num_layers: # 判断卷积层数和LSTM层数的一致性,若不同,则报错。
raise ValueError('Inconsistent list length.')
self.input_dim = input_dim
self.hidden_dim = hidden_dim
self.kernel_size = kernel_size
self.num_layers = num_layers
self.batch_first = batch_first
self.bias = bias
self.return_all_layers = return_all_layers #一般都为False。
cell_list = [] #每个ConvLSTMCell会存入该列表中。
for i in range(0, self.num_layers): # 当LSTM为多层,每一层的单元输入。
if i==0:
cur_input_dim = self.input_dim #一层的时候,单元输入就为input_dim,多层的时候,单元第一层输入为input_dim。
else:
cur_input_dim = self.hidden_dim[i - 1] #多层的时候,单元输入为对应的,前一层的隐藏层节点情况。
cell_list.append(ConvLSTMCell(input_dim=cur_input_dim, #输入,或前一层细胞的输出
hidden_dim=self.hidden_dim[i], #当前层细胞的输出
kernel_size=self.kernel_size[i],
bias=self.bias)) #层连接
self.cell_list = nn.ModuleList(cell_list) # 把定义的LSTM层串联成网络模型,ModuleList中模型可以自动更新参数。
def forward(self, input_tensor, hidden_state=None):
"""
input_tensor: 5D张量,[l, b, c, h, w] 或者 [b, l, c, h, w]
hidden_state: 第一次输入为None,
Returns:last_state_list, layer_output
"""
if not self.batch_first:
input_tensor = input_tensor.permute(1, 0, 2, 3, 4) # (t, b, c, h, w) -> (b, t, c, h, w)
if hidden_state is not None:
raise NotImplementedError()
else:
b, _, _, h, w = input_tensor.size() # 自动获取 b,h,w信息。
hidden_state = self._init_hidden(batch_size=b,image_size=(h, w))
#input_tensor.size = [b,l,c,h,w]
layer_output_list = [] #每层输出,size[b,l,hidden_size,h,w]
last_state_list = [] #最后一个stamp的输出状态的[h,c]
seq_len = input_tensor.size(1) #根据输入张量获取lstm的长度。
cur_layer_input = input_tensor #主线记忆的第一次输入为input_tensor。
for layer_idx in range(self.num_layers): #逐层计算。
h, c = hidden_state[layer_idx] #获取每一层的短期和主线记忆的初始输入状态。
output_inner = []
for t in range(seq_len): #时间序列里逐个计算,然后更新。
h, c = self.cell_list[layer_idx](input_tensor=cur_layer_input[:, t, :, :, :],cur_state=[h, c]) #更新h和c
#此处h.size = [b,hidden_dim,h,w]
output_inner.append(h) #第layer_idx层的第t个stamp的输出状态。
#完成时间序列的计算
layer_output = torch.stack(output_inner, dim=1) #将h连接变为5D,dim=1表示变为[b,l,c,h,w]。
cur_layer_input = layer_output #准备第layer_idx+1层的输入张量,其实就是上一层的所有stamp的输出状态。
layer_output_list.append(layer_output) #当前层(第layer_idx层)的所有timestamp的h状态的串联后,分层存入列表中。
last_state_list.append([h, c]) #当前层(第layer_idx层)的最后一个stamp的输出状态的[h,c],存入列表中。
if not self.return_all_layers: #当不返回所有层时
layer_output_list = layer_output_list[-1:] #只取最后一层的所有timestamp的h状态。
last_state_list = last_state_list[-1:] #只取最后一层的最后的stamp的输出状态[h,c]。
return layer_output_list, last_state_list
def _init_hidden(self, batch_size, image_size):
"""
所有lstm层的第一个时间点单元的输入状态。
"""
init_states = []
for i in range(self.num_layers):
init_states.append(self.cell_list[i].init_hidden(batch_size, image_size)) #每层初始单元,输入h和c,存为1个列表。
return init_states
@staticmethod #静态方法,不需要访问任何实例和属性,纯粹地通过传入参数并返回数据的功能性方法。
def _check_kernel_size_consistency(kernel_size):
"""
检测输入的kernel_size是否符合要求,要求kernel_size的格式是list或tuple
"""
if not (isinstance(kernel_size, tuple) or
(isinstance(kernel_size, list) and all([isinstance(elem, tuple) for elem in kernel_size]))):
raise ValueError('`kernel_size` must be tuple or list of tuples')
@staticmethod
def _extend_for_multilayer(param, num_layers):
"""
扩展到LSTM多层的情况
"""
if not isinstance(param, list):
param = [param] * num_layers
return param
def MNISTdataLoader(path):
# MovingMNIST数据集的shape:(20,10000,64,64)
# 读取后,需要S B H W --> B S H W
data = np.load(path)
data_trans = data.transpose(1, 0, 2, 3)
return data_trans #此时为 B S H W,转化为:(10000,20,64,64)
class convlstm_dataset(Data.Dataset):
def __init__(self, data0, data1):
# 将数据转化为tensor
data_in = torch.as_tensor(data0).float().view(-1,10,1,64,64)
data_target = torch.as_tensor(data1).float().view(-1,10,1,64,64)
# 装载数据和标签
self.data_in = data_in
self.data_target = data_target
def __len__(self):
# 返回长度
return self.data_in.shape[0]
def __getitem__(self, seq_id):
data_in = self.data_in[seq_id] #默认[]里为dim=0
data_target = self.data_target[seq_id]
return data_in, data_target
mnistdata = MNISTdataLoader("./mnist_test_seq.npy")
data_all = torch.tensor(mnistdata) #4D tensor:(10000,20,64,64)
data_all =data_all.view(-1,20,1,64,64) #5D tensor:(10000,20,1,64,64)
data_use = convlstm_dataset(data_all[:,:10,:,:,:],data_all[:,10:,:,:,:] ) #用前250个数据,前10张图像为输入,后10张图像为输出。
#data_test = convlstm_dataset(data_all[800:1000,:10,:,:,:],data_all[800:1000,10:,:,:,:] )
# 批次大小
batch_size = 16 #每一次训练传入数据个数
# 装载训练集
train_loader = DataLoader(dataset=data_use,
batch_size=batch_size,
shuffle=True) #shuffle=True用于打乱数据集。
# 装载测试集
test_loader = DataLoader(dataset=data_use,
batch_size=batch_size,
shuffle=True)
def train():
model.train() #模型通过 model.train() 将 self.training = True
for i, data in enumerate(train_loader): #enumerate()表示给每条数据加一个序列标签i。
# 获得数据和对应的标签
data_in, data_target = data #data_target(batch_size,10,1,64,64)
data_in = data_in.to(device)
data_target = data_target.to(device)
# 获得模型预测结果
_, last_states = model(data_in) #每一层最后一个单元的输出h,这个h才是学习了所有序列的结晶。
out =last_states[0][0] #out(batch_size,hidden_dim,64,64)
data_target =data_target.view(-1,10,64,64)
# 代价函数
loss = mse_loss(out,data_target)
# 梯度清0
optimizer.zero_grad()
# 计算梯度
loss.backward()
# 修改权值
optimizer.step()
def test():
model.eval() #模型通过 model.eval() 将 self.training = False
total_loss = 0
for i, data in enumerate(test_loader):
# 获得数据和对应的标签
data_in, data_target = data #data_target(batch_size,10,1,64,64)
data_in = data_in.to(device)
data_target = data_target.to(device)
# 获得模型预测结果
with torch.no_grad():
_, last_states = model(data_in)
out =last_states[0][0] #out(batch_size,hidden_dim,64,64)
data_target =data_target.view(-1,10,64,64)
# 代价函数
loss = mse_loss(out,data_target)
total_loss += loss
print("Test loss: {0}".format(total_loss.item()))
total_loss = 0
for i, data in enumerate(train_loader):
# 获得数据和对应的标签
data_in, data_target = data #data_target(batch_size,10,1,64,64)
data_in = data_in.to(device)
data_target = data_target.to(device)
# 获得模型预测结果
with torch.no_grad():
_, last_states = model(data_in)
out =last_states[0][0] #out(batch_size,hidden_dim,64,64)
data_target =data_target.view(-1,10,64,64)
# 代价函数
loss = mse_loss(out,data_target)
# 误差
loss = mse_loss(out,data_target)
total_loss += loss
print("Train loss: {0}".format(loss.item()))
# 定义模型
#model = ConvLSTM(1, 128,(5,5), 1, True, True, False).to(device)
model = ConvLSTM(1, [64,10],[(5,5),(5,5)], 2, True, True, False).to(device) #多层时gpu内存不够。
# 定义代价函数
mse_loss = nn.MSELoss()
# 定义优化器
optimizer = optim.Adam(model.parameters(), lr=0.005)
import time
import gc
time_strat = time.time()
for epoch in range(0, 15):
print('epoch:',epoch)
train()
#gc.collect() #垃圾回收
#torch.cuda.empty_cache() #清内存
test()
time_end = time.time()
print("迭代时间: {0}".format(time_end-time_strat))
import random
import torchvision
from matplotlib import pyplot as plt
model.eval()
seq_id = random.randint(0,len(data_use)-1)
print(seq_id)
# 此数据输入和对应结果
data_id = data_use[seq_id]
data_in_id,data_target_id = data_id
data_in_id = data_in_id.view(-1,10,1,64,64).to(device)
data_target_id = data_target_id.view(-1,10,64,64).to(device)
# 获得此条数据模型的预测结果
_, last_states = model(data_in_id)
out_id =last_states[0][0] #out(1,10,64,64)
loss = mse_loss(out_id, data_target_id)
print(loss)
# 可视化
data_in_id = data_in_id.view(10,-1,64,64)
data_target_id = data_target_id.view(10,-1,64,64)
out_id = out_id.view(10,-1,64,64)
data_real = torch.cat((data_in_id,data_target_id),0)
data_predict = torch.cat((data_in_id,out_id),0)
img_real = torchvision.utils.make_grid(data_real,nrow=10).detach().cpu()
img_real = img_real.numpy().transpose(1, 2, 0)
img_predict = torchvision.utils.make_grid(data_predict,nrow=10).detach().cpu()
img_predict = img_predict.numpy().transpose(1, 2, 0)
plt.imshow(img_real.astype("uint8"))
plt.show()
plt.imshow(img_predict.astype("uint8"))
plt.show()