利用随机梯度递减解决线性回归问题(附python代码)

数据来源:来自加州大学埃文分校(UCI)机器学习数据库,采用白酒质量数据集合,共有4898条数据,共包含12项评价指标分别为酸度、游离二氧化硫、密度、pH、氯化物、硫酸盐、酒精度含量等,最后一项表示白酒的评级(0-10级)

链接:http://archive.ics.uci.edu/ml/datasets/Wine+Quality

一、认识数据

通过观察数据,有些指标范围在0-1之间,有些指标取值范围大的数百,小的又在小数点的百分位浮动,如果不对数据特征作处理,小数据特征很容易被大数据特征淹没,要进行归一化处理。

二、数据读取及预处理

定义了Database类,主要负责完成数据的读取和预处理。预处理包含两部分:(1)把csv文件中读取的字符串数值,转换为可计算的浮点数(由dataset_str_to_float方法来完成);(2)将第(1)步加工过的数据做归一化处理。

from csv import reader
class Database():
        def __init__(self):
            self.dataset=list()

        # 导入csv文件
        def load_csv(self,filename):
            with open(filename,'r',encoding='utf-8') as file:
                csv_reader=reader(file)
                headings=next(csv_reader)    # 读取表头,文件指针下移至第一条真正的数据
                for row in csv_reader:
                    if not row:       # 判定是否有空行,如有,则跳入下一行
                        continue
                    self.dataset.append(row)

        # 将字符串数转换为浮点数
        def dataset_str_to_float(self):
            col_len=len(self.dataset[0])
            for row in self.dataset:
                for column in range(col_len):
                    row[column]=float(row[column].strip())

        # 找到每一列(属性)的最小值和最大值
        def _dataset_minmax(self):
            self.minmax=list()
            for i in range(len(self.dataset[0])):
                col_values=[row[i] for row in self.dataset]
                value_min=min(col_values)
                value_max=max(col_values)
                self.minmax.append([value_min,value_max])

         # 将数据集合中的每个(列)属性都规整化到0~1之间
        def normalize_dataset(self):
            self._dataset_minmax()     # 获取每一列的最小值,最大值
            for row in self.dataset:
                for i in range(len(row)):
                    row[i]=(row[i]-self.minmax[i][0])/(self.minmax[i][1]-self.minmax[i][0])
            return self.dataset

三、训练模型,找到适配数据的权值

定义了线性单元类LinearUnit,它利用随机梯度递减的策略,完成权值网络更新的功能。

class LinearUnit(object):
    def __init__(self,input_para_num,acti_func):
          """
          :param input_para_num:
          :param acti_func:
          """
          self.activator=acti_func      # 初始化线性单元激活函数

    def predict(self,row_vec):
          """
          :param row_vec: 输入向量,输出线性单元的预测结果
          :return:
          """
          act_values=self.weights[0]
          for i in range(len(row_vec)-1):
              act_values+=self.weights[i+1]*row_vec[i]
          return self.activator(act_values)

    def train_sgd(self,dataset,rate,n_epoch):
          """
          :param dataset: 训练数据
          :param rate: 学习率
          :param n_epoch: 训练轮数
          :return:
          """
          self.weights=[0.0 for i in range(len(dataset[0]))]   # 权重向量初始化为0
          for i in range(n_epoch):
              for input_vec_label in dataset:
                  prediction=self.predict(input_vec_label)
                  self._update_weights(input_vec_label,prediction,rate)    # 更新权值

    def _update_weights(self,input_vec_label,prediction,rate):
          delta=input_vec_label[-1]- prediction
          self.weights[0]=self.weights[0]+rate+delta   # 更新权值:第一个哑元的权值
          for i in range(len(self.weights)-1):
              self.weights[i+1]=self.weights[i+1]+rate*delta*input_vec_label[i]

定义激活函数

# 定义激活函数f
def func_activator(input_value):
        return input_value

四、对测试数据进行预测,以验证模型的正确性

