当前位置:AIGC资讯 > AIGC > 正文

房价预测模型代码笔记以及文心一言Q.A记录

导入库方面略过不提

第一部分:简易日期处理模块

def processdate(date):
    date_num = (int(date[:4]) - 2014)*12 + (int(date[4:6])-5)
    return date_num

processdate 函数

这个函数的作用是将输入的日期字符串转化为一个整数。它将日期中的年份和月份信息分别转化为从2014年开始的月数。例如,对于输入 '201806',函数会返回 (2018-2014)*12 + (6-5) = 48 + 1 = 49

日期从2014年5月开始,所以这个模块会计算出距离2014年5月过去了多少个月。

第二部分:数据集


class houseDataset(Dataset):
    def __init__(self, path, mode="train", feature_dim=5):
        with open(path,'r') as f:
            csv_data = list(csv.reader(f))
            for index in range(1, len(csv_data)):
                csv_data[index][1] = processdate(csv_data[index][1]) //日期处理
                csv_data[index][2] = str(eval(csv_data[index][2])) //这里的数据是价格,转为字符串形式
            x = np.delete(np.array(csv_data)[1:].astype(float), [0, 2, 16], axis=1)
//这里处理了特征项。去掉了第一行也就是特征的名称,然后去掉了0列(id)以及2列(price,实际上是每一组x对应的target)以及16列(zipcode,邮政编码。由于难以处理且在特征中无不可替代性而直接删去)。处理完后将这些数据转换为np的数组并转为浮点形式。
            y = np.array(csv_data)[1:, 2].astype(float)/10**6
//如上所述,第二列实际上是我们的target。取出target后除以10^6,将价格转为一个较小的数方便对比。
            self.x = torch.tensor(x)
            self.y = torch.tensor(y)
            self.x = (self.x - self.x.mean(dim=0,keepdim=True)) / self.x.std(dim=0,keepdim=True) //特征归一化
            print('Finished reading the {} set of house Dataset ({} samples found)'
                  .format(mode, len(self.x)))

    def __getitem__(self, item):
            return self.x[item].float(), self.y[item]
    def __len__(self):
        return len(self.x)

houseDataset 类

这个类用于从CSV文件中加载和处理数据。其中:

__init__ 方法首先读取CSV文件中的数据,并逐行进行处理。对于每行的第二个元素(索引为1的元素,即日期),使用 processdate 函数进行处理;对于每行的第三个元素,使用 eval 函数进行处理(注意这里使用 eval 可能存在安全风险,如果数据源不可靠的话)。然后,该方法创建输入 x 和目标输出 y,并对输入数据进行标准化处理。 __getitem__ 方法用于返回给定索引处的数据样本。 __len__ 方法返回数据集的长度。

笔记:数据处理逻辑已于代码注释给出,init函数实际上完成了数据集的导入、处理,并且将处理完的x和y作为数据集类型的全局变量供其它函数使用。getitem和len函数非常简单,实现的就是返回对应xy和数据集长度。

第三部分:用于预测的神经网络模型

class myNet(nn.Module):
    def __init__(self,inDim):
        super(myNet,self).__init__()//使用super修改nn.Module的初始化,这个步骤在数据集中就不用实现。
        self.fc1 = nn.Linear(inDim, 64)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(64, 1)
        # self.fc3 = nn.Linear(256,256)
        # self.fc4 = nn.Linear(inDim,1) /*inDim是你的特征维数,比如说你有5个特征,那么inDim就该输入为5。这里自定义了这个预测模型的网络层数,在未注释的版本中是将输入维度经过全连接层fc1,变化为64维的输出,此输出经过relu函数的非线性变换后再进入全连接层fc2,将64维降为1维即可输出。如果你有需要,可以像注释的两行一样自定义更多层数*/


    def forward(self, x):
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        # x = self.relu(x)
        #
        # x = self.fc3(x)
        # x = self.relu(x)
        #
        # x = self.fc4(x)
        # x = self.relu(x)

        if len(x.size()) > 1:
            return x.squeeze(1) //将经过网络的x降为一维,方便与y作对比。
        else:
            return x

myNet 类

这是一个用于房价预测的神经网络模型。其中:

__init__ 方法定义了网络的结构。该网络有两个全连接层(fc1 和 fc2),并在两层之间使用ReLU激活函数。 forward 方法定义了数据在网络中的前向传播过程。输入数据首先通过第一个全连接层 fc1,然后经过ReLU激活函数,再通过第二个全连接层 fc2。如果输出的维度大于1,就使用 squeeze 方法去掉维度为1的维度。

