NNDL 实验三 线性回归、多元线性回归、Runner类的封装和基于线性回归的波士顿房价预测

使用pytorch实现

2.2 线性回归

2.2.1 数据集构建

用到的库

import random
import matplotlib.pyplot as plt
import torch
import torch.nn
from torch import optim
import numpy as np

构造一个小的回归数据集:

生成 150 个带噪音的样本,其中 100 个训练样本,50 个测试样本,并打印出训练数据的可视化分布。

def add_gauss(data,mu,sigma):
    '''添加高斯模糊'''
    for i in range(len(data)):
        data[i][0]+=random.gauss(mu,sigma=sigma)
        data[i][1] += random.gauss(mu,sigma=sigma)
    return data

def init_data(len,rate,t_w,t_b):
    '''数据集数目,训练集占比、真实的w,真实的b '''
    data = []
    for i in range(len):
        x = random.uniform(0, 50)
        y = t_w * x + t_b
        if [x, y] not in data:
            data.append([x, y])
    data = add_gauss(data, 0, 0.5)
    train_data = random.sample(data, int(rate*len))
    test_data = []
    for i in data:
        if i not in train_data:
            test_data.append(i)
    for i in data:
        if i not in train_data:
            test_data.append(i)
    plt.figure()
    '''转换为tensor'''
    train_data = torch.Tensor(train_data)
    test_data = torch.Tensor(test_data)
    datax = []
    datay = []
    for i in train_data:
        datax.append([i[0]])
        datay.append([i[1]])
    train_data = [datax, datay]
    datax = []
    datay = []
    for i in test_data:
        datax.append([i[0]])
        datay.append([i[1]])
    test_data= [datax, datay]
    return torch.tensor(train_data),torch.tensor(test_data)
    
if __name__=='__main__':
    train_data,test_data=init_data(150,2/3,0.5,1)

NNDL 实验三 线性回归、多元线性回归、Runner类的封装和基于线性回归的波士顿房价预测_第1张图片

2.2.2 模型构建

class LM(torch.nn.Module):
    '''线性模型'''
    def __init__(self):
        super(LM,self).__init__()
        self.linear=torch.nn.Linear(1,1)#输入和输出数目

    def forward(self,x):
        y_pred=self.linear(x)
        return y_pred

2.2.3 损失函数

回归任务中常用的评估指标是均方误差

均方误差(mean-square error, MSE)是反映估计量与被估计量之间差异程度的一种度量。

    loss=torch.nn.MSELoss()

【注意:代码实现中没有除2】思考:没有除2合理么?谈谈自己的看法,写到实验报告。
是合理的,除二与不除二的唯一区别在于精确度,不除二时数量级偏大,反而更容易比较损失的大小。

2.2.4 模型优化

经验风险 ( Empirical Risk ),即在训练集上的平均损失。

optimizer = optim.SGD(model.parameters(), lr=0.001,weight_decay=1e-5)
'''其中model.parameters()初始化随机的参数w,b
	lr学习率
	weight_decay 是L2正则化系数,为防止过拟合'''

思考1. 为什么省略了1/N不影响效果?
和上一问一样,都是数量级的问题,去掉不去掉只影响了数据的灵敏度,不影响数值的比较的结果。
思考 2. 什么是最小二乘法 ( Least Square Method , LSM )
回答以上问题,写到实验报告。
就是对LSM的损失函数的各个w和b求偏导,零偏导等于零,所得到的解就是下一次迭代需要的的可行解,用于更新数据。
2.2.5 模型训练
在准备了数据、模型、损失函数和参数学习的实现之后,开始模型的训练。

在回归任务中,模型的评价指标和损失函数一致,都为均方误差。

通过上文实现的线性回归类来拟合训练数据,并输出模型在训练集上的损失。

