Resnet网络回归的简单实现

注:参考《动手学深度学习》一书

构建的网络模型是ResNet-18,即4个残差块,每块里面有四层卷积(不包括用以改变通道数的1×1卷积层),以及最开始的卷积层和最后的全连接层,总共18层

数据集类型为简单的array或pd.DataFrame类型的二维表或二维矩阵

1、加载测试数据

1.1、构建训练数据的X-y的Dataset类,用以后续的批处理和shuffle

class AnalysisDataset(Dataset):
    def __init__(self, in_data, column_X, column_Y):
        self.data = in_data
        self.column_X = column_X
        self.column_Y = column_Y
    def __len__(self):
        return len(self.data)
    def __getitem__(self, index):
        X = self.data[self.column_X]
        y = self.data[self.column_Y]
        tensor_X = torch.from_numpy(np.array(X.iloc[index])).float()
        tensor_y = torch.from_numpy(np.array(y.iloc[index])).float()
        return tensor_X.view(1, tensor_X.shape[0], 1), tensor_y

1.2、构建预测数据的X的Dataset类

class PredictDataset(Dataset):
    def __init__(self, in_data, column_X):
        self.data = in_data
        self.column_X = column_X
    def __len__(self):
        return len(self.data)
    def __getitem__(self, index):
        X = self.data[self.column_X]
        tensor_X = torch.from_numpy(np.array(X.iloc[index])).float()
        return tensor_X.view(1, tensor_X.shape[0], 1)

1.3 导入训练测试数据

def loadData(batch_size, X, y):

    if not isinstance(X, pd.DataFrame):
        X = pd.DataFrame(X, columns=list(np.linspace(0, X.shape[1] -1, X.shape[1])))

    if not isinstance(y, pd.DataFrame):
        y = pd.DataFrame(y, columns=list(np.linspace(X.shape[1], X.shape[1] + y.shape[1] - 1, y.shape[1])))
    column_X = X.columns
    column_y = y.columns
    temp_df = X.join(y)
    train_set, test_set = train_test_split(temp_df, test_size=0.2, random_state=10, shuffle=True)

    torch_trainset = AnalysisDataset(train_set, column_X, column_y)
    torch_testset = AnalysisDataset(test_set, column_X, column_y)
    # 批量导入

    if sys.platform.startswith("win"):
        works_num = 1
    else:
        works_num = 4

    train_batch = Data.DataLoader(torch_trainset, batch_size=batch_size, shuffle=True, num_workers=works_num)
    # for X, y in train_batch:
    #     print(X.shape)
    #     print(y.shape)
    test_batch = Data.DataLoader(torch_testset, batch_size=batch_size, shuffle=False, num_workers=works_num)

    return train_batch, test_batch

2、构建ResNet网络

2.1 相关工具计算类

class GlobalAvgPool2d(torch.nn.Module):
    # 全局平均池化层可通过将池化窗口形状设置成输入的高和宽实现
    def __init__(self):
        super(GlobalAvgPool2d, self).__init__()
    def forward(self, x):
       return F.avg_pool2d(x, kernel_size=x.size()[2:])

class FlattenLayer(torch.nn.Module):
    # 用于全连接层
    def __init__(self):
        super(FlattenLayer, self).__init__()
    def forward(self, x):
        # x shape: (batch_size, *, *, ...)
        return x.view(x.shape[0], -1)

2.2、基本框架

def ResNet():
    model = torch.nn.Sequential(
        torch.nn.Conv2d(1, 4, kernel_size=2, padding=1, stride=1),
        torch.nn.BatchNorm2d(4),
        torch.nn.ReLU(),
        torch.nn.MaxPool2d(kernel_size=2)
    )

    # 添加残差层
    # ...... #
    
    model.add_module("global_avg_pool", GlobalAvgPool2d())
    model.add_module("fc", torch.nn.Sequential(FlattenLayer(), torch.nn.Linear(32, 2)))

2.3 残差块

