pytorch学习笔记(16)

# 树叶分类

此代码参考simple resnet baseline | Kaggle,我仅仅对代码做了一些注释

labels_dataframe = pd.read_csv('./leave_train.csv')  # 读取文件
labels_dataframe.describe() # count:每列的非缺失值数量。unique:每列中不同数值(唯一值)的数量。top:每列中的众数,即在该列中出现次数最多的数值。freq:众数出现的频数,即众数在该列中出现的次数。

def barw(ax):  # ax表示表示绘图的坐标轴(axis)对象。
    for p in ax.patches:  # ax.patches包含了图表中的所有图形元素,每个条形都是其中的一部分。
        val = p.get_width()  # 获取当前条形的宽度,也就是条形的高度,表示当前类别的频数。
        x = p.get_x() + p.get_width()  # 获取当前条形的x位置,即条形的右边缘的位置,表示频数值的位置。
        y = p.get_y() + p.get_height() / 2  # 获取当前条形的y位置,即条形的垂直中心位置,表示频数值的位置。
        fontsize = 7  # 调整字体大小
        ax.annotate(round(val, 2), (x, y), fontsize=fontsize)  # 使用ax.annotate函数在当前条形的位置(x, y)处添加标签,标签的内容是频数值(通过round(val, 2)四舍五入保留两位小数)。这样就将频数值标签添加到了每个条形上。



plt.figure(figsize = (15,30))
ax0 =sns.countplot(y=labels_dataframe['label'],order=labels_dataframe['label'].value_counts().index)  # 按降序排列
barw(ax0)
ax0.set_ylabel('Y-axis', fontsize=7)
plt.show()

 


leaves_labels = sorted(list(set(labels_dataframe['label'])))  # set(...)将标签列中的唯一标签提取出来,使用 set 函数去重,得到一个包含唯一标签的集合(set)。list(...):将唯一标签的集合转换为一个列表,sorted对标签列表进行排序,得到按字母顺序或数值大小升序排列的标签列表。
n_classes = len(leaves_labels)  # 计算不同标签的数量,也就是类别的数量。这将得到数据集中不同类别的总数。
print(n_classes)
leaves_labels[:10]  # 取前 10 个标签,以便查看数据集中的一部分不同类别

总共有176个类别 

class_to_num = dict(zip(leaves_labels, range(n_classes)))  # 将标签转成数字
class_to_num

 

