【一起深度学习——修改昨天叶子】

目的:将叶子进行分类。

实现步骤:

1、数据处理:

对数据进行处理。由于数据图片(测试集,训练集,验证集全都放在了images中,应将其进行分开)
处理步骤:
1、 读取csv文件,解析其中的地址信息。
2、 创建 训练集 和 测试集 的文件夹,用于存放对应的图片数据。
3、 遍历 训练数据 和 测试数据 中的图片地址,从images复制到对应文件夹中

定义数据处理函数:

#定义读取数据的函数
def read_images():
    # 读取 csv文件。
    train_df = pd.read_csv("train.csv")
    test_df = pd.read_csv("test.csv")

    # 创建 保存训练集和测试集的文件夹
    os.makedirs(name="train_images",exist_ok=True)  #训练集
    os.makedirs(name="val_images",exist_ok=True)    #验证集
    os.makedirs(name="test_images",exist_ok=True)   #测试集

    #设置验证集的占比
    val_ration = 0.2

    #获取训练集的样本数量。
    num_samples = len(train_df)
    #验证集的样本数量。
    num_val_samples = int(num_samples * val_ration)
    # 取出验证集的所有索引
    val_indices = random.sample(range(num_samples),num_val_samples)
    # print(val_indices)
    #复制images中的数据到 训练集中
    for index,row in train_df.iterrows():
        # print(row[1])  #打印看一下数据格式
        # print(index)
        # print(row['image'])
        image_path = row['image']
        label = row['label']
        # 若下标在验证集的索引中:
        if index in val_indices:
            target_dir = os.path.join("val_images",label)
        else:
            target_dir = os.path.join("train_images", label)
        os.makedirs(target_dir,exist_ok=True)
        shutil.copy(image_path,target_dir)

    #复制images中的数据到 训练集中
    for row in train_df.iterrows():
        #print(row[1])  #打印看一下数据格式
        # print(row[1]['image'])
        image_path = row[1]['image']
        label = row[1]['label']
        target_dir = os.path.join("train_images",label)
        os.makedirs(target_dir,exist_ok=True)
        shutil.copy(image_path,target_dir)
    #复制images中的数据到 测试集 中
    for row in test_df.iterrows():
        #print(row[1])  #打印看一下数据格式
        # print(row[1]['image'])
        image_path = row[1]['image']
        target_dir = os.path.join("test_images/test_data")
        os.makedirs(target_dir,exist_ok=True)
        shutil.copy(image_path,target_dir)
# read_images()

对于读取测试集时,
target_dir = os.path.join(“test_images/test_data”) 这里的路径我为啥要设置多一个/test_data呢,因为如果不设置这个文件夹的话,后续使用Dataloader.datasets.ImageFolder 就读取不了,但是我现在不知道如何直接加载一个文件夹中图片,所以暂时只能这样。

2、加载数据

tran = torchvision.transforms.Compose([
        torchvision.transforms.ToTensor(),
        # torchvision.transforms.Resize((96,96))
   ]
)

#读取数据
train_data = torchvision.datasets.ImageFolder(root="train_images",transform=torchvision.transforms.ToTensor())
val_data = torchvision.datasets.ImageFolder(root="val_images",transform=torchvision.transforms.ToTensor())
test_data = torchvision.datasets.ImageFolder(root="test_images",transform=torchvision.transforms.ToTensor())

batch_sz = 256
#加载数据
train_loader = DataLoader(train_data,batch_sz,shuffle=True,drop_last=True)
val_loader = DataLoader(val_data,batch_sz,shuffle=False,drop_last=False)
test_loader = DataLoader(test_data,batch_sz,shuffle=False,drop_last=False)
# for (images,labels) in train_loader:
#     print(images.shape)
# 从上边这段代码可知 images维度torch.Size([128, 3, 224, 224])  (样本数量,通道,高度,宽度)

3、 定义残差块