def resnet_block(in_channels, out_channels, num_residuals, first_block=False):
    '''
    :param in_channels: 输入层通道数
    :param out_channels: 输出层通道数
    :param num_residuals: 残差层数
    :param first_block: 是否是第一个resnet块
    :return: 
    '''
    # 第一个模块的通道数同输入通道数一致。
    # 由于之前已经使用了步幅为2的最大池化层,所以无须减小高和宽。
    # 之后的每个模块在第一个残差块里将上一个模块的通道数翻倍,并将高和宽减半
    if first_block:
        # 第一个块 输入和输出的通道数需一致
        assert in_channels == out_channels
    block = []
    for i in range(num_residuals):
        if i == 0 and not first_block:
            block.append(Residual(in_channels, out_channels, use_1x1conv=True, stride=1))
        else:
            block.append(Residual(out_channels, out_channels))
    return torch.nn.Sequential(*block)

2.3.1 核心类Residual

class Residual(torch.nn.Module):
    def __init__(self, in_channels, out_channels, use_1x1conv=False, stride=1):
        '''
        :param in_channels: 输入的通道数
        :param out_channels: 输出的通道数
        :param use_1x1conv: 是否使用1*1卷积层 
        :param stride: 步长
        '''
        super(Residual, self).__init__()
        # kernel_size=3, padding=1, stride=1保证输入与输出宽高一致
        self.conv1 = torch.nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1, stride=stride)
        self.conv2 = torch.nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1)
        if use_1x1conv:
            self.conv3 = torch.nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride)
        else:
            self.conv3 = None
        # 批量归一化
        self.b1 = torch.nn.BatchNorm2d(out_channels)
        self.b2 = torch.nn.BatchNorm2d(out_channels)

    def forward(self, X):
        Y = F.relu(self.b1(self.conv1(X)))
        Y = self.b2(self.conv2(Y))
        if self.conv3 is not None:
            X = self.conv3(X)
        # 输出和输入,此时Y = F(X) - X为残差层的输出,所以残差层实际拟合的是F(X)-X
        return F.relu(Y + X)

3、训练验证模型

3.1 训练模型

def train(model, train_batch, test_batch, batch_size, optimizer, device, num_epochs):
    model = model.to(device)
    print("run in " , device)

    # 损失函数,MSE函数
    loss = torch.nn.MSELoss()

    for epoch in range(num_epochs):
        train_loss_sum, train_rmse_sum, n, batch_count = 0.0, 0.0, 0, 0
        start = time.time()

        for X, y in train_batch:
            # 转置
            X = X.to(device)
            y = y.to(device)


            # 前向计算
            y_pre = model(X)

            l = loss(y_pre, y)

            # 梯度清零
            optimizer.zero_grad()

            l.backward()
            optimizer.step()

            train_loss_sum += l.cpu().item()
            train_rmse_sum += torch.sqrt(((y_pre-y)**2).sum()).cpu().item()
            n += y.shape[0]
            batch_count += 1

        test_rmse = evaluate_rmse(test_batch, model)

        print("epoch:%d, loss:%.4f, train_rmse:%.3f, test_rmse %.3f, cost: %.1f sec" %
              (epoch + 1, train_loss_sum / batch_count, train_rmse_sum / n, test_rmse, time.time() - start))

3.1.1 计算rmse的函数

def evaluate_rmse(data_batch, model, device = None):
    if device is None and isinstance(model, torch.nn.Module):
        device = list(model.parameters())[0].device

    rmse_sum, n = 0, 0

    with torch.no_grad():
        for X, y in data_batch:
            if isinstance(model, torch.nn.Module):
                # 评估模式,关闭dropout
                model.eval()
                rmse_sum += torch.sqrt(((model(X.to(device)) - y) ** 2).sum()).cpu().item()
                # 改回训练模式
                model.train()
            else:
                # 自定义模型
                if ('is_training' in model.__code__.co_varnames):
                    # 如果有is_training这个参数
                    # 将is_training设置成False
                    rmse_sum += torch.sqrt(((model(X.to(device), is_training=False) - y) ** 2).sum()).cpu().item()
                else:
                    rmse_sum += torch.sqrt(((model(X.to(device)) - y) ** 2).sum()).cpu().item()
            n += y.shape[0]
    return rmse_sum / n

3.2 验证

