# machine-learning-deep-learning **Repository Path**: zhu-ronghui/machine-learning-deep-learning ## Basic Information - **Project Name**: machine-learning-deep-learning - **Description**: 记录我学习pytotch、tensorflow的学习心得。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 1 - **Created**: 2023-11-04 - **Last Updated**: 2024-10-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 1. [Pytorch]CNN识别手写数字(Mnist数据集),最高准确率99.71% 简介:虽说是基于卷积神经网络的,但我也尝试缝合进去了残差神经网络的一些模块,理论上残差神经网络能消除梯度消失和梯度爆炸的问题,事实上也有一点提升,不过我用的还不是很好,所以提升不是很大。经过漫长的改进,最终模型对10000个测试集的识别正确率达到了99.71%,也算是差强人意了。 ## 1.准备数据集 数据集统一lecun的标准数据集,其中50000个训练集,10000个测试集。[下载地址:MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges](http://yann.lecun.com/exdb/mnist/) 为了增强数据,给数据增加随即仿射变换、随机旋转、和标准化。并把训练集和测试集都转为tensor方便处理。 在数据集导入时进行随机打乱和将数据存储在cuda固定内存中,提高数据的存储速度。 batch_size设置为256有点偏大了,收敛的精度可能不会很好。但是加上动态调节学习率倒也还可以,不过为了精度的话还是小一点比如128为好 ```python #构建一个compose类的实例用于图像预处理,训练集做仿射变换、随即旋转、转tensor(张量)、后面那个是标准化 train_transform = transforms.Compose([ transforms.RandomAffine(degrees = 0,translate=(0.1, 0.1)), transforms.RandomRotation((-10,10)), transforms.ToTensor(), transforms.Normalize((0.1307,),(0.3081,))]) test_transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,),(0.3081,))]) train_batch_size = 256 learning_rate = 0.06 test_batch_size = 100 random_seed = 2 # 随机种子,设置后可以得到稳定的随机数 torch.manual_seed(random_seed) torch.cuda.manual_seed_all(random_seed) #为gpu提供随机数 train_dataset = datasets.MNIST(root='../dataset/mnist/', train=True, download=False, transform=train_transform) test_dataset = datasets.MNIST(root='../dataset/mnist/', train=False, download=False, transform=test_transform) #将数据存储在cuda固定内存中,提高数据的存储速度 train_loader = DataLoader(dataset=train_dataset,batch_size=train_batch_size,shuffle=True,pin_memory=True) test_loader = DataLoader(dataset=test_dataset,batch_size=test_batch_size,shuffle=False,pin_memory=True) ``` ## 2.设计残差神经网络的模块 模型的准确率并不会随着神经网络层数的增加而一直增长,反而会在达到最大值后快速降低。ResNet团队把这一现象称为“退化(Degradation)”,他们把退化现象归因为深层神经网络难以实现“恒等变换(*y*=*x*)“。随着网络深度的增加,我们引入的非线性激活函数越来越多,也就把数据映射向更离散的空间,使得数据越来越难以回到原点(实现恒等变换)。因此,Resnet团队在采样构建块的主杆分支增加了一个1×1的卷积操作。本网络使用的残差模块即是如此,在采用两层的3×3卷积的同时,还设计了一路1×1卷积,将二者的结果求和再激活得到最终结果。 image-20220719075317078 ```python class ResidualBlock(nn.Module): # Residual Block需要保证输出和输入通道数x一样 def __init__(self, channels): super(ResidualBlock, self).__init__() self.channels = channels # 3*3卷积核,保证图像大小不变将padding设为1 # 第一个卷积 self.conv1 = nn.Conv2d(channels, channels, kernel_size=3, padding=1) # 第二个卷积 self.conv2 = nn.Conv2d(channels, channels, kernel_size=3, padding=1) #第三个卷积,1*1 self.conv3 = nn.Conv2d(channels, channels, kernel_size=1) def forward(self, x): # 激活 y = F.relu(self.conv1(x)) y = self.conv2(y) # 先求和 后激活 z = self.conv3(x) return F.relu(z + y) ``` ## 3.设计网络 在我的设计里,网络为了不丢失边缘信息(实际上好像有点多此一举),使用5*5卷积核的时候,每一个卷积层都使用了2的增扩,使得长宽在卷积中不改变。卷积对通道的改变依次是:1-32-64-128-192,不断增大权重的规模,这也是为了保留更多的特征用于分类。【由于还经过了两次最大池化,所以长宽都变为原来的1/4。 也就是:1-28-28的输入(训练集的图片均为单通道,长宽28的正方形图片),提取特征后转化为192-7-7输出。 经过两层全连接层192×7×7 = 9408 - - 256 - - 10(10个分类)得到最后输出。 下图为网络结构示意图: ![image-20231104102608363](https://s2.loli.net/2023/11/04/21NJWPXQVjOaBSg.png) ![image-20231104102825787](https://s2.loli.net/2023/11/04/DAjf8yxpvE6FP1d.png) ```python class Net(nn.Module): def __init__(self): super(Net, self).__init__() #卷积层 self.conv1 = nn.Conv2d(1 ,32, kernel_size=5,padding=2) self.conv2 = nn.Conv2d(32 ,64, kernel_size=5,padding=2) self.conv3 = nn.Conv2d(64 ,128,kernel_size=5,padding=2) self.conv4 = nn.Conv2d(128,192,kernel_size=5,padding=2) #残差神经网络层,其中已经包含了relu self.rblock1 = ResidualBlock(32) self.rblock2 = ResidualBlock(64) self.rblock3 = ResidualBlock(128) self.rblock4 = ResidualBlock(192) #BN层,归一化,使数据在进行Relu之前不会因为数据过大而导致网络性能的不稳定 self.bn1 = nn.BatchNorm2d(32) self.bn2 = nn.BatchNorm2d(64) self.bn3 = nn.BatchNorm2d(128) self.bn4 = nn.BatchNorm2d(192) #最大池化,一般最大池化效果都比平均池化好些 self.mp = nn.MaxPool2d(2) #fully connectected全连接层 self.fc1 = nn.Linear(192*7*7, 256) # 线性 self.fc6 = nn.Linear(256, 10) # 线性 def forward(self, x): in_size = x.size(0) x = self.conv1(x) #channels:1-32 w*h:28*28 x = self.bn1(x) x = F.relu(x) x = self.rblock1(x) x = self.conv2(x) #channels:32-64 w*h:28*28 x = F.relu(x) x = self.bn2(x) x = self.rblock2(x) x = self.mp(x) #最大池化,channels:64-64 w*h:28*28->14*14 x = self.conv3(x) #channels:64-128 w*h:14*14 x = self.bn3(x) x = F.relu(x) x = self.rblock3(x) x = self.conv4(x) #channels:128-192 w*h:14*14 x = self.bn4(x) x = F.relu(x) x = self.rblock4(x) x = self.mp(x) #最大池化,channels:192-192 w*h:14*14->7*7 x = x.view(in_size, -1) #展开成向量 x = F.relu(self.fc1(x)) # 使用relu函数来激活 return self.fc6(x) ``` ## 4.准备优化器以及其他工作 1.将数据移动到显卡处理,需要cuda。 2.损失函数选择交叉熵损失函数,适用于多分类问题。计算公式: $$ L(y,\hat y) = -∑y_clog(\hat y_c) $$ 3.优化器使用普通的SGD优化器,设置了0.5的动量因子。 ```python #调用GPU os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" torch.backends.cudnn.benchmark = True #启用cudnn底层算法 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") #print(device) torch.cuda.empty_cache() #释放显存 model.to(device) # #构建损失函数 criterion = torch.nn.CrossEntropyLoss() #交叉熵 # # #构建优化器,参数1:模型权重,参数二,learning rate optimizer = optim.SGD(model.parameters(),lr=learning_rate,momentum=0.5) #带动量0.5 #optimizer = optim.RMSprop(model.parameters(),lr=learning_rate,alpha=0.99,momentum = 0.5) # optimizer = torch.optim.Adam(model.parameters(), # lr=0.05, # betas=(0.9, 0.999), # eps=1e-08, # weight_decay=0, # amsgrad=False) #设置学习率梯度下降,如果连续2个epoch测试准确率没有上升,则降低学习率,系数0.5 scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=2, verbose=True, threshold=0.00005, threshold_mode='rel', cooldown=0, min_lr=0, eps=1e-08) ``` ## 5.构建训练函数 ```python #把训练封装成一个函数 def train(epoch): running_loss =0.0 for batch_idx,data in enumerate(train_loader,0): inputs,target = data inputs,target = inputs.to(device),target.to(device) optimizer.zero_grad() #forward,backward,update outputs = model(inputs) loss = criterion(outputs,target) loss.backward() optimizer.step() running_loss+=loss.item() train_loss_val.append((running_loss)) writer.add_scalar('Loss',running_loss,int((epoch-1)*50000/train_batch_size)+batch_idx) running_loss = 0.0 return running_loss / 300 ``` ## 6.构建测试函数 ```python #把测试封装成函数 def test(): correct = 0 total = 0 with torch.no_grad(): for data in test_loader: images,labels = data images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs.data,dim=1) #从第一维度开始搜索 total += labels.size(0) correct += (predicted==labels).sum().item() return correct/total ``` ## 7.主函数 ```python train_epoch= [] model_accuracy = [] temp_acc = 0.0 train_loss_val = [] now = datetime.datetime.now() print(now.strftime('%Y/%m/%d %H:%M:%S')) for epoch in range(1): # train(epoch) acc = test() # print(epoch,acc) train_epoch.append(epoch) # acc = model_accuracy.append(acc) writer.add_scalar('Accuracy',acc,epoch+1) now = datetime.datetime.now() print(now.strftime('%Y/%m/%d %H:%M:%S'),"Accuracy:%f "% (acc)) if torch.cuda.is_available(): graph_inputs = torch.from_numpy(np.random.rand(1,1,28,28)).type(torch.FloatTensor).cuda() else: graph_inputs = torch.from_numpy(np.random.rand(1,1,28,28)).type(torch.FloatTensor) writer.add_graph(model, (graph_inputs,)) writer.close() torch.cuda.empty_cache() #释放显存 ``` ## 8. Tensorboard可视化 为了更直观地观察模型的训练,使用tensorboard对模型各项参数进行可视化。 ### 正确率: ![image-20231104104905760](https://s2.loli.net/2023/11/04/BqJcVKLFUNwMm89.png) ### 损失曲线: ![image-20231104104853418](https://s2.loli.net/2023/11/04/oaZSiW54XkutKTY.png) ### 网络结构图: ![](https://s2.loli.net/2023/11/04/LJcWeRXOBx49wSt.png) ### 残差模块示意图: ![image-20231104105112961](https://s2.loli.net/2023/11/04/bIY7OlznNkXsRDo.png) ## 9.模型结果 最高成功率为99.71%,说明它具有不俗的表现,但由于网络的设计过于冗余,需要的训练时间比较长,还有待改进。