《PyTorch深度学习实践》第9讲

# 直接复制到编译器里面看就行
# 视频配套:09.多分类问题_哔哩哔哩_bilibili
import torch
import time
start_time = time.time() # 为了计算和gpu计算时的差距,调用这个包来计算程序运行时间
# 以下三个是与数据集相关的包
from torchvision import transforms #torchvision里的transforms针对各种各样的图像进行的处理的工具
from torchvision import datasets
from torch.utils.data import DataLoader

import torch.nn.functional as F # for using function relu() 采用relu激活函数
import torch.optim as optim # 需要用到的一些优化器

# 1.prepare dataset
batch_size = 100
#先把提取到的图像转变成pytorch里面的tensor张量
transform = transforms.Compose([ # transforms属于torchvision模块的方法,它是常见的图像预处理的方法
    transforms.ToTensor(),  # convert the PIL(pillow) Image to Tensor
    # 具体转换见:https://www.bilibili.com/video/BV1Y7411d7Ys?p=9&vd_source=181df9645c4940d2088894ef3be6cc28
    # 31:00
    # 重点看图像张量是怎么实现变换的
    # 图像有单通道图和多通道图 用pillow或者opencv读进来的图像一般是(W(wight),H(height),C(channel))
    # 在pytorch里面需要把图像转换为(C,W,H),通道需要放在前面,主要是为了在pytorch里面进行更加高效的卷积运算和图像处理
    # (W,H,C) -> (C,W,H)
    transforms.Normalize((0.1307,),(0.3081,)) # 后面两个数字是已经计算好的minst数据集的像素平均值和标准差,如果是自己的数据集,得先计算出来
    # 标准化分布的训练效果比较好(eg:标准正态分布)
    # 上面是在归一化,第一个数是均值,第二个数是标准差,想的是把(0~255)映射到(0~1)上
    # 满足(0,1)标准化分布,上面这两个数字是算好的,以后拿到数据集先对平均值和标准差进行计算
    # 数据归一化处理transforms.Normalize详情:
    # https://blog.csdn.net/qq_38765642/article/details/109779370
])

# 准备训练集 这个训练集有60000张图像
train_dataset = datasets.MNIST(root='../dataset/mnist/', # 训练集的文件位置
                               train= True, # 是否为训练集
                               download=True, # 是否需要下载,如果相应位置已经有数据集,那么不需要下载,如果没有的话就得下载
                               transform=transform)
# 训练数据集的时候需要shuffle
train_loader = DataLoader(train_dataset,
                          shuffle=True,
                          batch_size=batch_size,
                          num_workers=4)
# 准备测试集 这个测试集有10000张图像
test_dataset = datasets.MNIST(root='../dataset/mnist/',
                               train= False,
                               download=True,
                               transform=transform)

# 测试数据集的时候,不需要shuffle,这样是为了保证顺序是一样的,对观察结果比较有帮助
test_loader = DataLoader(test_dataset,
                          shuffle=False,
                          batch_size=batch_size,
                          num_workers=4)