def validation(model, test_batch, device=None):
    if device is None and isinstance(model, torch.nn.Module):
        device = list(model.parameters())[0].device

    predX, predy = iter(test_batch).next()

    rmse_sum, n = 0, 0

    with torch.no_grad():
        if isinstance(model, torch.nn.Module):
            rmse_sum += torch.sqrt(((model(predX.to(device)) - predy) ** 2).sum()).cpu().item()
        else:
            if ('is_training' in model.__code__.co_varnames):
                # 如果有is_training这个参数
                # 将is_training设置成False

                rmse_sum += torch.sqrt(((model(predX.to(device), is_training=False) - predy) ** 2).sum()).cpu().item()
            else:
                rmse_sum += torch.sqrt(((model(predX.to(device)) - predy) ** 2).sum()).cpu().item()
        n += predy.shape[0]
        # print("pre:", model(predX))
    return rmse_sum / n

4、预测

def predict(model, df_X, batch_size, device=None):
    ''' 预测
    :param model: 训练好的resnet模型
    :param df_X: 待预测的自变量, dataframe类型
    :return: 
    '''
    if device is None and isinstance(model, torch.nn.Module):
        device = list(model.parameters())[0].device

    if not isinstance(df_X, pd.DataFrame):
        df_X = pd.DataFrame(df_X, columns=list(np.linspace(0, df_X.shape[1] -1, df_X.shape[1])))

    predict_dataset = PredictDataset(df_X, df_X.columns)

    if sys.platform.startswith("win"):
        works_num = 1
    else:
        works_num = 4

    predict_batch = Data.DataLoader(predict_dataset, batch_size=batch_size, shuffle=False, num_workers=works_num)

    estimated_Y = torch.tensor([])
    for X in predict_batch:
        X = X.to(device)

        temp_Y = model(X)
        # 合并
        estimated_Y = torch.cat([estimated_Y, temp_Y], dim=0)
    estimated_Y = estimated_Y.detach().numpy()
    return estimated_Y

5、程序入口

if __name__ == "__main__":
    batch_size  = 100
    x = np.random.randn(1000, 8)
    y = np.asarray(
        [
            [0.8, 0.4],
            [0.4, 0.3],
            [0.34, 0.45],
            [0.67, 0.32],
            [0.88, 0.67],
            [0.78, 0.77],
            [0.55, 0.66],
            [0.55, 0.43],
            [0.54, 0.1],
            [0.1, 0.5],
        ] * 100
    )
    train_batch, test_batch = loadData(batch_size, x, y)
    # for X, y in train_batch:
    #     print("train_X", X.shape)
    #     print("train_y", y.shape)
    # for X, y in test_batch:
    #     print("test_X", X.shape)
    #     print("test_y", y.shape)


    # 加载模型
    lr, num_epochs = 0.001, 5
    model = ResNet()
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)

    # 训练模型
    train(model, train_batch, test_batch, batch_size, optimizer, device, num_epochs)

    # 验证
    valid_rmse = validation(model, test_batch)
    print("validation rmse:", valid_rmse)

    # 预测
    x = np.random.randn(1000, 8) * 1.1
    print(predict(model, x, batch_size))

6、结果

  • 18
    点赞
  • 121
    收藏
    觉得还不错? 一键收藏
  • 32
    评论
姿态估计是指通过计算机视觉技术从图像或视频中提取物体的姿态信息,如旋转角度、平移向量、尺度因子等。其中的热力图回归是一种常用的姿态估计方法,其主要思路是将姿态估计问题转化为一个回归问题,通过训练神经网络来直接预测出目标物体的关键点热力图。 具体来说,热力图回归实现步骤如下: 1. 数据准备:首先需要准备包含目标物体的图像或视频数据,以及标注好的关键点位置数据。这些数据可以通过手工标注或使用自动化工具进行标注。 2. 热力图生成:接下来,需要根据标注的关键点位置数据生成热力图。热力图是一个与图像尺寸相同的二维数组,其中每个元素表示该位置是目标物体的某个关键点的概率。生成热力图的方法有多种,例如高斯分布、线性分布等。 3. 神经网络训练:然后,将生成的热力图作为输入,训练一个神经网络来直接预测目标物体的关键点位置。常用的神经网络结构包括卷积神经网络(CNN)和残差网络ResNet)等。 4. 关键点位置预测:在测试阶段,将目标物体的图像输入训练好的神经网络中,即可得到预测的关键点热力图。然后,根据热力图中的最大值点作为预测的关键点位置,即可得到目标物体的姿态信息。 总体来说,热力图回归方法通过直接回归关键点热力图来解决姿态估计问题,具有训练简单、预测效果好等优点,已被广泛应用于计算机视觉领域。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 32
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值