Lasso回归和岭回归的底层实现

目录

一、前言

二、基本信息

 三、基本思路

四、Lasso回归

1、数据处理

2、选择模型

3、计算风险函数

4、优化

5、重复以上步骤,直到收敛

五、Ridge回归

六、总结

七、源码展示


一、前言

近日,实践了lasso回归和岭回归的底层实现,这是一个课程作业,也是初次尝试,问题很多,就是记录一下。

二、基本信息

基本原理:

数据集:本次数据集是一个预测房价的数据集,包括编号、交易日期、房龄、到最近地铁站距离、便利店数量、经度和纬度七个属性,预测值为每平米的价格。为了专注于实现线性回归,已经为大家处理好的数据,删除了编号信息,六个属性和预测值存于numpy数组中保存在了/data/exp02目录下,命名分别为X_train.npy和y_train.npy。测试时调用read_data函数得到特征矩阵和标签向量

岭回归:

岭回归(Ridge Regression)是一种线性回归的扩展形式,它通过在损失函数中添加L2正则化项来解决线性回归中的过拟合问题。L2正则化项会使得模型的权重参数向零收缩,从而降低模型的复杂度,避免过拟合。

在岭回归中,我们优化的目标函数为:

其中,$X$是输入特征矩阵,$y$是对应的标签,$w$是模型的权重参数,$\alpha$是正则化强度超参数。该目标函数包括两个部分,第一个部分是平方误差损失函数,它用来衡量模型的预测值与真实值之间的误差;第二个部分是L2正则化项,它用来限制模型的复杂度。

与普通的线性回归相比,岭回归在解析式中增加了一个偏置项,也就是模型权重的平方和,同时加上一个超参数 $\alpha$ 控制模型复杂度和惩罚力度,通过调整这个超参数可以得到不同的模型表现。

Lasso回归(Lasso Regression)是一种线性回归方法,它通过加入正则化项(L1正则化)来惩罚模型中的大型系数,以避免过拟合问题。Lasso回归在高维数据集中非常有用,因为它可以自动选择最相关的特征,从而简化模型。

Lasso回归的优化目标是最小化以下损失函数:

其中,$X$是输入特征矩阵,$y$是对应的标签,$w$是模型的权重参数,$\alpha$是正则化强度超参数。该目标函数包括两个部分,第一个部分是平方误差损失函数,它用来衡量模型的预测值与真实值之间的误差;第二个部分是L1正则化项,它用来限制模型的复杂度。

Lasso回归的优点是可以自动选择最相关的特征,从而降低模型的复杂度,并且可以处理具有高度相关特征的数据集。缺点是当特征数量非常大时,计算量可能会很大。

要求:只能使用numpy,禁止使用sklearnpytorchtensorflow等第三方库

 三、基本思路

  1. 数据处理
  2. 选择模型
  3. 计算风险函数
  4. 优化
  5. 重复以上步骤,直到收敛

四、Lasso回归

1、数据处理

在机器学习中,对数据进行预处理是很重要的一步。数据预处理可以帮助我们清洗数据,去除噪声和异常值,提高模型的准确性和可靠性。

数据预处理通常包括以下几个步骤:

  1. 数据清洗:去除重复数据、缺失值和异常值,以提高数据的质量。
  2. 数据转换:将数据转换为适合机器学习算法处理的格式。例如,可以对分类变量进行独热编码(One-hot Encoding)处理,将其转化为虚拟变量。
  3. 数据规范化:将数据缩放到一个特定的范围内,以便于不同特征之间的比较。例如,可以使用最大最小值归一化(Min-Max Normalization)或标准化(Standardization)方法对数据进行规范化。

通过对数据进行预处理,我们可以提高模型的准确性和可靠性,从而更好地解决实际问题。

在这个实现里面,因为数据集是已经处理好的,所以这个环节只进行了数据规范化。常用的数据规范化方法有以下几种

1、最大最小值归一化(Min-Max Normalization):这种方法通过将数据缩放到一个特定的范围内(通常是0到1之间)来实现规范化。具体来说,对于每个数据点,我们可以使用下面的公式进行归一化:

                                        x' = (x - min) / (max - min)