# 以下为网络模型
class Net(torch.nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.l1 = torch.nn.Linear(784, 512) # 第二个数字和下面的第一个数字对应
        self.l2 = torch.nn.Linear(512, 233)
        self.l3 = torch.nn.Linear(233, 128)
        self.l4 = torch.nn.Linear(128, 64)
        self.l5 = torch.nn.Linear(64, 10) # 最后一个数字是类别数

    # 调用model(inputs)直接进入下面的这个forward()方法
    def forward(self,x):
        # 关于x.view() : https://blog.csdn.net/echo_gou/article/details/121035061
        # x.view()就是对tensor进行reshape
        # 这里采用的时全连接网络,需要把每一张图像转换成一个一维的矩阵
        x = x.view(-1,784)
        # print(x.shape) # torch.Size([64, 784]) 说明有64(和batch-size相同,batch-size可更改)张图片
        """
        -1表示一个不确定的数,784表示列,就是对x进行reshape,n行784列
        由于每一张图片是 1*28*28 = 784的,所以这里每一行就是一张图像
        """
        x = F.relu(self.l1(x)) # self.l1(x)就是先调用上面的torch.nn.Linear(784, 512),
                               # 先对x进行线性变换,然后经过relu进行非线性变换
        x = F.relu(self.l2(x))
        x = F.relu(self.l3(x))
        x = F.relu(self.l4(x))
        return self.l5(x) # 最后一层不做激活,这个是为了用交叉熵损失,把输出直接经过 CrossEntropyLoss 函数计算损失

model = Net()

#建立损失函数和优化器
"""
使用CrossEntropyLoss的时候,神经网络的最后一层是不需要做激活的,正如下面代码所示
"""
criterion = torch.nn.CrossEntropyLoss() # 这个损失函数结合了softmax和交叉熵损失,把这俩集成到了一个里面
optimizer = optim.SGD(model.parameters(),lr=0.01,momentum=0.9) # momentum为动量,一般采用0.9

# 4.Train and Test
def train(epoch):
    # 这里是把一个epoch的训练封装成函数
    running_loss = 0.0
    #print(train_loader)
    for batch_idx,data in enumerate(train_loader,0):
        """
        上方的batch_idx就是我们所说的迭代次数,所以可以这样直接拿出来,即 batch-index ,即每一个mini-batch的索引,
        minst训练集有60000张图片,所以需要经过 60000 / batch_size 个循环才能完成一次epoch
        """
        inputs, target = data
        # print(inputs.shape) # torch.Size([64, 1, 28, 28])
        # print(target.shape) # torch.Size([64])
        optimizer.zero_grad() # 优化器清零,默认梯度会累加,如果不清0,那么loss会很快变成无穷大

        # forward + backward + update(前馈+反馈+更新)
        #前馈:
        """
        __call__魔术方法,使得类实例对象可以像调用普通函数那样,以"对象名()"的形式使用
        在Net继承的父类 torch.nn.Module 里面也有forward方法,而本代码在Net里面重写了forward方法
        所以下面 model(inputs) 就是在调用类Net里面的forward方法,下面这一行代码就是网络的接口
        详情见:https://blog.csdn.net/fengbingchun/article/details/122331018
        """
        outputs = model(inputs) # 经过所设定的网络计算输出
        loss = criterion(outputs,target) # 通过输出计算交叉熵损失
        #反馈:
        loss.backward() # 反向传播,梯度计算
        #更新:
        optimizer.step() # 更新参数

        # 为了更方便地表示loss,先让它累加三百次,然后再将这300次的累加值取平均,以便更好地表示出来,
        # 否则循环一次就表示一次loss,那也太蠢了
        running_loss += loss.item() # 再强调一遍,.item()就是将tensor转换成值,便于python里面的计算
        #每输出300次迭代,输出一次running_loss
        if batch_idx % 200 == 199:
            # %5d是C语言的格式化输出,对不足5位的整形用空格补齐5位
            # print('[%d, %d] loss: %.6f' % (epoch + 1, batch_idx + 1, running_loss / 200))
            print('[%d, %d] loss: %.6f' % (epoch + 1, batch_idx + 1, running_loss / 200))
            running_loss = 0.0

def test():
    correct = 0
    total = 0
    # 执行完torch.no_grad(),下面的代码就不会再去计算梯度了,不会形成计算图,不会迭代新参数,相当于拿训练好的模型来测试
    # torch.no_grad()的说明:https://blog.csdn.net/Answer3664/article/details/99460175
    with torch.no_grad():
        # with的说明:https://blog.csdn.net/ego_bai/article/details/80873242
        for data in test_loader: # 这一行和test_loader那一行差不多的意思
            images, labels = data
            outputs = model(images) # imags相当于测试集的输入,outputs是测试集地输出
            # print(outputs.data.shape) # torch.Size([100, 10]) batch-size = 100
            #print(images.shape)
            _, predicted = torch.max(outputs.data, dim=1)  # 按行取最大值,得出行上面最大值所对应地索引,这里比较特殊,得出的索引就是识别出来的数字
            # print(predicted)
            # print(labels)
            # print(predicted.shape) # torch.Size([100])
            # print(labels.size(0)) # 100
            # 下面地total就是已经测试过的样本总数
            total += labels.size(0) # 这里labels.size(0)和batch-size相等,等于每一个batch的样本数量
            # 下面是对 predicted 和 labels 里面的值分别比较
            # predicted == labels 输出的是一个tensor,里面是bool类型的东西,相应位置一样就是true,不一样就是false
            # (predicted == labels).sum() 输出的是一个tensor类型的数字,我的batch是100,输出了tensor(97)
            # 最后的.item()就是把tensor(97)转换成97,用来求解数字,赋值给correct
            # correct是目前测试的正确的总数,而total是目前测试的样本总数,当测试完成后,correct / total 就是正确率
            correct += (predicted == labels).sum().item()  # 张量之间的比较运算
            """
            关于 .item()的笔记:https://pytorch.org/docs/stable/tensors.html?highlight=item#torch.Tensor.item
            x = torch.tensor([[1]])
            print(x) # tensor([[ 1]])
            print(x.item()) # 1
            简而言之,.item()就是把tensor类型转换成数字类型,便于运算
            """
    # 下面的代码对正确率进行了处理,以便于输出百分数
    # print(correct / total)
    print('accuracy on test set: %.3f %% ' % (100 * correct / total))



if __name__ == '__main__':
    for epoch in range(10):
        print("epoch: %d / %d"%(epoch+1,20))
        # 下面相当于训练 1 个epoch就测试 1 次,用来查看测试准确率是否上升
        train(epoch)
        # test() # 这样是训练一轮测试一次,写在epoch的for循环里面
    test() # 这样是所有样本训练完了最后测试,写在epoch的for循环外面
    end_time = time.time()
    print("Total running time:",end_time - start_time,"s")

你可能感兴趣的:(深度学习,pytorch,计算机视觉)