使用梯度下降法实现变量之间的线性回归

1、介绍

1、概述
  本博客的梯度下降法实现变量之间的线性回归,主要考虑到了最小二乘法 X T X X^TX XTX有可能逆不存在的情况。使用该方法求解线性回归的过程如下:


  m表示样本的个数,在目标函数前面的系数 1 m \frac{1}{m} m1加不加无所谓,只是为了更好的计算而已,本文的代码中并没有该系数。
2、代码功能
  本博客使用梯度下降法来实现对于波士顿平均房价的预测。通过前面的特征矩阵来实现对于波士顿平均房价(medv)的预测。
  由于特征矩阵的每一列数据的数量级相差很大,导致模型生成的权重向量数据很大,我们对特征矩阵和分类标签向量的每一列都进行了标准化,即均值为0,方差为1的正态分布。采用的公式如下:
若 X − N ( μ , σ 2 ) , 则 Y = X − μ σ − N ( 0 , 1 ) 若X - N(\mu,\sigma^2),则Y=\frac{X-\mu}{\sigma} - N(0,1) XN(μ,σ2)Y=σXμN(0,1)

2、代码

1、测试数据boston.csv
(1)数据格式

"id","crim","zn","indus","chas","nox","rm","age","dis","rad","tax","ptratio","black","lstat","medv"
"1",0.00632,18,2.31,0,0.538,6.575,65.2,4.09,1,296,15.3,396.9,4.98,24
"2",0.02731,0,7.07,0,0.469,6.421,78.9,4.9671,2,242,17.8,396.9,9.14,21.6
"3",0.02729,0,7.07,0,0.469,7.185,61.1,4.9671,2,242,17.8,392.83,4.03,34.7

为了大家能够更好的学习最小二乘法,我将这个csv文件放到我博客的资源中,通过下面的链接即可下载,注意在将下面的文件放到代码中运行时,注意修改代码中的文件路径。

https://download.csdn.net/download/weixin_43334389/13134305

3、代码

# @Time : 2020/11/21 20:41
# @Description :

import numpy as np
import pandas as pd

data = pd.read_csv(r"dataset/boston.csv", header=0)
data.drop(["id"], axis=1, inplace=True)


class LinearRegression:
    """
    @desc:使用梯度下降回归算法
    """

    def __init__(self, learning_rate, times):
        """
        @desc:初始化算法
        @param learning_rate:学习率。梯度下降步长
        @param times:迭代次数
        """
        self.learning_rate = learning_rate
        self.times = times

    def fit(self, X, y):
        """
        @desc:训练模型,对样本进行预测
        @param X:特征矩阵
        @param y:标签数组
        """
        # X.shape (400, 14)
        # y.shape (400,)
        X = np.asarray(X)
        y = np.asarray(y)

        # 创建权重,初始值为0(也可以试试其他值),长度比X特征数多1,多出的就是w0(截距b)
        self.w_ = np.zeros(1 + X.shape[1])  # X.shape[1] 为特征数
        # 创建损失值列表,用来每次迭代的损失值。
        # 损失值计算公式:(1/2 )*( 预测值 - 真实值)^2
        self.loss_ = []
        # 开始迭代
        for x in range(self.times):
            # 为什么使用 self.w_[1:] ? 因为X特征个数比w少1个,所以这里w0先去掉,点乘后再加上
            # 矩阵可以使用*运算,但是这里是ndarray数组只能用点乘,1表示x0
            y_hat = np.dot(X, self.w_[1:]) + self.w_[0]
            # 计算误差
            error = y - y_hat
            # 将损失值加入损失列表
            self.loss_.append(np.sum(error ** 2) / 2)
            # 根据差距调整权重w_
            # 调整为 w(j)=w(j)+learn_rate*sum((y - y_hat)*x(j))
            # 更新w0,将数据x的首项看成1,然后进行更新
            self.w_[0] += self.learning_rate * np.sum(error * 1)
            # 使用x1到xn的某一列向量(即某一特征)的转置来更新w,也就是将w向x进拟合
            # (X.T).shape (14, 400)
            # error.shape (400,)
            self.w_[1:] += self.learning_rate * np.dot(X.T, error)

    def predict(self, X):
        """
        @desc:预测测试集数据
        @param X:传入的训练集数据
        @return:预测结果
        """
        X = np.asarray(X)
        result = np.dot(X, self.w_[1:]) + self.w_[0]
        return result


class StandardScaler:
    """
    @desc:对数据集进行标准化处理
    """

    def fit(self, X):
        """
        @desc:根据传递的样本,计算每个特征列的均值和标准差。在transform()中进行使用,
        使得最后返回的均值为0,标准差为1。将每一列变成标准正态分布的形式。
        @param X:训练数据。用来计算均值和标准差。
        """
        X = np.asarray(X)
        self.std_ = np.std(X, axis=0)  # 按列计算标准差
        self.mean_ = np.mean(X, axis=0)  # 按列计算均值

    def transform(self, X):
        """
        @desc:对给定数据进行标准化处理,将每一列都转换成标准正态分布的形式。
        即满足均值为0,标准差为1。
        标准化也叫标准差标准化,经过处理的数据符合标准正态分布。
        @param X:类数组类型。待转换的训练数据。
        @return:返回每一列都是标准正态分布的一个二维数据
        """
        return (X - self.mean_) / self.std_

    def fit_transform(self, X):
        """
        @desc:集fit和transform函数于一体,在训练的时候进行转换操作。
        @param X:类数组类型。待转换的训练数据。
        @return:返回每一列都是标准正态分布的一个二维数据
        """
        self.fit(X)
        return self.transform(X)