# 再转换回来,方便最后预测的时候使用
num_to_class = {v : k for k, v in class_to_num.items()}
class LeavesData(Dataset):
    def __init__(self, csv_path, file_path, mode='train', valid_ratio=0.2, resize_height=256, resize_width=256):
        """
        Args:
            csv_path: CSV 文件的路径,其中包含图像文件的名称和标签。
            file_path: 图像文件的存储路径。
            mode: 数据集模式,可以是 'train'(训练集)、'valid'(验证集)或'test'(测试集)。
            valid_ratio: 验证集的比例,用于将数据集划分为训练集和验证集。
            resize_height 和 resize_width: 需要调整的图像尺寸。
        """

        # 需要调整后的照片尺寸,我这里每张图片的大小尺寸不一致#
        self.resize_height = resize_height  # 存储了需要调整的图像尺寸。
        self.resize_width = resize_width

        self.file_path = file_path  # 存储了图像文件的存储路径。
        self.mode = mode  # 存储了数据集的模式,可以是 'train'、'valid' 或 'test'。

        # 读取 csv 文件
        # 利用pandas读取csv文件
        self.data_info = pd.read_csv(csv_path, header=None)  # header=None是去掉表头部分
        # 计算 length
        self.data_len = len(self.data_info.index) - 1  # 计算数据集中的总行数。。由于CSV文件的第一行通常包含表头信息而不是样本数据,因此需要减去1,以得到真实的样本数量。
        self.train_len = int(self.data_len * (
                1 - valid_ratio))  # 计算训练集的长度。valid_ratio 表示验证集的比例,通常在0和1之间。计算方法是将 self.data_len 乘以 (1 - valid_ratio),然后使用 int 函数将结果转换为整数。

        if mode == 'train':  # 如果 mode 是 'train',则提取训练集的图像文件名和标签。
            # 第一列包含图像文件的名称
            self.train_image = np.asarray(
                self.data_info.iloc[1:self.train_len,
                0])  # self.data_info.iloc[1:,0]表示读取第一列,从第二行开始到train_len, np.asarray():将结果转换为 NumPy 数组。
            # 第二列是图像的 label
            self.train_label = np.asarray(self.data_info.iloc[1:self.train_len, 1])
            self.image_arr = self.train_image  # 这两行代码将获得的图像文件名和标签信息保存到 self.image_arr 和 self.label_arr 实例变量中。
            self.label_arr = self.train_label
        elif mode == 'valid':  # 如果 mode 是 'valid',则提取验证集的图像文件名和标签。
            self.valid_image = np.asarray(self.data_info.iloc[self.train_len:,
                                          0])  # 我们选择了行索引从 self.train_len (训练集)开始到最后一行的所有行(包括self.train_len行),以及第0列。
            self.valid_label = np.asarray(
                self.data_info.iloc[self.train_len:, 1])  # 选择了从第self.train_len行开始到最后一行的所有行(包括第self.train_len行),以及第1列。
            self.image_arr = self.valid_image
            self.label_arr = self.valid_label
        elif mode == 'test':  # 如果 mode 是 'test',则提取测试集的图像文件名。
            self.test_image = np.asarray(
                self.data_info.iloc[1:, 0])  # 选择 self.data_info 数据框的所有行(从第1行到最后一行,也就是整个 CSV 文件)以及第0列。
            self.image_arr = self.test_image

        self.real_len = len(self.image_arr)

        print('Finished reading the {} set of Leaves Dataset ({} samples found)'
              .format(mode, self.real_len))
        def __getitem__(self, index):
        # 从 image_arr中得到索引对应的文件名
        single_image_name = self.image_arr[index]

        # 读取图像文件
        img_as_img = Image.open(self.file_path + single_image_name)

        # 如果需要将RGB三通道的图片转换成灰度图片可参考下面两行
        #         if img_as_img.mode != 'L':
        #             img_as_img = img_as_img.convert('L')

        # 设置好需要转换的变量,还可以包括一系列的nomarlize等等操作
        if self.mode == 'train':
            transform = transforms.Compose([
                transforms.Resize((224, 224)),
                transforms.RandomHorizontalFlip(p=0.5),  # 随机水平翻转 选择一个概率
                transforms.ToTensor()
            ])
        else:
            # valid和test不做数据增强
            transform = transforms.Compose([
                transforms.Resize((224, 224)),
                transforms.ToTensor()
            ])

        img_as_img = transform(img_as_img)

        if self.mode == 'test':
            return img_as_img
        else:
            # 得到图像的 string label
            label = self.label_arr[index]  # self.label_arr存储的是标签
            # number label
            number_label = class_to_num[label]

            return img_as_img, number_label  # 返回每一个index对应的图片数据和对应的label

    def __len__(self):
        return self.real_len

 

 

train_path = './input/leave_train.csv'
test_path = './input/leave_test.csv'
img_path = './input/'

train_dataset = LeavesData(train_path, img_path, mode='train')
val_dataset = LeavesData(train_path, img_path, mode='valid')
test_dataset = LeavesData(test_path, img_path, mode='test')
print(train_dataset)
print(val_dataset)
print(test_dataset)

# 定义data loader
train_loader = torch.utils.data.DataLoader(  # 加载训练集数据
    dataset=train_dataset,
    batch_size=8,  # 批量大小为8
    shuffle=False,  # 不随机打乱
    num_workers=5  # 工作进程为5  这个只能在liunx下运行,在windows下运行会报多线程错误,如果是winndows注释掉即可
)

val_loader = torch.utils.data.DataLoader(  # 验证数据
    dataset=val_dataset,
    batch_size=8,
    shuffle=False,
    num_workers=5
)
test_loader = torch.utils.data.DataLoader(  # 测试集数据
    dataset=test_dataset,
    batch_size=8,
    shuffle=False,
    num_workers=5
)

 