class ResidualBlock(nn.Module):
    def __init__(self,in_channels,out_channels,strides=1):
        super(ResidualBlock,self).__init__()
        # 设置卷积核为3,padding =1能够保持大小不变
        self.conv1 = nn.Conv2d(in_channels,out_channels,kernel_size=3,stride=strides,padding=1)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = nn.Conv2d(out_channels,out_channels,kernel_size=3,stride=1,padding=1)
        self.bn2 = nn.BatchNorm2d(out_channels)

        #如果
        self.downsample = None
        # 因为步幅不为 1 的话 就会导致形状大小发生变化。
        if strides != 1 or in_channels != out_channels:
            self.downsample = nn.Sequential(
                nn.Conv2d(in_channels,out_channels,kernel_size=1,stride=strides),
                nn.BatchNorm2d(out_channels)
            )

    def forward(self,X):
        Y = self.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.downsample is not  None:
            X = self.downsample(X)
        Y += X
        return self.relu(Y)

4、定义Resnet模型。

原理图:
在这里插入图片描述

定义Resnet18模型。
第一层:7x7 的卷积层,64 个输出通道,步幅为 2。
最大池化层:3x3 的池化核,步幅为 2,用于下采样。
4 个阶段(layers),每个阶段包含若干个残差块(ResidualBlock)。
第一个阶段:2 个残差块。 每个残差块包括两个卷积。
第二个阶段:2 个残差块。
第三个阶段:2 个残差块。
第四个阶段:2 个残差块。
全局平均池化层:对特征图进行全局平均池化,将每个通道的特征图变成一个值。
全连接层:将全局平均池化层的输出连接到输出类别数量的全连接层,用于分类

class Resnet18(nn.Module):
    def __init__(self):
        super(Resnet18, self).__init__()
        # H2 = (224- 7 + 3 * 2) / 2 +1 =112
        # [128,3,224,224] => [128,64,112,112]
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU(inplace=True)
        # H2 = (112 - 3 + 2*1 )/2 +1 = 56
        # [128,64,112,112] => [128,64,56,56]
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        # 添加残缺块 1,每个残缺块呢,当block2为2时,会产生2个残缺块,执行四次卷积。
        # H2 = (56 - 3+2*1)/1 +1 = 56
        # 第一个残缺块:[128,64,56,56] => [128,64,56,56]
        #             [128,64,56,56] => [128,64,56,56]
        # 第二个残缺块:[128,64,56,56] => [128,64,56,56]
        #             [128,64,56,56] => [128,64,56,56]
        self.layer1 = self.make_layer(64, 64, 2)
        # 添加残缺块 2
        # 第一个残缺块:H2 = (56 - 3 +2*1)/2+1 = 28
        #            [128,64,56,56] => [128,128,28,28]
        #            H2 = (28 - 3 + 2*1)/1 +1 =28
        #            [128,128,56,56] => [128,128,28,28]
        #     X :    [128,64,56,56] => [128,128,28,28]  只是将X的通道数和形状变为与 Y一致,可相加。
        #第二个残缺块:[128,128,28,28] => [128,128,28,28]
        #            [128,128,28,28] => [128,128,28,28]
        self.layer2 = self.make_layer(64, 128, 2, stride=2)
        # 添加残缺块 3
        # 第一个残缺块:H2 = (28-3 + 2*1)/2 +1 = 14
        #               [128,128,28,28] => [128,256,14,14]
        #               [128,256,14,14] => [128,256,14,14]
        #       x:      [128,128,28,28] => [128,256,14,14]
        # 第二个残缺块:H2 = (14-3 +2*1) /1 +1 = 14
        #                [128,256,14,14] => [128,256,14,14]
        #                 [128,256,14,14] => [128,256,14,14]
        self.layer3 = self.make_layer(128, 256, 2, stride=2)
        # 添加残缺块 4
        # 第一个残缺块:H2 = (14 - 3 +2*1)/2 +1 = 7
        #               [128,256,14,14] =>[128,512,7,7]
        #               [128,512,7,7] => [128,512,7,7]
        #       X :     [[128,256,14,14]] => [128,512,7,7]
        # 第二个残缺块:H2 = (7-3+2*1)/1+1 =7
        #               [128,512,7,7] => [128,512,7,7]
        #               [128,512,7,7] => [128,512,7,7]
        self.layer4 = self.make_layer(256, 512, 2, stride=2)
        # 池化层,采用自适应池化(指定特征图的高度,宽度)
        # [128,512,7,7] => [128,512,1,1]
        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
        # 全连接层
        #  [128,512] => [128,176]
        self.fc = nn.Linear(512, 176)

    def make_layer(self, in_channels, out_channels, blocks, stride=1):
        layers = []
        layers.append(ResidualBlock(in_channels, out_channels, stride))
        for _ in range(1, blocks):
            # 这里的stride 默认为1
            layers.append(ResidualBlock(out_channels, out_channels))
        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x = self.avgpool(x)
        #   [128,512,1,1] => [128,512*1*1]
        x = torch.flatten(x, 1)
        x = self.fc(x)
        return x