if __name__=='__main__':
    train_data,test_data=init_data(150,2/3,0.5,1)
    model=LM()
    loss=torch.nn.MSELoss()
    optimizer = optim.SGD(model.parameters(), lr=0.001,weight_decay=1e-5)
    X=train_data[0]
    y=train_data[1]
    num_epochs = 20
    for epoch in range(num_epochs):
        pre_y = model(X)
        l = loss(pre_y, y)
        optimizer.zero_grad()  # 梯度清零
        l.backward()
        optimizer.step()
        # 输出权重和偏置
        print('w = ', model.linear.weight.item())
        print('b = ', model.linear.bias.item())
        print('epoch %d, loss: %f' % (epoch, l.item()))

w = 1.2275058031082153
b = 1.0194733142852783
epoch 0, loss: 783.282349
w = -0.06824445724487305
b = 0.9822576642036438
epoch 1, loss: 473.114380

w = 0.4923485517501831
b = 0.9970325827598572
epoch 19, loss: 0.382204

2.2.6 模型评估

用训练好的模型预测一下测试集的标签,并计算在测试集上的损失。

 x=test_data[0]
    y=test_data[1]
    l=loss(model(x),y)
    print('测试集loss:',l.item())

测试集loss: 0.4034019112586975

2.2.7 样本数量 & 正则化系数

(1) 调整训练数据的样本数量,由 100 调整到 5000,观察对模型性能的影响。
if __name__=='__main__':
    train_data,test_data=init_data(5000,2/3,0.5,1)

NNDL 实验三 线性回归、多元线性回归、Runner类的封装和基于线性回归的波士顿房价预测_第2张图片

w = 0.6784240007400513
b = -0.625267744064331
epoch 0, loss: 37.602818
w = 0.47063958644866943
b = -0.6307486295700073
epoch 1, loss: 14.453145

w = 0.548579216003418
b = -0.6132088899612427
epoch 19, loss: 0.991307
测试集loss: 1.0176087617874146
数据量不是越大越好,这种情况下,图像是一条巨粗的线,很难拟合出原本的直线。当然这是高斯模糊的结果,数据量巨大的同时图像也展示出了高斯模糊的大致范围。

(2) 调整正则化系数,观察对模型性能的影响。

对主函数略作调整

if __name__=='__main__':
    train_data,test_data=init_data(150,2/3,0.5,1)
    for i in [1e-8,1e-4,1e-2,1]:
        model = LM()
        loss = torch.nn.MSELoss()
        optimizer = optim.SGD(model.parameters(), lr=0.001,weight_decay=i)
        X=train_data[0]
        y=train_data[1]
        num_epochs = 20
        for epoch in range(num_epochs):
            pre_y = model(X)
            l = loss(pre_y, y)
            optimizer.zero_grad()  # 梯度清零
            l.backward()
            optimizer.step()
            # 输出权重和偏置
            '''print('w = ', model.linear.weight.item())
            print('b = ', model.linear.bias.item())'''
        print('epoch %d, loss: %f' % (epoch, l.item()))


        x=test_data[0]
        y=test_data[1]
        l=loss(model(x),y)
        print('测试集loss:',l.item())

        x=np.linspace(0,50,10)
        plt.plot(x,x*model.linear.weight.item()+model.linear.bias.item(),label='weight_decay='+str(i))
    plt.legend()
    plt.show()

数据创建函数也有所调整,为了作图。

def init_data(len,rate,t_w,t_b):
    data = []
    for i in range(len):
        x = random.uniform(0, 50)
        y = t_w * x + t_b
        if [x, y] not in data:
            data.append([x, y])
    data = add_gauss(data, 0, 0.5)
    train_data = random.sample(data, int(rate*len))
    test_data = []
    for i in data:
        if i not in train_data:
            test_data.append(i)
    plt.figure()
    for i in train_data:
       plt.scatter(i[0],i[1],c='r')
    for i in test_data:
        plt.scatter(i[0], i[1], c='g')
    '''转换为tensor'''
    train_data = torch.Tensor(train_data)
    test_data = torch.Tensor(test_data)
    datax = []
    datay = []
    for i in train_data:
        datax.append([i[0]])
        datay.append([i[1]])
    train_data = [datax, datay]
    datax = []
    datay = []
    for i in test_data:
        datax.append([i[0]])
        datay.append([i[1]])
    test_data= [datax, datay]
    return torch.tensor(train_data),torch.tensor(test_data)