注意:

代码中有一些被注释掉的代码,这些代码可能是用于测试或调试的,但在当前的实现中并未使用。 在 houseDataset 类中,数据被标准化(即进行了Z-score归一化),这是一种常见的数据预处理方法,有助于模型更好地学习和收敛。 神经网络模型 myNet 的结构相对简单,可能适合处理一些较为简单的房价预测任务。如果需要处理更复杂的任务,可能需要增加更多的层或调整网络结构。

第四部分:训练与验证

def train_val(model, trainloader, valloader,optimizer, loss, epoch, device, save_):

    # trainloader = DataLoader(trainset,batch_size=batch,shuffle=True)
    # valloader = DataLoader(valset,batch_size=batch,shuffle=True)
    model = model.to(device)   
    plt_train_loss = []
    plt_val_loss = []
    val_rel = []
    min_val_loss = 100000
    for i in range(epoch):
        start_time = time.time()
        model.train()
        train_loss = 0.0
        val_loss = 0.0     //记录时间,进入训练模式并初始化训练损失和验证损失。
        for data in trainloader:
            optimizer.zero_grad()
            x, target = data[0].to(device), data[1].to(torch.float32).to(device)
            pred = model(x)
            bat_loss = loss(pred, target, model)
            bat_loss.backward()
            optimizer.step()
            train_loss += bat_loss.detach().cpu().item()
//在一个epoch中,每组数据首先清零上一组遗留的梯度,然后将x和y放到机器上。
//接下来在model中传入特征x,计算预测值pred并计算每一个batch的损失。计算完成后将损失反向传播,并使用优化器更新模型。最后累加单个批次的损失,先将这个批次的loss取下并用item取值。
        plt_train_loss. append(train_loss/trainloader.dataset.__len__())
//累加每个批次的损失,除以一个批次的训练集长度得平均损失。
        model.eval()
        with torch.no_grad():
            for data in valloader:
                val_x , val_target = data[0].to(device), data[1].to(device)
                val_pred = model(val_x)
                val_bat_loss = loss(val_pred, val_target, model)
                val_loss += val_bat_loss
                val_rel.append(val_pred)
        if val_loss < min_val_loss:
            torch.save(model, save_)
//验证模式与训练模式大同小异。重要区别在于,验证时由于数据集不可用于改进模型,因此应该在无梯度计算下运行,同时也不需要反向传播算法。如果本次验证集的损失小于曾经记录的最小损失,更新这个值并保存这次训练的模型。
        plt_val_loss. append(val_loss/valloader.dataset.__len__())

        print('[%03d/%03d] %2.2f sec(s) TrainLoss : %3.6f | valLoss: %3.6f' % \
              (i, epoch, time.time()-start_time, plt_train_loss[-1], plt_val_loss[-1])
              )

    plt.plot(plt_train_loss)
    plt.plot(plt_val_loss)
    plt.title('loss')
    plt.legend(['train', 'val'])
    plt.show()
//打印训练成果和损失图

这段代码实现了一个基本的训练和验证过程,用于训练一个神经网络模型(由参数`model`提供)。它使用给定的训练数据加载器(`trainloader`)和验证数据加载器(`valloader`)来分别加载训练数据和验证数据。以下是代码的主要功能和步骤:

1. **设置模型和设备**:首先,它将模型发送到指定的设备(通常是GPU,如果可用)。

model = model.to(device)


2. **初始化损失列表**:初始化三个列表来存储训练损失、验证损失和验证预测。


plt_train_loss = []
plt_val_loss = []
val_rel = []


3. **开始训练循环**:对于每一个epoch(由参数`epoch`提供),代码执行以下操作:


    * **重置损失**:在开始每个epoch之前,重置训练损失和验证损失。

python`train_loss = 0.0
    val_loss = 0.0`


    * **训练阶段**:设置模型为训练模式,并对`trainloader`中的每个数据批次执行以下操作:
    
    
        + 清零梯度。
        + 将数据和目标加载到设备上。
        + 通过模型进行前向传播以获取预测。
        + 计算损失。
        + 反向传播损失。
        + 更新模型权重。
        + 累加训练损失。
    * **计算平均训练损失**:在每个epoch结束时,计算平均训练损失并存储在`plt_train_loss`列表中。
    