其中,x表示原始数据点,x'表示归一化后的数据点,minmax分别表示数据集中的最小值和最大值。

2、标准化(Standardization):这种方法通过将数据转换为均值为0,标准差为1的分布来实现规范化。具体来说,对于每个数据点,我们可以使用下面的公式进行标准化:

                                        x' = (x - mean) / std

其中,x表示原始数据点,x'表示标准化后的数据点,meanstd分别表示数据集中的均值和标准差。

3、鲁棒缩放(Robust Scaling):这种方法与最大最小值归一化类似,但是它使用数据集中的中位数和四分位数来代替最小值和最大值。这样可以使得规范化后的数据更加鲁棒,不受异常值的影响。

这里选用了标准化的方法。

#对测试数据进行统一标准化
def standard_st(data):
    data_mean = np.mean(data)
    # X_var=np.var(X)#方差
    data_std = np.std(data)
    data = (data - data_mean) / data_std
    return data
#对数据进行处理(标准化操作并将构建增广矩阵)
def data_processing_st(X):
    X = np.apply_along_axis(standard_st, 1, X)
    ones = np.ones((X.shape[0], 1))
    X = np.hstack((X, ones))
    return X

(基本思路就是先编写一个实现标准化的函数,然后再通过np.apply_along_axis()的方法实现对数据矩阵的每一行做规范化)

在初次尝试的时候犯了一个错误,在对数据进行规范化的时候,是基于对全体数据集进行规范化的(r然后就导致精度一直上不来),这种全局规范化的方法虽然可以确保数据的统一性,但实际上会产生一些问题。首先,全局规范化可能会损失数据中的一些重要信息。例如,如果我们有两个不同的数据子集,它们的数据分布可能不同,全局规范化可能会使它们的特征值相同,导致模型无法区分它们。

其次,全局规范化还可能导致模型过度拟合训练数据。这是因为全局规范化会使数据的分布变得更加集中,从而增加模型在训练数据上的准确性,但同时也可能减少了模型在未知数据上的泛化能力。

还有,在对房价进行预测时,房屋面积和房龄这两个特征的值范围可能相差很大,因此我们需要对它们进行独立的规范化,以确保它们在相同的尺度上。

2、选择模型

本次实践直接指定使用lasso回归,所以不用选择模型。

一般来说,要从具体的问题来选择相对应的模型。本次的机器学习目标是通过进行相关计算来预测房价,常用的模型有线性回归模型、随机森林、岭回归、lasso回归等。

3、计算风险函数

前面lasso回归的损失函数已经给出,即

# 定义损失函数
mse = np.sum(((X @ w )- y.T) @ ((X @ w) - y.T).T)/(np.shape(X)[0])
l1 = alpha * ((np.sum(np.abs(w))))
lassoloss = mse + l1

4、优化

使用梯度下降的方法对学习准则的优化

# 计算梯度
dw = X.T @ ((X @ w) - y.T) + alpha * np.sign(w)  # 不是很理解为什么
loss_old = lassoloss#记录上一次的风险损失
# 更新参数
w = w - beta * dw

5、重复以上步骤,直到收敛

这一步直接通过for循环迭代实现就行,在循环里面我们还应该设置一个提前停止的条件,避免无意义的循环迭代。

# 后边损失函数下降十分缓慢,设置提前停止的条件
if (np.abs(min - loss_old) < 0.0001):
    print('提前停止!')
    break
# 获取最小损失时候的参数w
if (min >= lassoloss):
    min = lassoloss
    best = w

小结:基本的代码就已经完成了,整个代码我是通过手动的修改超参数,以获取到一个最优的参数(这一步可以使用网格搜索或是随机搜索的方法来进行优化的)。

五、Ridge回归

基本思路:

在岭回归中我的优化目标函数为在岭回归中,我们优化的目标函数为:

因为该函数是凸函数,有解析解。极值点是导数为0的点,所以先对w进行求导,

令偏导等于0 ,然后对等式进行处理,便得到w的方程。代码中只需要找到一个合适的超参数代入该方程,便可以求得我们的目标参数。