epoch 19, loss: 0.793325
测试集loss: 0.7044939994812012
epoch 19, loss: 0.805041
测试集loss: 0.7179186940193176
epoch 19, loss: 1.028256
测试集loss: 0.9195249676704407
epoch 19, loss: 0.320158
NNDL 实验三 线性回归、多元线性回归、Runner类的封装和基于线性回归的波士顿房价预测_第3张图片
其中红色圆点为训练集,测试圆点为测试集。
学到这里不得不回忆起第一次做一元LSM回归,当时是直接套用了一次LSM的w、b的公式,直接算出来了。但是要改成二元的就很麻烦,直接加了一个维度,代码得从头到尾的改。而这次用的torch就把模型改一下就行了。

2.3 多项式回归

2.3.1 数据集构建

构建训练和测试数据,其中:

训练数样本 15 个,测试样本 10 个,高斯噪声标准差为 0.1,自变量范围为 (0,1)。

def init_data(begin,stop,num,rate,t_w,t_b,mu,sigma):
    '''begin:数据起点,
    stop:数据集终点,
    num:生成数目,
    rate:训练集占比,
    t_w:真实的w,
    t_b:真实的b,
    mu,sigma:高斯模糊的均值和方差'''
    data = []
    for i in range(num):
        x = [random.uniform(begin, stop)+random.gauss(mu,sigma=sigma),random.uniform(begin, stop)+random.gauss(mu,sigma=sigma),random.uniform(begin, stop)+random.gauss(mu,sigma=sigma)]
        y = x[0]*t_w[0]+x[1]*t_w[1]+x[2]*t_w[2]+t_b+random.gauss(mu,sigma=sigma)
        data.append([x, y])
    train_data = random.sample(data, int(rate*num))
    test_data = []
    for i in data:
        if i not in train_data:
            test_data.append(i)

    '''转换为tensor'''
    datax = []
    datay = []
    for i in train_data:
        datax.append(i[0])
        datay.append([i[1]])
    train_data = [torch.tensor(datax), torch.tensor(datay)]
    datax = []
    datay = []
    for i in test_data:
        datax.append(i[0])
        datay.append([i[1]])
    test_data= [torch.tensor(datax), torch.tensor(datay)]
    return train_data,test_data

2.3.2 模型构建

套用求解线性回归参数的方法来求解多项式回归参数

class PolyLM(torch.nn.Module):
    '''线性模型'''
    def __init__(self):
        super(PolyLM,self).__init__()
        self.linear=torch.nn.Linear(3,1)#输入和输出数目

    def forward(self,x):
        y_pred=self.linear(x)
        return y_pred

2.3.3 模型训练

对于多项式回归,我们可以同样使用前面线性回归中定义的LinearRegression算子、训练函数train、均方误差函数mean_squared_error。

if __name__=='__main__':
    train_data,test_data=init_data(0,1,25,3/5,[0.5,0.4,0.3],2,0,0.1)
    model = PolyLM()
    loss = torch.nn.MSELoss()
    optimizer = optim.SGD(model.parameters(), lr=0.01,weight_decay=1e-3)
    X=train_data[0]
    y=train_data[1]
    num_epochs = 30
    for epoch in range(num_epochs):
        pre_y = model(X)
        l = loss(pre_y, y)
        optimizer.zero_grad()  # 梯度清零
        l.backward()
        optimizer.step()
        # 输出权重和偏置
        '''print('w = ', model.linear.weight.item())
        print('b = ', model.linear.bias.item())'''
        print('num_epoch %d, train_loss: %f' % (epoch, l.item()))

    

