pytorch
全连接网络
mnist数据集
有验证集
class HandWritingNumberRecognize_Dataset(Dataset):
def __init__(self,datasetfold_path,dataset_type,transform=None,update_dataset=False):
# 这里添加数据集的初始化内容
#dataset_path = 'E:\桌面\dml\深度学习课程-实验1\深度学习课程-实验1\datas\dataset\\'
dataset_path = datasetfold_path
if update_dataset:
make_txt_file(dataset_path+dataset_type,dataset_type) # update datalist in dataset_path
# make_txt_file函数:写一个文件,用来对应图片路径和标签
self.transform = transforms.ToTensor()
self.sample_list = list()
self.dataset_type = dataset_type
f = open(dataset_path + self.dataset_type + '/datalist.txt')
lines = f.readlines()
for line in lines:
self.sample_list.append(line.strip()) # 这是重构之后的datalist文件。
f.close()
#pass
def __getitem__(self, index):
# 这里添加getitem函数的相关内容
item = self.sample_list[index]
img = Image.open(item.split(' _')[0])
if self.transform is not None:
img = self.transform(img)
if self.dataset_type == 'test' :
label = 0
else:
label = int(item.split(' _')[-1])
return img, label
#pass
def __len__(self):
# 这里添加len函数的相关内容
return len(self.sample_list)
#pass
这段代码可以看出,我们构建的class HandWritingNumberRecognize_Dataset,是继承了Dataset的子类。
Dataset是pytorch 给出的类,源代码如下:
class Dataset(object):
"""An abstract class representing a Dataset.
All other datasets should subclass it. All subclasses should override
``__len__``, that provides the size of the dataset, and ``__getitem__``,
supporting integer indexing in range from 0 to len(self) exclusive.
"""
def __getitem__(self, index):
raise NotImplementedError
def __len__(self):
raise NotImplementedError
def __add__(self, other):
return ConcatDataset([self, other])
我们要通过dataset类完成“数据和标签”的对应,具体的构建代码,需要先观察自己的数据集格式,保证能读到文件,并且对应上。
在本实验中,包括一下几个内容:
__getitem__就是获取样本对,模型直接通过这一函数获得一对样本对{x:y}
在这里,把图片由路径读出图片;把标签内容读到label里面
——这里需要讨论一下,对于本实验中的test数据集,不需要加载label,直接填充为0.
__len__是指数据集长度。
由源码可以看出,继承的时候一定要覆写这两个函数,不然会返回错误。
def make_txt_file(path,dataset_type):
labellist=list()
if dataset_type=='test':
files = os.listdir(path+'\images') # 读入文件夹
num_png = len(files) #统计test文件夹中图片的个数
f = open(path+'/datalist.txt','w') #写对应文件,这里用'w'方法,覆盖原文件
countimg=0
while countimg<num_png:
f.write(path+'\images\\'+dataset_type +'_'+str(countimg)+'.bmp\n')
countimg+=1
f.close()
else:
f = open(path + '\labels_'+dataset_type+'.txt')
lines = f.readlines()
for line in lines:
labellist.append(line.strip()) #把读到的纯标签写入列表
f.close()
f = open(path+'/datalist.txt','w') #写对应文件,这里用'w'方法,覆盖原文件
countimg=0
for i in labellist:
f.write(path+'\images\\'+dataset_type +'_'+str(countimg)+'.bmp'+' _'+i+'\n')
#写完的效果是“图片路径 _标签”
countimg+=1
f.close()
这个函数要观察数据集,本实验提供的数据集中,图片为一个文件夹,中图片名数字递增;train和validation有label文件,里面每一行为一个标签,和文件夹里面图片顺次对应。
这里用**os.listdir(path+’\images’)**来读文件夹
构造完之后,测试一下
刚开始测试时候,遇到了IndexError: list index out of range 错误,可能有两个原因:
检查代码,发现是读datalist时候,里面只有数字,没有图片路径。于是重写了def make_txt_file(path,dataset_type):函数,解决了。
if __name__ == '__main__':
ds = HandWritingNumberRecognize_Dataset('train',update_dataset=True)
print(ds.__len__())
img, gt = ds.__getitem__(100) # get the 34th sample
print(type(img))
print(gt)
再看一下自己写的makefile
在make_txt_file代码里面可以看到,我用的都是绝对路径
class HandWritingNumberRecognize_Network(torch.nn.Module):
def __init__(self,in_dim,out):
super(HandWritingNumberRecognize_Network, self).__init__()
self.in_dim=in_dim
# 此处添加网络的相关结构,下面的pass不必保留
self.layer = torch.nn.Linear(in_dim,out)
#self.layer2 = torch.nn.Linear(hidden,out)
#pass
def forward(self, input_data):
# 此处添加模型前馈函数的内容,return函数需自行修改
x = self.layer(input_data)
#x = self.layer2(x)
x = torch.nn.functional.softmax(x)
# x = self.layer3(x)
return x
在定义类时可见,我们继承的是torch.nn.Module类
这个super函数的意思是, 继承了父类(nn.module)的初始化设置, 这一步一定要写
继承 nn.Module 的神经网络模块在实现自己的 init 函数时,一定要先调用 super().init()
这是nn.module 的 init:
self.training = True # 控制 training/testing 状态
self._parameters = OrderedDict() # 在训练过程中会随着 BP 而更新的参数
self._buffers = OrderedDict() # 在训练过程中不会随着 BP 而更新的参数
self._non_persistent_buffers_set = set()
self._backward_hooks = OrderedDict() # Backward 完成后会被调用的 hook
self._forward_hooks = OrderedDict() # Forward 完成后会被调用的 hook
self._forward_pre_hooks = OrderedDict() # Forward 前会被调用的 hook
self._state_dict_hooks = OrderedDict() # 得到 state_dict 以后会被调用的 hook
self._load_state_dict_pre_hooks = OrderedDict() # load state_dict 前会被调用的 hook
self._modules = OrderedDict() # 子神经网络模块
这是torch.nn.Module的一个方法,类似的还有
class Module(object):
def __init__(self):
def forward(self, *input):
def add_module(self, name, module):
def cuda(self, device=None):
def cpu(self):
def __call__(self, *input, **kwargs):
def parameters(self, recurse=True):
def named_parameters(self, prefix='', recurse=True):
def children(self):
def named_children(self):
def modules(self):
def named_modules(self, memo=None, prefix=''):
def train(self, mode=True):
def eval(self):
def zero_grad(self):
def __repr__(self):
def __dir__(self):
'''
有一部分没有完全列出来
'''
有一些注意技巧:
根据实验报告要求,只用了一层全连接,也没用relu激活,在最后用了一层softmax
def train(epoch_num):
# 循环外可以自行添加必要内容
mode = True
model.train(mode=mode)
#print(data_loader_train)
for index,(images,true_labels) in enumerate(data_loader_train):
#这里用index和 enumerate,为了统计训练到了第几个样本,中间可以返回过程值
optimizer.zero_grad()
output = model(images.reshape(-1,784))
#这里不能用结果数字,需要用原来的概率和标签的独热编码比较。
#保留非最大值的信息训练效果好
one_hot = torch.zeros(1, 10).long()
one_hot.scatter_(dim=1,index=true_labels.unsqueeze(dim=1),src=torch.ones(1, 10).long())
loss = loss_function(output.float(), one_hot.float())#得到损失函数
loss.backward() # 反向传播训练参数
optimizer.step()
# 必要的时候可以添加损失函数值的信息,即训练到现在的平均损失或最后一次的损失,下面两行不必保留
if index % 3000 == 0:
print(index, loss.item()) # 获取损失
#print(epoch_num, images, true_labels)
# pass
这里损失函数计算的时候,要用两个tensor计算,所以需要把label转为单热点的,和输出的概率向量计算。
同时,因为选用的loss时mse,需要转为float型计算
def validation():
# 验证函数,任务是在训练经过一定的轮数之后,对验证集中的数据进行预测并与真实结果进行比对,生成当前模型在验证集上的准确率
correct = 0
total = 0
accuracy = 0
with torch.no_grad(): # 该函数的意义需在实验报告中写明
for data in data_loader_val:
images, true_labels = data
# 在这一部分撰写验证的内容,下面两行不必保留
output = model(images.reshape(-1,784))
pred = output.data.max(dim=-1)[-1] # max(dim=-1):行角度找最大值,dim=0:列最大值,返回下标
#在这里通过返回下标将概率转为数字结果,验证函数中用数字结果和标签对比,统计正确率
if pred.equal(true_labels):
correct+=1
total+=1
#print(images, true_labels)
#pass
accuracy=correct/total
print("验证集数据总量:", total, "预测正确的数量:", correct)
print("当前模型在验证集上的准确率为:", accuracy)
torch.no_grad()
是一个上下文管理器,被该语句 wrap 起来的部分将不会track 梯度。
禁用梯度,仅在局部地区有效,将requires_grad改为false,用以节约计算资源。
optimizer.zero_grad()清除了优化器中所有 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OZXCrRQJ-1647959427155)(https://www.zhihu.com/equation?tex=x)] 的 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xwQgzHLK-1647959427156)(https://www.zhihu.com/equation?tex=x.grad)] ,在每次loss.backward()之前,不要忘记使用,否则之前的梯度将会累积,这通常不是我们所期望的( 也不排除也有人需要利用这个功能)。
同样还可以用 torch.set_grad_enabled()来实现不计算梯度。
例如:
def eval():
torch.set_grad_enabled(False)
... # your test code
torch.set_grad_enabled(True)
引用一条评论,体会一下这个函数作用:
在测试函数前加了装饰器,解决了cuda out of memory 感谢!!
这里计算正确率用的是对比结果数字。
所以用返回下标的方式,将概率向量中最大的下标返回成结果。
def alltest(result_path):
# 测试函数,需要完成的任务有:根据测试数据集中的数据,逐个对其进行预测,生成预测值。
loss_list = []
acc_list = []
with torch.no_grad():
#f = open('E:\桌面\dml\深度学习课程-实验1\\result.txt','w') #写对应文件,这里用'w'方法,覆盖原文件
f = open(result_path,'w')
for data in data_loader_test:
images,label= data
output = model(images.reshape(-1,784))
pred = output.data.max(dim=-1)[-1] # max(dim=-1):行角度找最大值,dim=0:列最大值,返回下标
f.write(str(pred.item())+'\n')#将结果写入文档
f.close()
# 将结果按顺序写入txt文件中,下面一行不必保留
pass
这里根据本次实验的要求,将test文件夹里面的图片通过网络之后的结果写入文件。
这里将路径写为参数,方便之后整理文件夹结构
if __name__ == "__main__":
#先用绝对路径,调整文件夹结构之后,改为相对路径,方便迁移。
datasetfold_path = 'E:\桌面\dml\深度学习课程-实验1\深度学习课程-实验1\datas\dataset\\'
# 构建数据集,参数和值需自行查阅相关资料补充。
dataset_train = HandWritingNumberRecognize_Dataset(datasetfold_path,'train',transform=True, update_dataset=True)
dataset_val = HandWritingNumberRecognize_Dataset(datasetfold_path,'val',transform=True,update_dataset=True)
dataset_test = HandWritingNumberRecognize_Dataset(datasetfold_path,'test',transform=True,update_dataset=True)
# 构建数据加载器,参数和值需自行完善。
data_loader_train = DataLoader(dataset=dataset_train)
data_loader_val = DataLoader(dataset=dataset_val)
data_loader_test = DataLoader(dataset=dataset_test)
#print(data_loader_train)
# 初始化模型对象,可以对其传入相关参数
model = HandWritingNumberRecognize_Network(28*28*1,10)
# 损失函数设置
loss_function = torch.nn.MSELoss() # torch.nn中的损失函数进行挑选,并进行参数设置
# 优化器设置
optimizer =torch.optim.Adam(model.parameters(), lr = 0.0001) # torch.optim中的优化器进行挑选,并进行参数设置
max_epoch = 1 # 自行设置训练轮数
num_val = 1 # 经过多少轮进行验证
# 然后开始进行训练
for epoch in range(max_epoch):
train(epoch)
# 在训练数轮之后开始进行验证评估
if epoch % num_val == 0:
validation()
# 自行完善测试函数,并通过该函数生成测试结果
result_path = 'E:\桌面\dml\深度学习课程-实验1\\result.txt'
alltest(result_path)
这里的损失函数先选择的是MSEloss
优化器用的Adam
训练轮数和验证轮数都是先设定这样,自己电脑cpu跑一轮太慢了
params(iterable):可用于迭代优化的参数或者定义参数组的dicts。
lr (float, optional) :学习率(默认: 1e-3)
betas (Tuple[float, float], optional):用于计算梯度的平均和平方的系数(默认: (0.9, 0.999))
eps (float, optional):为了提高数值稳定性而添加到分母的一个项(默认: 1e-8)
weight_decay (float, optional):权重衰减(如L2惩罚)(默认: 0)
本文的网络层数只有一层,不涉及到中间层层数和神经元个数
激活函数也定位softmax了
超参数有:优化器的选择(Adam的lr需要调)、损失函数的选择,训练的轮数
没啥经验,瞎调