赞
踩
本文主要是学习记录,在小土堆Pytorch课程学习后第一次写,对于MLP与CNN的相关知识还不够了解,后续对这块学习时再进行补充。这里特别感谢 小土堆Pytorch课程、同济子豪兄的MNIST课程。
MNIST 的全称是 Modified National Institute of Standards and Technology database。
MNIST数据集是一个常用的手写数字识别数据集,包含了大约 60000 张训练集图片和 10000 张测试集图片,每张图片都是 28 像素 * 28 像素的灰度图像。这些图像都经过了预处理和标准化,使得每张图像都被表示为一个行向量,其中每个元素的值都在 0 到 1 之间。MNIST数据集中的每个图像都标注有对应的数字,因此该数据集通常用于训练和评估机器学习算法和模型的性能,尤其是对手写数字识别算法和模型的评估。
Pytorch官方介绍:Pytorch官网MNIST文档
import torchvision.transforms as transforms
from torchvision import datasets
from torch.utils.data.sampler import SubsetRandomSampler
#在数据中使用的处理器个数
num_workers = 0
#每一批数据的个数
batch_size = 20
#验证数据集的占比
valid_size = 0.2
#将数据转化为Pytorch的张量Tensor类型
transform = transforms.ToTensor()
# 选择训练集和测试集,root:数据文件根目录
# train:是否载入训练集
# download:如果目录中找不到数据集,是否自动下载
# transform:将载入的数据按上面transform定义的方式进行转换
##训练数据下载
train_data = datasets.MNIST(root="./MNIST_Dataset",train=True,
transform=transform,download=True)
##测试数据下载
test_data = datasets.MNIST(root="./MNIST_Dataset",train=False,
transform=transform,download=True)
这里将加载的训练数据集进行划分,分为训练和验证这样方便对最后测试集进行最后的检验
测试集相当于是最后考试,划分为的验证集相当于是模拟测试,训练集则为学习
#### 将训练集打乱并分为验证集索引和训练集索引 ###
num_train = len(train_data) # 获取训练集的长度
indices = list(range(num_train)) # 生成list,里面从0开始一共有num_train长度
np.random.shuffle(indices) # 将列表打乱(相当于随即将训练集打乱,类似于DataLoader的shuffle)
split = int(np.floor(valid_size*num_train)) # 计算分化split长度
train_idx = indices[split:None] # 获取训练集索引(list)
valid_idx = indices[None:split] # 获取验证集索引(list)
#### 将训练数据集划分为新的训练数据集和验证集 ###
train_sampler = SubsetRandomSampler(train_idx) # 相当于将list转换为Sampler,并随机采样序列
valid_sampler = SubsetRandomSampler(valid_idx) # 相当于将list转换为Sampler,并随机采样序列
### 创建数据Loader,python中的生成器,每次条用返回一个batch ###
# 训练集
train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size,
sampler=train_sampler, num_workers=num_workers)
# 验证集
valid_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size,
sampler=valid_sampler, num_workers=num_workers)
# 测试集
test_loader = torch.utils.data.DataLoader(test_data, batch_size=batch_size
,shuffle= True, num_workers=num_workers)
# 可重复运行,展示不同的图片
# 构造迭代器,获得训练集中的一批次的数据
dataiter = iter(train_loader)
images, labels = next(dataiter)
# 将数据集中的Tensor张量转换为numpy的array数据类型
images = images.numpy()
# 可视化图片和标签
fig = plt.figure(figsize=(15,3))
for idx in np.arange(20):
ax = fig.add_subplot(2, 10, idx+1, xticks=[], yticks=[])
ax.imshow(np.squeeze(images[idx]),cmap='gray')
# .item()获张量的数值
ax.set_title(str(labels[idx].item()))
# 去掉图像的批次维度,只保留索引为1的单张图像的长宽像素值
img = np.squeeze(images[1])
fig = plt.figure(figsize=(12,12))
ax = fig.add_subplot(111)
ax.imshow(img, cmap='gray')
width, height = img.shape
thresh = img.max()/2.5
# 遍历每一行每一列每一个元素
for x in range(width):
for y in range(height):
# 像素保留两位小数,如果为0则显示为0
val = round(img[x][y],2) if img[x][y] != 0 else 0
# annotate 在每个像素上标记,在0上为白色,在亮的地方为黑色
ax.annotate(str(val), xy=(y,x),
horizontalalignment='center',
verticalalignment='center',
color='white' if img[x][y]<thresh else 'black')
输入为784维向量,输出为10个数字对应的概率 ,中间层是两个隐含层,每个层都是512
import torch.nn as nn
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
self.model = nn.Sequential(
nn.Linear(28*28, 512), # 输入层到隐含层 784 -> 512
nn.ReLU(), # 非线性激活ReLU
nn.Dropout(0.3), # Dropout 防止过拟合
nn.Linear(512, 512), # 隐含层到隐含层 512 -> 512
nn.ReLU(), # 非线性激活ReLU
nn.Dropout(0.3), # Dropout 防止过拟合
nn.Linear(512, 10), # 隐含层到输出层 512 ->10
)
def forward(self,x):
flatten = nn.Flatten()
x = flatten(x)
x = self.model(x)
return x
Model = Net()
if torch.cuda.is_available():
Model = Model.cuda()
print(Model)
Net(
(model): Sequential(
(0): Linear(in_features=784, out_features=512, bias=True)
(1): ReLU()
(2): Dropout(p=0.3, inplace=False)
(3): Linear(in_features=512, out_features=512, bias=True)
(4): ReLU()
(5): Dropout(p=0.3, inplace=False)
(6): Linear(in_features=512, out_features=10, bias=True)
)
)
如图所示,大致卷积大致结构为:
import torch.nn as nn
class CNNet(nn.Module):
def __init__(self):
super().__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels= 1 ,out_channels= 5 ,kernel_size= 5,padding=2), # 卷基层1
nn.ReLU(), # 激活函数
nn.MaxPool2d(kernel_size=2), # 最大池化
nn.Conv2d(in_channels=5 ,out_channels= 16,kernel_size= 5, padding=2), # 卷基层2
nn.ReLU(), # 激活函数
nn.MaxPool2d(kernel_size=2), # 最大池化
nn.Flatten(), # 展开
nn.Linear(784,64),
nn.ReLU(),
nn.Linear(64,10)
)
def forward(self,x):
x = self.model(x)
return x
Model_CNN = CNNet()
if torch.cuda.is_available():
Model_CNN = Model_CNN.cuda()
print(Model_CNN)
CNNet(
(model): Sequential(
(0): Conv2d(1, 5, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(1): ReLU()
(2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(3): Conv2d(5, 16, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(4): ReLU()
(5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(6): Flatten(start_dim=1, end_dim=-1)
(7): Linear(in_features=784, out_features=64, bias=True)
(8): ReLU()
(9): Linear(in_features=64, out_features=10, bias=True)
)
)
使用交叉熵作为分类问题的损失函数
# 损失函数为交叉熵损失函数
criterion = nn.CrossEntropyLoss()
if torch.cuda.is_available():
criterion = criterion.cuda()
# 定义优化器
optimizer = torch.optim.SGD(Model.parameters(),lr=0.01)
optimizer_cnn = torch.optim.SGD(Model_CNN.parameters(),lr=0.01)
1.清除所有梯度
2.正向预测,求出模型对数据集的预测分类
3.计算损失函数
4.反向传播,计算损失函数的每一个权重求导,求得对应权重的梯度
5.优化器更新权重
6.计算每一轮的平均训练误差和验证误差
# 训练轮次,每一轮都完整遍历数据集中所有图像
num_epoch = 20
# 初始化验证集最小误差为无穷大
valid_loss_min = np.Inf
# 初始化loss_list
train_loss_list = []
valid_loss_list = []
# 每一轮训练:
for epoch in range(num_epoch):
#初始化损失
train_loss = 0
valid_loss = 0
### 训练阶段 ###
Model.train()
for data in train_loader:
imgs, labels = data # 读取数据
if torch.cuda.is_available():
imgs = imgs.cuda()
labels = labels.cuda()
optimizer.zero_grad() # 将梯度归零
output = Model(imgs) # 正向预测
loss = criterion(output,labels) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 优化器进行更新
train_loss += loss.item()*imgs.size(0) # 计算本批次损失
### 验证阶段 ###
Model.eval()
for data in valid_loader:
imgs, labels = data # 读取数据
if torch.cuda.is_available():
imgs = imgs.cuda()
labels = labels.cuda()
output = Model(imgs) # 正向预测
loss = criterion(output,labels) # 计算损失
valid_loss += loss.item()*imgs.size(0) # 计算本批次损失
# 结束本论的训练和验证,打印训练和验证的指标
# 计算平均训练损失和平均验证损失,存储在列表中
train_loss = train_loss/len(train_loader.dataset)
valid_loss = valid_loss/len(valid_loader.dataset)
train_loss_list.append(train_loss)
valid_loss_list.append(valid_loss)
print("----------------------------------------------------")
print("第{}轮 \t训练损失: {:.6f} \t验证损失: {:.6f}".format(epoch+1, train_loss, valid_loss))
#损失有降低则保存到本地
if valid_loss <= valid_loss_min:
print("验证误差比上次降低了({:.6f} ---> {:.6f}). 保存模型!".format(valid_loss_min,valid_loss))
torch.save(Model.state_dict(), "MNIST.pth")
valid_loss_min = valid_loss
----------------------------------------------------
第1轮 训练损失: 0.788821 验证损失: 0.077319
验证误差比上次降低了(inf ---> 0.077319). 保存模型!
----------------------------------------------------
第2轮 训练损失: 0.295448 验证损失: 0.059102
验证误差比上次降低了(0.077319 ---> 0.059102). 保存模型!
----------------------------------------------------
第3轮 训练损失: 0.233553 验证损失: 0.048407
验证误差比上次降低了(0.059102 ---> 0.048407). 保存模型!
----------------------------------------------------
第4轮 训练损失: 0.193519 验证损失: 0.040578
验证误差比上次降低了(0.048407 ---> 0.040578). 保存模型!
----------------------------------------------------
第5轮 训练损失: 0.165305 验证损失: 0.035517
验证误差比上次降低了(0.040578 ---> 0.035517). 保存模型!
----------------------------------------------------
第6轮 训练损失: 0.143009 验证损失: 0.031903
验证误差比上次降低了(0.035517 ---> 0.031903). 保存模型!
----------------------------------------------------
第7轮 训练损失: 0.127322 验证损失: 0.028366
验证误差比上次降低了(0.031903 ---> 0.028366). 保存模型!
----------------------------------------------------
第8轮 训练损失: 0.113886 验证损失: 0.026533
验证误差比上次降低了(0.028366 ---> 0.026533). 保存模型!
----------------------------------------------------
第9轮 训练损失: 0.103299 验证损失: 0.024597
验证误差比上次降低了(0.026533 ---> 0.024597). 保存模型!
----------------------------------------------------
第10轮 训练损失: 0.095750 验证损失: 0.022435
验证误差比上次降低了(0.024597 ---> 0.022435). 保存模型!
----------------------------------------------------
第11轮 训练损失: 0.087945 验证损失: 0.021507
验证误差比上次降低了(0.022435 ---> 0.021507). 保存模型!
----------------------------------------------------
第12轮 训练损失: 0.081725 验证损失: 0.019907
验证误差比上次降低了(0.021507 ---> 0.019907). 保存模型!
----------------------------------------------------
第13轮 训练损失: 0.076345 验证损失: 0.019214
验证误差比上次降低了(0.019907 ---> 0.019214). 保存模型!
----------------------------------------------------
第14轮 训练损失: 0.071628 验证损失: 0.018275
验证误差比上次降低了(0.019214 ---> 0.018275). 保存模型!
----------------------------------------------------
第15轮 训练损失: 0.066302 验证损失: 0.017198
验证误差比上次降低了(0.018275 ---> 0.017198). 保存模型!
----------------------------------------------------
第16轮 训练损失: 0.061777 验证损失: 0.016841
验证误差比上次降低了(0.017198 ---> 0.016841). 保存模型!
----------------------------------------------------
第17轮 训练损失: 0.058538 验证损失: 0.015928
验证误差比上次降低了(0.016841 ---> 0.015928). 保存模型!
----------------------------------------------------
第18轮 训练损失: 0.054368 验证损失: 0.016356
----------------------------------------------------
第19轮 训练损失: 0.052580 验证损失: 0.015056
验证误差比上次降低了(0.015928 ---> 0.015056). 保存模型!
----------------------------------------------------
第20轮 训练损失: 0.049286 验证损失: 0.014715
验证误差比上次降低了(0.015056 ---> 0.014715). 保存模型!
# 训练轮次,每一轮都完整遍历数据集中所有图像
num_epoch_cnn = 20
# 初始化验证集最小误差为无穷大
valid_loss_min_cnn = np.Inf
# 初始化loss_list
train_loss_list_cnn = []
valid_loss_list_cnn = []
# 每一轮训练:
for epoch in range(num_epoch_cnn):
#初始化损失
train_loss = 0
valid_loss = 0
### 训练阶段 ###
Model_CNN.train()
for data in train_loader:
imgs, labels = data # 读取数据
if torch.cuda.is_available():
imgs = imgs.cuda()
labels = labels.cuda()
optimizer_cnn.zero_grad() # 将梯度归零
output = Model_CNN(imgs) # 正向预测
loss = criterion(output,labels) # 计算损失
loss.backward() # 反向传播
optimizer_cnn.step() # 优化器进行更新
train_loss += loss.item()*imgs.size(0) # 计算本批次损失
### 验证阶段 ###
Model_CNN.eval()
for data in valid_loader:
imgs, labels = data # 读取数据
if torch.cuda.is_available():
imgs = imgs.cuda()
labels = labels.cuda()
output = Model_CNN(imgs) # 正向预测
loss = criterion(output,labels) # 计算损失
valid_loss += loss.item()*imgs.size(0) # 计算本批次损失
# 结束本论的训练和验证,打印训练和验证的指标
# 计算平均训练损失和平均验证损失,存储在列表中
train_loss = train_loss/len(train_loader.dataset)
valid_loss = valid_loss/len(valid_loader.dataset)
train_loss_list_cnn.append(train_loss)
valid_loss_list_cnn.append(valid_loss)
print("----------------------------------------------------")
print("第{}轮 \t训练损失: {:.6f} \t验证损失: {:.6f}".format(epoch+1, train_loss, valid_loss))
#损失有降低则保存到本地
if valid_loss <= valid_loss_min_cnn:
print("验证误差比上次降低了({:.6f} ---> {:.6f}). 保存模型!".format(valid_loss_min_cnn,valid_loss))
torch.save(Model_CNN.state_dict(), "MNIST_CNN.pth")
valid_loss_min_cnn = valid_loss
----------------------------------------------------
第1轮 训练损失: 0.526256 验证损失: 0.041250
验证误差比上次降低了(inf ---> 0.041250). 保存模型!
----------------------------------------------------
第2轮 训练损失: 0.110631 验证损失: 0.025260
验证误差比上次降低了(0.041250 ---> 0.025260). 保存模型!
----------------------------------------------------
第3轮 训练损失: 0.077032 验证损失: 0.017001
验证误差比上次降低了(0.025260 ---> 0.017001). 保存模型!
----------------------------------------------------
第4轮 训练损失: 0.061163 验证损失: 0.018689
----------------------------------------------------
第5轮 训练损失: 0.053077 验证损失: 0.013754
验证误差比上次降低了(0.017001 ---> 0.013754). 保存模型!
----------------------------------------------------
第6轮 训练损失: 0.046435 验证损失: 0.013632
验证误差比上次降低了(0.013754 ---> 0.013632). 保存模型!
----------------------------------------------------
第7轮 训练损失: 0.040591 验证损失: 0.014042
----------------------------------------------------
第8轮 训练损失: 0.038105 验证损失: 0.012737
验证误差比上次降低了(0.013632 ---> 0.012737). 保存模型!
----------------------------------------------------
第9轮 训练损失: 0.033946 验证损失: 0.011880
验证误差比上次降低了(0.012737 ---> 0.011880). 保存模型!
----------------------------------------------------
第10轮 训练损失: 0.031287 验证损失: 0.013274
----------------------------------------------------
第11轮 训练损失: 0.028120 验证损失: 0.012257
----------------------------------------------------
第12轮 训练损失: 0.026858 验证损失: 0.010457
验证误差比上次降低了(0.011880 ---> 0.010457). 保存模型!
----------------------------------------------------
第13轮 训练损失: 0.024483 验证损失: 0.015123
----------------------------------------------------
第14轮 训练损失: 0.022900 验证损失: 0.010302
验证误差比上次降低了(0.010457 ---> 0.010302). 保存模型!
----------------------------------------------------
第15轮 训练损失: 0.020943 验证损失: 0.010131
验证误差比上次降低了(0.010302 ---> 0.010131). 保存模型!
----------------------------------------------------
第16轮 训练损失: 0.018959 验证损失: 0.012515
----------------------------------------------------
第17轮 训练损失: 0.018367 验证损失: 0.010460
----------------------------------------------------
第18轮 训练损失: 0.016924 验证损失: 0.009683
验证误差比上次降低了(0.010131 ---> 0.009683). 保存模型!
----------------------------------------------------
第19轮 训练损失: 0.015362 验证损失: 0.011653
----------------------------------------------------
第20轮 训练损失: 0.015022 验证损失: 0.011096
plt.plot(train_loss_list,label="训练误差")
plt.plot(valid_loss_list,label="验证误差")
plt.legend() # 将label贴上去
plt.title('MLP训练误差和验证误差变化')
plt.show()
plt.plot(train_loss_list_cnn,label="训练误差")
plt.plot(valid_loss_list_cnn,label="验证误差")
plt.legend() # 将label贴上去
plt.title('CNN训练误差和验证误差变化')
plt.show()
plt.plot(valid_loss_list, c='r',label="MLP误差") # c代表就是颜色,r就是指红色
plt.plot(valid_loss_list_cnn, c='g',label="CNN误差") # c代表就是颜色,r就是指红色
plt.legend()
plt.title("CNN/MLP验证误差变化")
plt.show()
Model.load_state_dict(torch.load('MNIST.pth'))
Model_CNN.load_state_dict(torch.load('MNIST_CNN.pth'))
<All keys matched successfully>
# 遍历整个测试集
# 初始化测试误差
test_loss = 0
class_correct = list(0 for i in range(10))
class_total = list(0 for i in range(10))
# 验证阶段
Model.eval()
for data in test_loader:
imgs, labels = data # 读取数据
if torch.cuda.is_available():
imgs = imgs.cuda()
labels = labels.cuda()
output = Model(imgs) # 正向预测
loss = criterion(output,labels) # 计算损失
test_loss += loss.item()*imgs.size(0) # 计算本批次损失
for i in range(batch_size):
pred = torch.argmax(output[i])
if pred == labels[i].item():
class_correct[labels[i].item()] += 1
class_total[labels[i].item()] +=1
test_loss = test_loss/len(test_loader.dataset)
print('MLP测试集上的误差为:{:.6}'.format(test_loss))
print('MLP整体测试集上正确率为:{}%'.format(((sum(class_correct))/sum(class_total))*100))
MLP测试集上的误差为:0.0689453
MLP整体测试集上正确率为:97.78999999999999%
for i in range(10):
print("MLP: 数字 {} 在测试集上正确率为: {}%".format(i, (class_correct[i]/class_total[i])*100))
MLP: 数字 0 在测试集上正确率为: 98.77551020408163%
MLP: 数字 1 在测试集上正确率为: 99.20704845814979%
MLP: 数字 2 在测试集上正确率为: 97.86821705426357%
MLP: 数字 3 在测试集上正确率为: 98.41584158415841%
MLP: 数字 4 在测试集上正确率为: 97.75967413441956%
MLP: 数字 5 在测试集上正确率为: 96.8609865470852%
MLP: 数字 6 在测试集上正确率为: 97.4947807933194%
MLP: 数字 7 在测试集上正确率为: 97.17898832684824%
MLP: 数字 8 在测试集上正确率为: 97.1252566735113%
MLP: 数字 9 在测试集上正确率为: 96.92765113974232%
# 遍历整个测试集
# 初始化测试误差
test_loss_CNN = 0
class_correct_CNN = list(0 for i in range(10))
class_total_CNN = list(0 for i in range(10))
# 验证阶段
Model.eval()
for data in test_loader:
imgs, labels = data # 读取数据
if torch.cuda.is_available():
imgs = imgs.cuda()
labels = labels.cuda()
output = Model_CNN(imgs) # 正向预测
loss = criterion(output,labels) # 计算损失
test_loss_CNN += loss.item()*imgs.size(0) # 计算本批次损失
for i in range(batch_size):
pred = torch.argmax(output[i])
if pred == labels[i].item():
class_correct_CNN[labels[i].item()] += 1
class_total_CNN[labels[i].item()] +=1
test_loss_CNN = test_loss_CNN/len(test_loader.dataset)
print('CNN测试集上的误差为:{:.6}'.format(test_loss_CNN))
print('CNN整体测试集上正确率为:{}%'.format(((sum(class_correct_CNN))/sum(class_total_CNN))*100))
CNN测试集上的误差为:0.0359796
CNN整体测试集上正确率为:98.83%
for i in range(10):
print("CNN: 数字 {} 在测试集上正确率为: {}%".format(i, (class_correct_CNN[i]/class_total_CNN[i])*100))
CNN: 数字 0 在测试集上正确率为: 99.79591836734694%
CNN: 数字 1 在测试集上正确率为: 99.73568281938327%
CNN: 数字 2 在测试集上正确率为: 98.25581395348837%
CNN: 数字 3 在测试集上正确率为: 98.8118811881188%
CNN: 数字 4 在测试集上正确率为: 98.98167006109979%
CNN: 数字 5 在测试集上正确率为: 98.76681614349776%
CNN: 数字 6 在测试集上正确率为: 98.01670146137788%
CNN: 数字 7 在测试集上正确率为: 99.0272373540856%
CNN: 数字 8 在测试集上正确率为: 98.56262833675564%
CNN: 数字 9 在测试集上正确率为: 98.21605550049554%
# 构造迭代器,获得训练集中的一批次的数据
dataiter = iter(test_loader)
images, labels = next(dataiter)
if torch.cuda.is_available():
images = images.cuda()
labels = labels.cuda()
output = Model(images) # 正向预测
# 将数据集中的Tensor张量转换为numpy的array数据类型
images = images.cpu().numpy()
# 可视化图片和标签
fig = plt.figure(figsize=(15,3))
for idx in np.arange(20):
pred = torch.argmax(output[idx]) #获取预测值
ax = fig.add_subplot(2, 10, idx+1, xticks=[], yticks=[])
ax.imshow(np.squeeze(images[idx]),cmap='gray')
# .item()获张量的数值
ax.set_title("{}[{}]".format(str(labels[idx].item()),pred.item()),
c = "green" if labels[idx].item() == pred.item() else 'r')
# 构造迭代器,获得训练集中的一批次的数据
dataiter = iter(test_loader)
images, labels = next(dataiter)
if torch.cuda.is_available():
images = images.cuda()
labels = labels.cuda()
output = Model_CNN(images) # 正向预测
# 将数据集中的Tensor张量转换为numpy的array数据类型
images = images.cpu().numpy()
# 可视化图片和标签
fig = plt.figure(figsize=(15,3))
for idx in np.arange(20):
pred = torch.argmax(output[idx]) #获取预测值
ax = fig.add_subplot(2, 10, idx+1, xticks=[], yticks=[])
ax.imshow(np.squeeze(images[idx]),cmap='gray')
# .item()获张量的数值
ax.set_title("{}[{}]".format(str(labels[idx].item()),pred.item()),
c = "green" if labels[idx].item() == pred.item() else 'r')
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。