num_epoch 0, train_loss: 4.053303
num_epoch 1, train_loss: 3.736230
num_epoch 2, train_loss: 3.444098

num_epoch 26, train_loss: 0.501111
num_epoch 27, train_loss: 0.463407
num_epoch 28, train_loss: 0.428665
num_epoch 29, train_loss: 0.396653

2.3.4 模型评估

通过均方误差来衡量训练误差、测试误差以及在没有噪音的加入下sin函数值与多项式回归值之间的误差,更加真实地反映拟合结果。多项式分布阶数从0到8进行遍历。

'''测试模型'''
    x=test_data[0]
    y=test_data[1]
    l=loss(model(x),y)
    print('测试集loss:',l.item())

测试集loss: 0.37486714124679565

对于模型过拟合的情况,可以引入正则化方法,通过向误差函数中添加一个惩罚项来避免系数倾向于较大的取值。

if __name__=='__main__':
    train_data,test_data=init_data(0,1,25,3/5,[0.5,0.4,0.3],2,0,0.1)
    for i in [1e-8, 1e-4, 1e-2, 1]:
        model = LM()
        loss = torch.nn.MSELoss()
        optimizer = optim.SGD(model.parameters(), lr=0.01,weight_decay=i)
        X=train_data[0]
        y=train_data[1]
        num_epochs = 30
        for epoch in range(num_epochs):
            pre_y = model(X)
            l = loss(pre_y, y)
            optimizer.zero_grad()  # 梯度清零
            l.backward()
            optimizer.step()
            # 输出权重和偏置
            '''print('w = ', model.linear.weight.item())
            print('b = ', model.linear.bias.item())'''
        print('weight_decay:{}, train_loss: {}'.format(i, l.item()))

        '''测试模型'''
        x=test_data[0]
        y=test_data[1]
        l=loss(model(x),y)
        print('测试集loss:',l.item())

weight_decay:1e-08, train_loss: 0.5947156548500061
测试集loss: 0.6567635536193848
weight_decay:0.0001, train_loss: 1.588309407234192
测试集loss: 1.6525604724884033
weight_decay:0.01, train_loss: 0.9914471507072449
测试集loss: 1.1179535388946533
weight_decay:1, train_loss: 1.3105849027633667
测试集loss: 1.3865985870361328

2.4 Runner类介绍

机器学习方法流程包括数据集构建、模型构建、损失函数定义、优化器、模型训练、模型评价、模型预测等环节。

为了更方便地将上述环节规范化,我们将机器学习模型的基本要素封装成一个Runner类。

除上述提到的要素外,再加上模型保存、模型加载等功能。

Runner类的成员函数定义如下:

__init__函数:实例化Runner类,需要传入模型、损失函数、优化器和评价指标等;
train函数:模型训练,指定模型训练需要的训练集和验证集;
evaluate函数:通过对训练好的模型进行评价,在验证集或测试集上查看模型训练效果;
predict函数:选取一条数据对训练好的模型进行预测;
save_model函数:模型在训练过程和训练结束后需要进行保存;
load_model函数:调用加载之前保存的模型。


import torch
import torch.nn
from torch import optim