def im_convert(tensor):
    """ 展示数据"""

    image = tensor.to("cpu").clone().detach()  # 首先将张量复制到 CPU,并去除梯度信息
    image = image.numpy().squeeze()  # 将张量转换为 NumPy 数组并去掉单一的通道维度
    image = image.transpose(1, 2, 0)  # 调整通道维度的顺序,将图像数据排列为 (height, width, channels)
    image = image.clip(0, 1)  # 裁剪像素值,确保在 [0, 1] 范围内

    return image


fig = plt.figure(figsize=(20, 12))  # 创建一个 Matplotlib 图形对象,设置图形大小为 20x12
columns = 4  # 定义子图列数
rows = 2  # 定义子图行数

for inputs, classes in val_loader:
    for idx in range(columns * rows):
        ax = fig.add_subplot(rows, columns, idx + 1, xticks=[], yticks=[])
        ax.set_title(num_to_class[int(classes[idx])])
        plt.imshow(im_convert(inputs[idx]))
    break  # 这里加上break是为了只展示一个批次的数据
plt.show()

for idx in range(columns * rows):
    ax = fig.add_subplot(rows, columns, idx + 1, xticks=[], yticks=[])  # 添加子图到 Matplotlib 图形中,设置 x 和 y 轴的刻度为空
    ax.set_title(num_to_class[int(classes[idx])])  # 设置子图标题为类别标签
    plt.imshow(im_convert(inputs[idx]))  # 显示图像,使用 im_convert 函数将 PyTorch 张量转换为可视化的图像
plt.show()

 

def get_device():
    return 'cuda' if torch.cuda.is_available() else 'cpu'


device = get_device()
print(device)

def set_parameter_requires_grad(model, feature_extracting):
    if feature_extracting:
        model = model
        for param in model.parameters():
            param.requires_grad = False


# resnet34模型
def res_model(num_classes, feature_extract=False, use_pretrained=True):  # num_classes:用于指定模型的输出类别数。feature_extract:一个布尔值,如果设置为True,将冻结模型的前面一些层,只训练分类器层。如果设置为False,将对整个模型进行微调。use_pretrained:一个布尔值,如果设置为True,将使用在大型图像数据集上预训练的权重来初始化模型。如果设置为False,将使用随机初始化的权重。
    model_ft = models.resnet34(pretrained=use_pretrained)  # 首先创建一个ResNet-34模型,如果use_pretrained为True,它将加载在大型图像数据集上预训练的权重。
    set_parameter_requires_grad(model_ft, feature_extract)  # 然后,根据feature_extract参数的值,它会冻结模型的前面一些层(如果feature_extract为True),或者将整个模型的参数都设置为可训练(如果feature_extract为False)。
    num_ftrs = model_ft.fc.in_features  # 最后,它会更改模型的分类器层,以适应给定的num_classes,并返回构建好的ResNet-34模型。
    model_ft.fc = nn.Sequential(nn.Linear(num_ftrs, num_classes))

    return model_ft
# 超参数
learning_rate = 3e-4
weight_decay = 1e-3
num_epoch = 50
model_path = './pre_res_model.ckpt'

 

# 初始化模型
model = res_model(176)  # 创建一个ResNet-34模型,用于176个不同的类别的图像分类任务。
model = model.to(device)  # model = model.to(device): 将模型移动到指定的设备(通常是GPU)上,以便在该设备上执行计算。
model.device = device

criterion = nn.CrossEntropyLoss()  # 定义损失函数,这里使用交叉熵损失来衡量模型性能。


optimizer = torch.optim.Adam(model.parameters(), lr = learning_rate, weight_decay=weight_decay)  #  创建一个Adam优化器来更新模型的参数,可以调整学习率(learning_rate)和权重衰减(weight_decay)等超参数。

n_epochs = num_epoch  # 指定训练的总轮数。