定义主方法,从训练数据中随机抽取4条数据,来验证模型的正确性(这些测试数据也必须先进行归一化处理,保证模型与测试数据在同一尺度上)。

# 构建训练数据
def get_training_dataset():
    db=Database()
    db.load_csv("winequality-white.csv")
    db.dataset_str_to_float()
    dataset=db.normalize_dataset()
    return dataset

def train_linear_unit():
    dataset=get_training_dataset()
    l_rate=0.01
    n_epoch=100
    linear_unit=LinearUnit(len(dataset[0]),func_activator)   # 创建训练线性单元,输入参数的特征数
    linear_unit.train_sgd(dataset,l_rate,n_epoch)   # 训练,迭代100轮,学习率为0.01
    return linear_unit          # 返回训练好的线性单元


if __name__=='__main__':
     LU=train_linear_unit()    # 获取训练数据并训练
     print("weights =",LU.weights)   # 打印训练获得的权重
     test_datas=[[0.38,0.14 ,0.23 ,0.15 ,0.15 ,0.09 ,0.21 ,0.18 ,0.36 ,0.29 ,0.32 ,0.5],
            [0.32,0.26 ,0.20 ,0.18 ,0.12 ,0.01 ,0.06 ,0.14 ,0.37 ,0.20 ,0.56 ,0.5],
            [0.32,0.12 ,0.20 ,0.03 ,0.07 ,0.16 ,0.33 ,0.06 ,0.40 ,0.20 ,0.76 ,0.66],
            [0.37,0.16 ,0.16 ,0.02 ,0.12 ,0.17 ,0.42 ,0.11 ,0.46 ,0.33 ,0.45 ,0.5]]
     for i in range(len(test_datas)):
        pred=LU.predict(test_datas[i])
        print("期望值={0},预测值={1}".format(test_datas[i][-1],pred))


输出结果:

weights = [1.3839765803171225, -0.9551010731530127, -0.427351513938813, -0.2812656304046924, 1.0103283347419902, -0.5671087345073004, -0.13481327366753815, -0.3471629881143812, -2.262791619220822, -0.4288675410738061, 0.04115382495757484, -0.2481081346020334]
期望值=0.5,预测值=0.2488085833942974
期望值=0.5,预测值=0.3964262500184253
期望值=0.66,预测值=0.3376413270279569
期望值=0.5,预测值=0.1563832018348324

 