class Runner():
    '''
    Runner类的成员函数定义如下:
    __init__函数:实例化Runner类,需要传入模型、损失函数、优化器和评价指标等;
    train函数:模型训练,指定模型训练需要的训练集和验证集;
    evaluate函数:通过对训练好的模型进行评价,在验证集或测试集上查看模型训练效果;
    predict函数:选取一条数据对训练好的模型进行预测;
    save_model函数:模型在训练过程和训练结束后需要进行保存;
    load_model函数:调用加载之前保存的模型。
    '''
    def __init__(self,model,loss,optim,eval_loss):
        '''传入模型、损失函数、优化器和评价指标'''
        self.model=model
        self.loss=loss
        self.optim=optim
        self.eval_loss=eval_loss
    
    def train(self,train_data):
        '''train_data:列表类型,两个元素为tensor类型,第一个是x,第二个是y'''
        model=self.model
        loss = torch.nn.MSELoss()
        optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=1e-3)
        X = train_data[0]
        y = train_data[1]
        num_epochs = 30
        for epoch in range(num_epochs):
            pre_y = model(X)
            l = loss(pre_y, y)
            optimizer.zero_grad()  # 梯度清零
            l.backward()
            optimizer.step()
            print('epoch %d, loss: %f' % (epoch, l.item()))
        self.__save_model(model)
        
    def evaluate(self,test_data):
        '''测试模型
        test_data:列表类型,两个元素为tensor类型,第一个是x,第二个是y'''
        x = test_data[0]
        y = test_data[1]
        l = self.loss(self.model(x), y)
        print('测试集loss:', l.item())
        
    def predict(self,X):
        '''预测数据'''
        return self.model(X)

    def __save_model(self,model):
        '''内部调用,保存模型'''
        self.model=model
    
    def load_model(self,model):
        '''外部调用,读取模型'''
        self.model=model

封装好Runner类后,直接调用类函数就行了,十分的简洁。
这里的__save_model()是内部隐藏类,外部是看不到的,也无法调用,就算添了’__'再调用也不行。
NNDL 实验三 线性回归、多元线性回归、Runner类的封装和基于线性回归的波士顿房价预测_第4张图片
主函数如下:

if __name__=='__main__':
    model = PolyLM()
    loss = torch.nn.MSELoss()
    optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=1e-3)
    runner=Runner(model=model,loss=loss,optim=optimizer,eval_loss=loss)
    train_data, test_data = init_data(0, 1, 25, 3 / 5, [0.5, 0.4, 0.3], 2, 0, 0.1)
    runner.train(train_data)
    runner.evaluate(test_data)

运行结果:
epoch 0, loss: 7.497038
epoch 1, loss: 6.954211
epoch 2, loss: 6.450910

epoch 27, loss: 1.009143
epoch 28, loss: 0.938562
epoch 29, loss: 0.873111
测试集loss: 1.039759635925293

2.5 基于线性回归的波士顿房价预测

使用线性回归来对马萨诸塞州波士顿郊区的房屋进行预测。

实验流程主要包含如下5个步骤:

数据处理:包括数据清洗(缺失值和异常值处理)、数据集划分,以便数据可以被模型正常读取,并具有良好的泛化性;
模型构建:定义线性回归模型类;
训练配置:训练相关的一些配置,如:优化算法、评价指标等;
组装训练框架Runner:Runner用于管理模型训练和测试过程;
模型训练和测试:利用Runner进行模型训练和测试。

2.5.1 数据处理

2.5.1.2 数据清洗

本次实验不需要数据清晰

2.5.1.3 数据集划分

用到的库


import numpy as np
import pandas as pd
import torch.nn
import torch.nn as nn
from Runner import *
import torch

def init_boston(path,rate):
    #波士顿标签
    labels = ["CRIM", "ZN", "INDUS", "CHAS", "NOX", "RM", "AGE", "DIS", "RAD", "TAX", "PTRATIO", "B", "LSTAT", "MEDV"]
    #读取csv
    boston = pd.read_csv(path,dtype=float)
    #数据集划分
    train_df = boston.sample(int(len(labels) * rate))
    test_df = boston.drop(index=train_df.index)
    #转化为tensor
    train_X= torch.from_numpy(train_df.drop('MEDV', axis=1).to_numpy().astype(np.float32))
    train_y=torch.from_numpy(train_df['MEDV'].to_numpy().astype(np.float32)*1000).reshape(len(train_df['MEDV']),1)
    test_X= torch.from_numpy(test_df.drop('MEDV', axis=1).to_numpy().astype(np.float32))
    test_y= torch.from_numpy(test_df['MEDV'].to_numpy().astype(np.float32)*1000).reshape(len(test_df['MEDV'].to_numpy()),1)
    return train_X,train_y,test_X,test_y

2.5.1.4 特征工程

2.5.2 模型构建