best_acc = 0.0  # 用于记录最佳验证集准确率。
for epoch in range(n_epochs):
    # ---------- Training ----------
    model.train()   # model.train(): 将模型设置为训练模式,启用一些训练特定的模块,比如Dropout。
    train_loss = []  # 记录损失
    train_accs = []  # 记录精度
   .
    for batch in tqdm(train_loader):
        
        imgs, labels = batch  # 获取图像数据和标签。
        imgs = imgs.to(device)  # 将数据和标签移动到指定的设备。
        labels = labels.to(device)
        # 要确保你的模型在同一个设备(GPU)上
        logits = model(imgs)
        # Calculate the cross-entropy loss.
        # We don't need to apply softmax before computing cross-entropy as it is done automatically.
        loss = criterion(logits, labels)
        
      
        optimizer.zero_grad()  # 清零之前参数的梯度。
       
        loss.backward()  # 计算损失的梯度
       
        optimizer.step()  # 更新模型参数。
        
        acc = (logits.argmax(dim=-1) == labels).float().mean()  # 计算并记录当前批次的准确率、损失等信息。

        # 汇总并输出本轮训练的平均损失和准确率。
        train_loss.append(loss.item())
        train_accs.append(acc)
        
    # 计算平均损失和精度
    train_loss = sum(train_loss) / len(train_loss)
    train_acc = sum(train_accs) / len(train_accs)

    # Print the information.
    print(f"[ Train | {epoch + 1:03d}/{n_epochs:03d} ] loss = {train_loss:.5f}, acc = {train_acc:.5f}")
    
    
    # ---------- Validation ----------
    
    model.eval()  # 将模型切换到评估模式,禁用一些训练特定的模块,以便在验证集上进行评估。
    # These are used to record information in validation.
    valid_loss = []
    valid_accs = []
    
    # 逐批次迭代验证集。

    for batch in tqdm(val_loader):
        imgs, labels = batch
        # 在验证过程中我们不需要梯度。
        # 使用torch.no_grad()可以加速前向传播过程。这意味着在验证期间不会记录参数的梯度,这样可以节省内存和计算资源。
        with torch.no_grad():
            logits = model(imgs.to(device))
            
        # 我们仍然可以计算损失(但不会计算梯度)。这表示我们仍然可以评估模型的性能,但不会修改模型参数。
        loss = criterion(logits, labels.to(device))

        # 对当前epoch计算精度
        acc = (logits.argmax(dim=-1) == labels.to(device)).float().mean()

        # 记录损失和精度
        valid_loss.append(loss.item())
        valid_accs.append(acc)
        
    # 整个验证集的平均损失和准确率是记录值的平均值。这表示对验证集中所有样本的损失和准确率进行平均计算,以得出整体性能指标。
    valid_loss = sum(valid_loss) / len(valid_loss)
    valid_acc = sum(valid_accs) / len(valid_accs)

    # Print the information.
    print(f"[ Valid | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}")
    
    # 如果本轮验证的准确率比历史最佳准确率更高,就保存模型的权重到文件
    if valid_acc > best_acc:
        best_acc = valid_acc
        torch.save(model.state_dict(), model_path)
        print('saving model with acc {:.3f}'.format(best_acc))

 

## 预测
model = res_model(176)


model = model.to(device)
model.load_state_dict(torch.load(model_path))  # 加载之前保存的最佳权重参数(checkpoint)。

# 确保模型处于评估模式(eval mode)。在评估模式下,模型的某些模块,如Dropout或BatchNorm,会影响模型的行为。
model.eval()

# 初始化一个列表以存储预测结果。
predictions = []
# 迭代测试集,逐批次进行预测。
for batch in tqdm(test_loader):
    
    imgs = batch
    with torch.no_grad():
        logits = model(imgs.to(device))
    
    # 选择具有最大对数净值的类别作为预测,并将其记录下来。
    predictions.extend(logits.argmax(dim=-1).cpu().numpy().tolist())

preds = []
for i in predictions:
    preds.append(num_to_class[i])  # 将所有的预测结果(类别索引)转换为类别标签,并将这些预测结果与测试集的图像文件名结合在一起。

test_data = pd.read_csv(test_path)
test_data['label'] = pd.Series(preds)
submission = pd.concat([test_data['image'], test_data['label']], axis=1)  # 将预测结果保存到一个CSV文件中(saveFileName)。
submission.to_csv(saveFileName, index=False)
print("Done!!!!!!!!!!!!!!!!!!!!!!!!!!!")

最后的预测结果 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值