# 原始梯度下降法的线性回归算法
lr = LinearRegression(learning_rate=0.0005, times=20)
t = data.sample(len(data), random_state=0)
train_X = t.iloc[:400, :-1]
train_y = t.iloc[:400, -1]
test_X = t.iloc[400:, :-1]
test_y = t.iloc[400:, -1]
lr.fit(train_X, train_y)
result = lr.predict(test_X)
print("--------标准化之前的训练和预测的损失值和权重------------------")
print(np.mean((result - test_y) ** 2))
print(lr.w_)
print(lr.loss_)

# 标准化之后的梯度下降法的线性回归算法
# 为了避免每个特征数量级的不同对梯度下降造成的影响。我们对每个特征进行标准化。
# 将每个特征列排除数量级的影响,将其变成同样的一个数量级。
lr = LinearRegression(learning_rate=0.0005, times=20)
# data.shape (506, 14)
print("data.shape", data.shape)
# type(data) <class 'pandas.core.frame.DataFrame'>
print("type(data)", type(data))
t = data.sample(len(data), random_state=0)
train_X = t.iloc[:400, :-1]
train_y = t.iloc[:400, -1]
# (106, 13)
# <class 'pandas.core.frame.DataFrame'>
test_X = t.iloc[400:, :-1]
print("------------------")
print(test_X.shape)
print("type(test_X)", type(test_X))
test_y = t.iloc[400:, -1]

# 对训练集和测试集进行标准化处理
ss = StandardScaler()
train_X = ss.fit_transform(train_X)
test_X = ss.fit_transform(test_X)
ss2 = StandardScaler()
train_y = ss2.fit_transform(train_y)
test_y = ss2.fit_transform(test_y)

# 训练 预测
lr.fit(train_X, train_y)
result = lr.predict(test_X)
print("--------标准化之后的训练和预测的损失值和权重------------------")
print(np.mean((result - test_y) ** 2))
print(lr.w_)
print(lr.loss_)

import matplotlib as mpl
import matplotlib.pyplot as plt

mpl.rcParams["font.family"] = "SimHei"
mpl.rcParams["axes.unicode_minus"] = False

plt.figure(figsize=(10, 10))
plt.plot(result, "ro-", label="预测值")
# pandas读取时serise类型,我们需要转为ndarray
plt.plot(test_y.values, "go-", label="真实值")
plt.title("线性回归预测-梯度下降")
plt.xlabel("测试机样本序号")
plt.ylabel("预测房价")
plt.legend()
plt.show()

# 绘制累计误差值,随着每次迭代,误差值不断下降,之后就趋于一个稳定的状态。
plt.plot(range(1, lr.times + 1), lr.loss_, "o-")
plt.xlabel("迭代次数")
plt.ylabel("累计损失误差")
plt.show()

# 因为房价更新涉及多个维度,不方便可视化
# 为了可视化,我们只选择其中一个维度(RM:平均房间数量 ),并画出直线,进行拟合
lr = LinearRegression(learning_rate=0.0005, times=20)
t = data.sample(len(data), random_state=0)
# 之前的数据使用到了除了目标列之外的所有列作为特征矩阵的,现在只使用RM这一列进行展示
# 特征矩阵应该是一个二维数组矩阵才可以,我们使用切片的形式。采用下面的第二种形式是一个一维的形式,不符合要求,舍去。
train_X = t.iloc[:400, 5:6]

# (400,)
# <class 'pandas.core.series.Series'>
# train_X = t.iloc[:400, 5]
# (400, 1)
#         rm
# 329  6.333
# 371  6.216
# 219  6.373
print(train_X.shape)
# <class 'pandas.core.frame.DataFrame'>
print(type(train_X))
print(train_X.head(3))
train_y = t.iloc[:400, -1]
test_X = t.iloc[400:, 5:6]
test_y = t.iloc[400:, -1]
# 标准化
ss = StandardScaler()
train_X = ss.fit_transform(train_X)
test_X = ss.fit_transform(test_X)
ss2 = StandardScaler()
train_y = ss2.fit_transform(train_y)
test_y = ss2.fit_transform(test_y)
lr.fit(train_X, train_y)
# 均方误差
print(np.mean((result - test_y) ** 2))