alpha=1#2023/4/30
ridgeloss = 2 * (X.T @ X @ w - X.T @ y + alpha * w)
# 对损失函数中w求偏导,令导数为0,求得w
w = np.linalg.inv((X.T @ X + alpha * np.eye(np.shape((X.T @ X))[0]))) @ X.T @ y

六、总结

1、对于本次预测房价的项目中,不适合对数据整体进行规范化,而应该是对每个特征进行规范化。

2、可以使用增广矩阵,将w、b参数合并一起,会方便一点

3、在实现Ridge回归的时候,我使用的规范方法是最大最小值规划化,将其缩放到(0,1)之间,针对这个机器学习的问题,好像这个找到的解更优。

七、源码展示

# 最终在main函数中传入一个维度为6的numpy数组,输出预测值

import os

try:
    import numpy as np
except ImportError as e:
    os.system("sudo pip3 install numpy")
    import numpy as np
#对测试数据进行统一标准化
def standard_st(data):
    data_mean = np.mean(data)
    # X_var=np.var(X)#方差
    data_std = np.std(data)
    data = (data - data_mean) / data_std
    return data
#对数据进行处理(标准化操作并将构建增广矩阵)
def data_processing_st(X):
    X = np.apply_along_axis(standard_st, 1, X)
    ones = np.ones((X.shape[0], 1))
    X = np.hstack((X, ones))
    return X
#使用01规范法处理数据
def data_processing_01(X):

    X = np.apply_along_axis(standard_01, 1, X)
    ones = np.ones((X.shape[0], 1))
    X = np.hstack((X, ones))
    return X
#使用01规范法处理数据
def standard_01(data):
    min_val = np.min(data)
    max_val = np.max(data)
    scaled_data = (data - min_val) / (max_val - min_val)
    return scaled_data

def ridge(data):
    data=standard_01(data)
    X, y = read_data()
    X = data_processing_01(X)
    # 选择模型并初始化参数
    w = np.zeros((7, 1))
    y_pre = X @ w  # 计算预测值
    # 定义损失函数
    # alpha = 10  # 正则化系数
    alpha=1#2023/4/30
    ridgeloss = 2 * (X.T @ X @ w - X.T @ y + alpha * w)
    # 对损失函数中w求偏导,令导数为0,求得w
    w = np.linalg.inv((X.T @ X + alpha * np.eye(np.shape((X.T @ X))[0]))) @ X.T @ y
    #获取w和b
    b = w[-1]
    w = w[:-1]
    w = w.reshape(6, 1)
    return data@w+b

    
def lasso(data):
    data = standard_st(data)
    X, y = read_data()
    X = data_processing_st(X)
    y=y.reshape(1,404)
    # 设置超参数
    alpha = 1000  # 正则化系数
    beta = 0.00045  # 学习率

    # 选择模型并初始化参数
    w = np.zeros((7, 1))
    best = w
    min = 365194055
    loss_old = 1  # 记录上一次的损失,如果两个损失变化太小,说明已经趋近最优值,提前停止
    # print(X@w)
    for i in range(100000):
        y_pre = X @ w  # 计算预测值
        # 定义损失函数
        mse = np.sum(((X @ w )- y.T) @ ((X @ w) - y.T).T)/(np.shape(X)[0])
        l1 = alpha * ((np.sum(np.abs(w))))
        lassoloss = mse + l1
        # 计算梯度
        dw = X.T @ ((X @ w) - y.T) + alpha * np.sign(w)  
        loss_old = lassoloss#记录上一次的风险损失
        # 更新参数
        w = w - beta * dw
        # 后边损失函数下降十分缓慢,设置提前停止的条件
        if (np.abs(min - loss_old) < 0.0001):
            print('提前停止!')
            break
        # 获取最小损失时候的参数w
        if (min >= lassoloss):
            min = lassoloss
            best = w
        # 输出损失函数的值

    #print(f'Iteration {i}: Loss = {lassoloss} ')
    w =best[0:6,:]
    b=best[6,0]
    print(data@w+b)
    return data@w+b

def read_data(path='./data/exp02/'):
    x = np.load(path + 'X_train.npy')
    y = np.load(path + 'y_train.npy')
    return x, y

猜你喜欢

转载自blog.csdn.net/qq_58233310/article/details/130455136