<think>我们被要求以教学方式详细分析一段线性回归模型代码。根据引用[1]和引用[2]的内容,我们可以看到线性回归代码实现通常包括数据预处理、模型构建(包括前向传播、代价函数、反向传播、参数更新等步骤)以及训练过程。此外,引用[3]中展示了使用scikit-learn库实现线性回归的简洁方式。 由于用户没有提供具体的代码,我们将参考常见的线性回归代码实现,并按照教学步骤详细分析。这里我们假设使用纯Python(不依赖机器学习库)来实现一元线性回归,因为这样更能揭示其原理。 ### 一元线性回归模型代码分析 下面是一段用纯Python实现的一元线性回归代码。我们将逐部分解释。 ```python import numpy as np import matplotlib.pyplot as plt # 生成模拟数据 np.random.seed(0) X = 2 * np.random.rand(100, 1) # 生成100个在[0,2)之间的随机数作为特征 y = 4 + 3 * X + np.random.randn(100, 1) # 生成带有噪声的标签,真实模型为 y = 4 + 3x # 数据可视化 plt.scatter(X, y) plt.xlabel('X') plt.ylabel('y') plt.title('Simulated Data') plt.show() # 初始化参数 theta0 = np.random.randn() # 截距初始化为随机值 theta1 = np.random.randn() # 斜率初始化为随机值 # 设置学习率和迭代次数 learning_rate = 0.1 n_iterations = 1000 # 梯度下降 for iteration in range(n_iterations): # 前向传播:计算预测值 y_pred = theta0 + theta1 * X # 计算代价函数(均方误差) cost = (1/(2*len(X))) * np.sum((y_pred - y)**2) # 反向传播:计算梯度 d_theta0 = (1/len(X)) * np.sum(y_pred - y) # 对theta0的偏导 d_theta1 = (1/len(X)) * np.sum((y_pred - y) * X) # 对theta1的偏导 # 参数更新 theta0 = theta0 - learning_rate * d_theta0 theta1 = theta1 - learning_rate * d_theta1 # 每100次迭代打印一次代价函数值 if iteration % 100 == 0: print(f'Iteration {iteration}, Cost: {cost}') # 打印最终参数 print(f'Final parameters: theta0 = {theta0}, theta1 = {theta1}') # 绘制回归线 plt.scatter(X, y) plt.plot(X, theta0 + theta1 * X, color='red') plt.xlabel('X') plt.ylabel('y') plt.title('Linear Regression Fit') plt.show() ``` #### 代码分析步骤 1. **数据生成与可视化**: - 使用`numpy`生成模拟数据,其中$X$是特征,$y$是目标变量。这里我们假设真实模型为$y = 4 + 3x + \epsilon$,其中$\epsilon$是噪声。 - 使用`matplotlib`绘制散点图,观察数据分布。 2. **参数初始化**: - 初始化截距$\theta_0$和斜率$\theta_1$为随机值。在更复杂的模型中,我们通常使用随机初始化来打破对称性,但在一元线性回归中,初始化影响不大。 3. **设置超参数**: - 学习率(`learning_rate`):控制每次参数更新的步长。过大会导致震荡甚至发散,过小则收敛慢。 - 迭代次数(`n_iterations`):控制梯度下降的轮数。 4. **梯度下降循环**: - **前向传播**:计算当前参数下的预测值,即$\hat{y} = \theta_0 + \theta_1 X$。 - **代价函数**:计算均方误差(MSE)的一半(这里为了求导方便,常除以2,使得梯度中系数为1)。公式为: $$J(\theta_0, \theta_1) = \frac{1}{2m}\sum_{i=1}^{m}(\hat{y}^{(i)} - y^{(i)})^2$$ 其中$m$是样本数量。 - **反向传播(梯度计算)**:计算代价函数对参数的偏导数: $$\frac{\partial J}{\partial \theta_0} = \frac{1}{m}\sum_{i=1}^{m}(\hat{y}^{(i)} - y^{(i)})$$ $$\frac{\partial J}{\partial \theta_1} = \frac{1}{m}\sum_{i=1}^{m}(\hat{y}^{(i)} - y^{(i)}) \cdot X^{(i)}$$ - **参数更新**:沿着梯度反方向更新参数(因为梯度方向是函数值增加最快的方向,我们要最小化代价函数): $$\theta_0 := \theta_0 - \alpha \frac{\partial J}{\partial \theta_0}$$ $$\theta_1 := \theta_1 - \alpha \frac{\partial J}{\partial \theta_1}$$ 其中$\alpha$是学习率。 5. **输出与可视化**: - 每100次迭代打印一次代价函数值,观察是否在下降。 - 训练结束后,打印最终参数,并绘制回归线。 #### 注意事项 - 在实际应用中,我们通常会对特征进行归一化,以加快梯度下降的收敛速度。但在一元线性回归中,如果特征范围不大,可以不做归一化。 - 梯度下降有多种变体(如批量梯度下降、随机梯度下降、小批量梯度下降)。上述代码使用的是批量梯度下降(每次迭代使用全部样本计算梯度)。 - 学习率的选择很重要,可以通过学习率衰减或尝试多个值来选择。 ### 使用scikit-learn实现 对于实际项目,我们通常使用成熟的库,如scikit-learn,它提供了高效且稳健的实现。引用[2]和引用[3]中都有相关示例。下面是一个简单的示例: ```python from sklearn.linear_model import LinearRegression import numpy as np # 生成数据 X = 2 * np.random.rand(100, 1) y = 4 + 3 * X + np.random.randn(100, 1) # 创建模型并训练 model = LinearRegression() model.fit(X, y) # 输出参数 print(f'截距: {model.intercept_}, 斜率: {model.coef_}') ``` scikit-learn的`LinearRegression`使用最小二乘法(解析解)或梯度下降(具体取决于实现)来求解参数,通常更高效。 ### 总结 通过上述代码分析,我们了解了线性回归的基本实现步骤,包括数据准备、模型训练(梯度下降)和结果可视化。对于初学者,建议从纯Python实现开始以理解原理,然后转向成熟的库以用于实际项目。 §§相关问题§§ 1. 梯度下降中学习率的选择对训练有什么影响? 2. 除了均方误差,线性回归还可以使用哪些损失函数? 3. 如何处理多元线性回归中的特征共线性问题? 4. 线性回归模型在什么情况下会过拟合?如何避免?</think>### 线性回归模型代码教学分析 下面我将以教学方式详细分析一段基于梯度下降法的线性回归Python实现代码,逐步解释每个关键部分的原理和作用。代码参考了机器学习中线性回归的核心实现逻辑[^1][^2]。 ```python import numpy as np import matplotlib.pyplot as plt class LinearRegression: def __init__(self, learning_rate=0.01, n_iter=1000): # 初始化模型参数 self.learning_rate = learning_rate # 学习率控制参数更新步长 self.n_iter = n_iter # 迭代次数 self.theta = None # 模型参数(权重) self.cost_history = [] # 记录代价函数变化 def fit(self, X, y): # 添加偏置项(截距项) X = np.c_[np.ones((X.shape[0], 1)), X] # 添加一列1作为x0 # 初始化参数矩阵(theta0, theta1, ...) self.theta = np.random.randn(X.shape[1], 1) # 梯度下降迭代 for i in range(self.n_iter): # 1. 前向传播:计算预测值 y_pred = X.dot(self.theta) # 2. 计算代价函数(均方误差) cost = (1/(2*len(y))) * np.sum((y_pred - y)**2) self.cost_history.append(cost) # 3. 反向传播:计算梯度 gradients = (1/len(y)) * X.T.dot(y_pred - y) # 4. 参数更新:沿梯度反方向更新 self.theta = self.theta - self.learning_rate * gradients # 每100次迭代打印进度 if i % 100 == 0: print(f"Iteration {i}: Cost = {cost:.4f}") def predict(self, X): # 添加偏置项后进行预测 X = np.c_[np.ones((X.shape[0], 1)), X] return X.dot(self.theta) def plot_cost_history(self): # 可视化代价函数变化过程 plt.plot(range(self.n_iter), self.cost_history) plt.xlabel('Iterations') plt.ylabel('Cost') plt.title('Cost Function Convergence') plt.show() ``` ### 代码分步解析 #### 1. 初始化方法 (`__init__`) ```python def __init__(self, learning_rate=0.01, n_iter=1000): self.learning_rate = learning_rate # 学习率(步长参数) self.n_iter = n_iter # 训练迭代次数 self.theta = None # 模型参数向量(θ) self.cost_history = [] # 代价函数历史记录 ``` - **学习率**:控制每次参数更新的步长,过大可能导致震荡,过小则收敛缓慢 - **迭代次数**:梯度下降的轮数,需足够大以确保收敛 - **参数theta**:包含截距项($\theta_0$)和特征权重($\theta_1,\theta_2,\cdots$) - **代价历史**:记录每次迭代的损失值,用于监控训练过程 #### 2. 添加偏置项 (数据预处理) ```python X = np.c_[np.ones((X.shape[0], 1)), X] # 添加一列1作为x0 ``` - 在特征矩阵前添加一列全1向量,对应截距项$\theta_0$ - 使线性方程保持统一形式:$h_\theta(x) = \theta_0 + \theta_1x_1 + \cdots + \theta_nx_n$ - 数学表示为:$X \leftarrow [\vec{1} | X]$ #### 3. 前向传播 (`y_pred = X.dot(self.theta)`) - 计算当前参数下的预测值:$\hat{y} = X\theta$ - 矩阵形式:$\begin{bmatrix} \hat{y}_1 \\ \hat{y}_2 \\ \vdots \\ \hat{y}_m \end{bmatrix} = \begin{bmatrix} 1 & x_{11} & \cdots & x_{1n} \\ 1 & x_{21} & \cdots & x_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ 1 & x_{m1} & \cdots & x_{mn} \end{bmatrix} \begin{bmatrix} \theta_0 \\ \theta_1 \\ \vdots \\ \theta_n \end{bmatrix}$ #### 4. 代价函数计算 ```python cost = (1/(2*len(y))) * np.sum((y_pred - y)**2) ``` - 使用**均方误差(MSE)** 作为损失函数 - 数学公式:$J(\theta) = \frac{1}{2m}\sum_{i=1}^{m}(h_\theta(x^{(i)}) - y^{(i)})^2$ - 除以2是为了后续梯度计算时简化导数形式 - 记录cost_history用于观察收敛情况 #### 5. 反向传播与梯度计算 ```python gradients = (1/len(y)) * X.T.dot(y_pred - y) ``` - 计算代价函数对参数的偏导数 - 向量化公式:$\nabla J(\theta) = \frac{1}{m}X^T(X\theta - y)$ - 其中$X^T$是特征矩阵的转置 - $(X\theta - y)$是预测值与真实值的误差向量 #### 6. 参数更新 ```python self.theta = self.theta - self.learning_rate * gradients ``` - 梯度下降核心更新规则:$\theta := \theta - \alpha \nabla J(\theta)$ - $\alpha$是学习率,控制更新步长 - 沿梯度反方向更新参数以最小化损失函数 - 迭代直到收敛或达到最大迭代次数 #### 7. 预测方法 ```python def predict(self, X): X = np.c_[np.ones((X.shape[0], 1)), X] return X.dot(self.theta) ``` - 对新数据添加偏置项后应用训练好的参数 - 返回预测结果:$\hat{y} = X\theta$ ### 可视化分析 ```python def plot_cost_history(self): plt.plot(range(self.n_iter), self.cost_history) ``` - 绘制代价函数随迭代次数的变化曲线 - 理想情况下应呈现单调递减趋势 - 用于诊断学习率是否合适(震荡说明过大,下降过缓说明过小) ### 使用示例 ```python # 生成样本数据 X = 2 * np.random.rand(100, 1) y = 4 + 3 * X + np.random.randn(100, 1) # 创建模型并训练 model = LinearRegression(learning_rate=0.1, n_iter=1000) model.fit(X, y) # 预测新数据 X_new = np.array([[0], [2]]) y_pred = model.predict(X_new) # 可视化结果 plt.scatter(X, y) plt.plot(X_new, y_pred, "r-") plt.show() # 查看训练过程 model.plot_cost_history() ``` ### 关键数学原理 1. **假设函数**:$h_\theta(x) = \theta^Tx = \theta_0 + \theta_1x_1 + \cdots + \theta_nx_n$ 2. **代价函数**:$J(\theta) = \frac{1}{2m}\sum_{i=1}^{m}(h_\theta(x^{(i)}) - y^{(i)})^2$ 3. **梯度计算**:$\frac{\partial J}{\partial \theta_j} = \frac{1}{m}\sum_{i=1}^{m}(h_\theta(x^{(i)}) - y^{(i)})x_j^{(i)}$ 4. **参数更新**:$\theta_j := \theta_j - \alpha \frac{\partial J}{\partial \theta_j}$ ### 注意事项 1. **特征缩放**:实际应用中应对特征进行归一化(如Z-score标准化),加速收敛 2. **学习率选择**:可通过学习率衰减策略提高后期训练稳定性 3. **迭代停止**:可添加早停机制(当损失变化小于阈值时停止) 4. **正则化**:为防止过拟合,可添加L2正则化项(岭回归)[^3] 通过这个实现,可以清晰理解线性回归梯度下降的核心原理,为理解更复杂模型奠定基础[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值