python`plt_train_loss.append(train_loss/trainloader.dataset.__len__())


    * **验证阶段**:设置模型为评估模式,并对`valloader`中的每个数据批次执行以下操作:
    
    
        + 将验证数据加载到设备上。
        + 通过模型进行前向传播以获取预测。
        + 计算验证损失。
        + 累加验证损失。
        + 存储验证预测。
    * **检查并保存最佳模型**:如果当前验证损失低于之前的最小验证损失,则保存模型。
    
    

python`if val_loss < min_val_loss:
        torch.save(model, save_)`


    * **计算平均验证损失**:在每个epoch结束时,计算平均验证损失并存储在`plt_val_loss`列表中。
    

plt_val_loss.append(val_loss/valloader.dataset.__len__())


    * **打印训练和验证损失**:打印每个epoch的训练损失、验证损失和训练时间。
    

4. **绘制损失图**:在训练结束后,使用matplotlib绘制训练和验证损失的图。


```python
plt.plot(plt_train_loss)
plt.plot(plt_val_loss)
plt.title('loss')
plt.legend(['train', 'val'])
plt.show()
```

总的来说,这段代码实现了神经网络模型的训练、验证和损失的可视化。它使用训练数据来更新模型权重,并使用验证数据来评估模型的性能。在每个epoch结束时,它保存了平均训练损失和平均验证损失,并在训练结束后绘制了损失图。

第五部分:事前准备

device = 'cuda' if torch.cuda.is_available() else 'cpu'
train_path = 'kc_house_data.csv'
house_data = pd.read_csv(train_path)

print(house_data.head())

house_data.info()

print(house_data.describe())

house_dataset = houseDataset(train_path)

train_set, val_set = random_split(house_dataset,[int(len(house_dataset)*0.8),len(house_dataset)- int(len(house_dataset)*0.8)])
print(train_set.indices)

简单点讲,完成了数据集的导入,并且将内容划分为训练集和验证集。做完这些后,打印了数据集的一些初始状态。

第六部分:损失函数

def mseLoss(pred, target, model):
    loss = nn.MSELoss(reduction='mean')
    ''' Calculate loss '''
    regularization_loss = 0
    for param in model.parameters():
        # TODO: you may implement L1/L2 regularization here
        # 使用L2正则项
        # regularization_loss += torch.sum(abs(param))
        regularization_loss += torch.sum(param ** 2)
    return loss(pred, target) + 0.00075 * regularization_loss

loss =  mseLoss

Q:发散:关于L2正则化与L1正则化?

A:L2正则化在机器学习和统计建模中起着关键的作用,尤其在控制模型的复杂度和提高泛化能力方面。具体来说,L2正则化有以下几个主要作用:

减少过拟合:过拟合是当模型在训练数据上表现非常好,但在新数据或测试数据上表现较差时发生的现象。L2正则化通过约束模型参数的大小,使其不会过于复杂,从而减少了过拟合的风险。这有助于模型更好地泛化到未见过的数据上。 控制模型复杂度:L2正则化通过在损失函数中添加模型参数的L2范数(即参数平方和的平方根),使得在优化过程中,参数值会倾向于变得更小。这有助于降低模型的复杂度,因为模型不再依赖于某些特定的、可能只在训练数据中出现的特征。

那么,为什么L2正则化会起效呢?这主要是基于以下几个原因:

奥卡姆剃刀原则:这个原则强调在多个可能的解释中,应选择最简单的那个。在机器学习中,这通常意味着更简单的模型(即参数值更小的模型)更可能是正确的模型。L2正则化通过鼓励参数值变小,从而遵循了这一原则。 数值稳定性:较小的参数值通常意味着模型对输入数据的微小变化不那么敏感。这有助于提高模型的数值稳定性,减少因数据微小变化而导致的模型输出的大幅波动。 权重衰减:L2正则化也被称为权重衰减,因为它在每次更新参数时都会减去一个与参数值成正比的项。这会导致参数值逐渐减小,从而降低模型的复杂度。

总的来说,L2正则化通过约束模型参数的大小,使其更加简单和稳定,从而提高模型的泛化能力。这是L2正则化在机器学习和统计建模中广泛应用的原因。

L1正则化是另一种在机器学习中常用的正则化方法,它与L2正则化在目标、方式和效果上都有所不同。

L1正则化是在损失函数的基础上,加上所有参数的绝对值之和(乘以一个常数),用于惩罚参数过大,促使模型更加稀疏化。它的主要目标是使模型参数尽量接近0,甚至变为0,从而达到特征选择的效果。在一些需要特征选择的问题上,L1正则化表现得更加优秀。然而,由于L1正则化的导数在0时不可导,使得优化问题更加复杂。

与L2正则化相比,L1正则化有以下几个主要区别:

稀疏性:L1正则化倾向于产生稀疏模型,将不重要的特征的权重压缩为零,从而可以作为特征选择的手段。而L2正则化则不会将权重完全压缩为零,而是将它们压缩到一个小的范围内,所有特征都会有不为零的权重。 解的少数性:在某些情况下,L1正则化可能存在多个解。而L2正则化通常具有少数解,尤其是当特征数量少于样本数量时。 计算复杂性:由于L1正则化倾向于产生稀疏模型,在某些算法中可能更难优化。而L2正则化的梯度是连续的,数学上更易处理和优化。 对于异常值的鲁棒性:L1正则化对异常值更鲁棒,因为它不会过分地惩罚大的权重。

此外,L1正则化和L2正则化在模型预测准确性方面也有所不同。L1正则化通过将参数压缩到极小值,可以抑制对数据的过拟合,因此会大大提升模型的泛化能力,但可能会使模型的预测准确率下降。而L2正则化不会完全抑制模型参数的系数,因此可能会减少模型的泛化能力,但有助于提升模型的预测准确性。

总的来说,L1正则化和L2正则化都是控制模型复杂度和防止过拟合的有效手段,但它们在实现方式、效果和应用场景上有所区别。选择哪种正则化方法需要根据具体的问题和实验结果来决定。

一些个人理解:两种正则化方法都试图降低参数大小。这种方法能够避免过拟合的原因是当你的模型出现过拟合时,它通常尝试模拟*所有*出现的特征点与标签的关系。我们知道,有时你的训练集中会出现明显过于偏移正常值的异常点。没有过拟合的函数将不会太受到此类异常点的影响,然而过拟合的函数由于其试图模拟所有点位,因此会导致模拟的结果异常的向此类异常点偏移,从而导致一些不应被考虑的特征点在模型训练中却占据了很大权重。

落实到参数时,理解这样一个场景:过拟合的模型由于过于复杂并试图模拟所有点位,因此其参数值将会异常偏大,导致他们对于异常特征点位更加敏感。较大的w与较大的x相乘,结果自然会对模型训练造成很大影响。正则化就是试图减少这种情况的发生。

再顺便一提:文心一言讲到L1正则化还可以用于选择最佳特征,这种特性是在模型的训练过程中自然而然的进行的。与此同时,还有一种用于选择最佳特征的方法SKB(Selective K-Best),可以在进行训练之前人为的选出最佳特征。如果你的数据集比较复杂,而你希望训练结果是一个相对稀疏、易于理解的模型,你可以选择L1正则化;如果你的数据集相对简单,或者你有自己的偏好,希望从一些特定角度理解特征和y之间的关系,可以考虑SKB。

第七部分:尾声

config = {
    'n_epochs': 50,                # maximum number of epochs
    'batch_size': 25,               # mini-batch size for dataloader
    'optimizer': 'SGD',              # optimization algorithm (optimizer in torch.optim)
    'optim_hparas': {                # hyper-parameters for the optimizer (depends on which optimizer you are using)
        'lr': 0.0001,                 # learning rate of SGD
        'momentum': 0.9              # momentum for SGD
    },
    'early_stop': 200,               # early stopping epochs (the number epochs since your model's last improvement)
    'save_path': 'model_save/model.pth',  # your model will be saved here
}

model = myNet(18).to(device)
# optimizer = optim.SGD(model.parameters(), lr=0.03,momentum=0.9)
optimizer = torch.optim.Adam(model.parameters(),
                             lr=0.03,
                             weight_decay=0.001)     #换一个优化器


trainloader = DataLoader(train_set,batch_size=config['batch_size'], shuffle=True)
valloader = DataLoader(val_set,batch_size=config['batch_size'], shuffle=True)

train_val(model, trainloader,valloader,optimizer, loss, config['n_epochs'], device,save_=config['save_path'])

这部分就是config的设置以及优化器的设置,然后进行一个训练的跑。

更新时间 2024-03-13