class BostonLM(nn.Module):
    def __init__(self):
        super(BostonLM, self).__init__()
        self.linear = torch.nn.Linear(13, 100)  # 输入和输出数目
        self.hide=torch.nn.Linear(100,1)

    def forward(self,X):
        y = self.linear(X)
        y2=self.hide(y)
        return y2

2.5.3 完善Runner类

# coding:utf-8

import torch
import torch.nn as nn
from torch import optim
import random

class Runner():

    def __init__(self,model,loss,optim):
        '''传入模型、损失函数、优化器和评价指标'''
        self.model=model
        self.loss=loss
        self.optim=optim

    def LSM_train(self,X,y,epoches=300):
        '''train_data:列表类型,两个元素为tensor类型,第一个是x,第二个是y'''
        print('start training....')
        loss = self.loss
        optimizer = self.optim
        train_X, train_y,  = X,y
        for i in range(epoches):
            pre_y =self.model(train_X)
            l = loss(pre_y, train_y)
            optimizer.zero_grad()
            l.backward()
            optimizer.step()
            if i % 100 == 0:
                print("epoch:{}, loss in train data:{}".format(i, l))
        print('training ended.')

    def LSM_evaluate(self,x,y):
        '''测试模型
        test_data:列表类型,两个元素为tensor类型,第一个是x,第二个是y'''
        l = self.loss(self.model(x), y)
        print('loss in test data:', l.item())

    def predict(self,X):
        '''预测数据'''
        return self.model(X)

    def save_model(self, save_path):
        ''''.pt'文件'''
        torch.save(self, save_path)

    def read_model(self, path):
        ''''.pt'文件'''
        torch.load(path)

   

2.5.4 模型训练

if __name__=='__main__':
    net = BostonLM()  # 初始化网络模型
    loss = torch.nn.MSELoss()  # 均方损失
    optimizer = torch.optim.Adam(net.parameters(), lr=0.01)
    train_X, train_y, test_X, test_y = init_boston('boston_house_prices.csv', rate=4 / 5)
    runner=Runner(net,loss=loss,optim=optimizer)
    runner.LSM_train(train_X,train_y,epoches=30000)
    test_loss=runner.LSM_evaluate(test_X,test_y)

2.5.5 模型测试

     test_loss=runner.LSM_evaluate(test_X,test_y)

2.5.6 模型预测

prey=runner.predict(input)
    print('24 predicted:',prey.item())

运行结果:

epoch:29600, loss in train data:0.7043233513832092
epoch:29700, loss in train data:14840.9619140625
epoch:29800, loss in train data:1.225315809249878
epoch:29900, loss in train data:0.6267527937889099
training ended.
loss in test data: 184685824.0
24 predicted:43825.0977

【注意】例程2.5中有:
from nndl.op import Linear

from nndl.opitimizer import optimizer_lsm

这两个python文件位置如下:

文件使用了paddle,改写成pytorch,并在后续工作中使用即可。

问题1:使用类实现机器学习模型的基本要素有什么优点?
调用函数更方便,代码更简洁,减少不必要的重复代码。
问题2:算子op、优化器opitimizer放在单独的文件中,主程序在使用时调用该文件。这样做有什么优点?
避免重复编写相同的代码。
问题3:线性回归通常使用平方损失函数,能否使用交叉熵损失函数?为什么?
交叉熵损失函数多用于线性分类问题,用于表示类别之间的相关性,进而判断分类的效果。

总结:花了大量时间查阅相关资料,编代码的过程中也遇到了很多错误,真的让人很崩溃,但是最后程序运行起来的时候心情会大好,看大自己努力的结果是一件让人开心的事。这次实验学习了线性回归、多元线性回归的相关知识,同时尝试了Runner类的封装和基于线性回归的波士顿房价预测,其中波士顿预测还不太通透,需要进一步学习。总的来说工程量不小,收获也是蛮大的。

你可能感兴趣的:(线性回归,机器学习,python)