# 展示rm对对于价格的影响
plt.scatter(train_X["rm"], train_y)
# 标准化之后展示权重
print(lr.w_)
# 构建方程 y = w0 + x*w1 = -3.05755421e-16 + x *  6.54993957e-01
x = np.arange(-5, 5, 0.1)
y = -3.05755421e-16 + x * 6.54993957e-01
# 第一条绿色直线显示我们根据权重进行绘制的拟合直线
plt.plot(x, y, "g", label="由权重绘制的图形")
# x.reshape(-1,1) 把一维转为二维
# 第二条红色曲线表示通过我们的预测值产生相应的y进行拟合

print("-----------------")
# <class 'numpy.ndarray'>
print(x)
# (100,)
print(x.shape)
print(x.reshape(-1, 1))
# <class 'numpy.ndarray'>
print(type(x.reshape(-1, 1)))
# (100, 1)
print((x.reshape(-1, 1).shape))

# 将X传入到模型之中,进行预测得出结果,然后进行绘制相应的图形。
plt.plot(x, lr.predict(x.reshape(-1, 1)), 'r', label="通过预测绘制的图形")
plt.legend()
plt.show()

4、结果展示
(1)预测值和真实值进行拟合

(2)随着迭代次数增加而统计的累计损失误差

(3)权重拟合的曲线和预测绘制的曲线进行对比


(4)标准化数据前后的均方误差、权重和损失值对比

--------标准化之前的训练和预测的损失值和权重------------------
2.5269094172606156e+194
[-4.91840744e+91 -2.24201028e+92 -4.88461060e+92 -5.87006550e+92
 -3.32031749e+90 -2.77875440e+91 -3.06624018e+92 -3.47870767e+93
 -1.79024405e+92 -5.41134085e+92 -2.15329860e+94 -9.17485507e+92
 -1.74958006e+94 -6.49542169e+92]
[116831.44, 352143849427807.9, 1.3577876251262872e+24, 5.238506743432333e+33,...

--------标准化之后的训练和预测的损失值和权重------------------
0.149072143352611
[ 1.49824597e-16 -7.82096101e-02  3.27417218e-02 -4.18423834e-02
  7.23915815e-02 -1.22422484e-01  3.18709730e-01 -9.44094559e-03
 -2.09320117e-01  1.04023908e-01 -5.20477318e-02 -1.82216410e-01
  9.76133507e-02 -3.94395606e-01]
[200.0, 107.18106695239439, 88.90466866295793, 79.78035025519532, 74.3187880885867, 70.90417512718281, 68.69155318506807, 67.20013197881178, 66.15079837015878, 65.37902020765745, 64.78625525603303, 64.31246996531246, 63.920412106879105, 63.586500210988966, 63.295479267264845, 63.03724485771134, 62.80493063951664, 62.5937408804752, 62.40022787877571, 62.221838400632855]

3、分析

1、核心代码分析

# 这里对应梯度下降法求解线性回归的结论。内积也可以表示一种求和
self.w_[1:] += self.learning_rate * np.dot(X.T, error)

# 举例,对应上面的内积内容
error = np.array([1, 2])
print(error)
# <class 'numpy.ndarray'>
print(type(error))
print(error.shape)

X_T = np.array([[1, 2], [3, 4], [5, 6]])
# [[1 2]
#  [3 4]
#  [5 6]]
print(X_T)
# (3, 2)
print(X_T.shape)

# 将一个二维数组矩阵和一个数组点积求和计算
result = np.dot(X_T, error)
# (3,)
print(result.shape)
# [ 5 11 17]
print(result)

  我们的波士顿数据格式如下,这里并没有在该矩阵的前面的添加数字1(为了和增广权向量的首项 w 0 w_0 w0相对应),是因为我们事先知道第一列是数字1,在代码中,为了简便,我们将有数字1的项和其他项分开,单独计算并更新相应权重。在分别训练好权重 s e l f . w [ 1 : ] , s e l f . w [ 0 ] self.w_[1:],self.w_[0] self.w[1:]self.w[0]后,将 w 0 w_0 w0加到函数表达式$ result = np.dot(X, self.w_[1:]) + self.w_[0]$中即可。

  通过上文介绍的梯度下降法求解线性回归过程,我们可以看到在结论中,都要乘以一项 x j ( i ) x_j^{(i)} xj(i),而该项正对应下面X矩阵的每一列数据。为了能够和 y − y h a t y - y_{hat} yyhat相乘后求和,这里刚好对应代码中的 n p . d o t ( X . T , e r r o r ) np.dot(X.T, error) np.dot(X.T,error)
X = [ x 11 x 12 ⋯ x 1 p x 21 x 22 ⋯ x 2 p ⋯ x n 1 x n 2 ⋯ x n p ] X= \left[ \begin{array}{c} x_{11} & x_{12} & \cdots & x_{1p} \\ x_{21} & x_{22} & \cdots & x_{2p} \\ \cdots \\ x_{n1} & x_{n2} & \cdots & x_{np} \end{array} \right] X=x11x21xn1x12x22xn2x1px2pxnp
2、由于这里涉及到了NumPy中的ndarray与Pandas的Series和DataFrame中的关系,我在下面这篇博客中进行简单叙述。

https://blog.csdn.net/weixin_43334389/article/details/109962837
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值