5、修改训练以及评估函数:

lr = 1e-3
num_epochs =10
#定义训练函数
def train2(net,train_iter,test_iter,num_epochs,lr,device):
    net = net.to(device)
    optimizer = optim.Adam(net.parameters(),lr)
    loss = nn.CrossEntropyLoss().to(device)
    # xlim =[1,num_epochs] 表示横坐标的范围是从1 到num_epochs
    animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
                            legend=['train_loss', 'train_acc','val_acc'])
    timer, num_batches = d2l.Timer(), len(train_iter)
    for epoch in range(num_epochs):
        print(f"开始第{epoch}次训练")
        metric = d2l.Accumulator(3)
        for i,(X,y) in enumerate(train_iter):
            timer.start()
            X,y = X.to(device),y.to(device)
            optimizer.zero_grad()
            output = net(X)
            l = loss(output,y).sum() #损失
            l.backward()
            optimizer.step()
            with torch.no_grad():
                metric.add(l,d2l.accuracy(output,y),y.shape[0])
            timer.stop()
            #更新条件
            if (i+1) % (num_batches // 5) == 0 or i ==num_batches - 1:
                #按顺序 legend=['train_loss', 'train_acc','val_acc'])进行添加的。
                animator.add(epoch + (i + 1) / num_batches,
                             (metric[0] / metric[2],metric[1] / metric[2], None))
                # animator.add(epoch + (i + 1) / num_batches,
                #              (metric[1] / metric[2], None))
        measures = f'train_loss {metric[0] / metric[2]:.3f},train_acc {metric[1] / metric[2]:.3f}'
        with torch.no_grad():
            val_acc = evaluate_accuracy_gpu(net,test_iter,device)
            animator.add(epoch + 1, (None,None,val_acc))
            print(measures)
            print("val_acc:", val_acc)


loss = nn.CrossEntropyLoss(reduction='none')
# def evaluate_gpu(data_iter, net,device):
#     l_sum, n = 0.0, 0
#     for X, y in data_iter:
#         X, y = X.to(device), y.to(device)
#         outputs = net(X)
#         l = loss(outputs, y)
#         l_sum += l.sum()
#         n += y.numel()  # numel()函数:返回数组中元素的个数
#     return l_sum / n

def evaluate_accuracy_gpu(net, data_iter, device=None):
    if isinstance(net, nn.Module):
        net.eval()   #设置为评估模式
        if not device:
            device = next(iter(net.parameters())).device
    metric = d2l.Accumulator(2)

    with torch.no_grad():
        for X, y in data_iter:
            if isinstance(X, list):
                X = [x.to(device) for x in X]
            else:
                X = X.to(device)
            y = y.to(device)
            metric.add(d2l.accuracy(net(X), y), d2l.size(y))
    return metric[0] / metric[1]


net = Resnet18()
train2(net,train_loader,val_loader,10,lr,device)

7、输出结果:

好家伙,比昨天好多了。
请添加图片描述

train_loss 0.018,train_acc 0.804
val_acc: 0.8115731618790832

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值