如何在Python中从头开始实现线性回归
许多机器学习算法的核心是优化。
机器学习算法使用优化算法来找到给定训练数据集的一组良好的模型参数。
机器学习中最常用的优化算法是随机梯度下降。
在本教程中,你将了解如何使用Python从头开始实现随机梯度下降来优化线性回归算法。
完成本教程后,你将了解:
- 如何使用随机梯度下降估计线性回归系数。
- 如何对多元线性回归进行预测。
- 如何实现随机梯度下降的线性回归对新数据进行预测。
描述
在本节中,我们将描述线性回归、随机梯度下降技术和本教程中使用的葡萄酒质量数据集。
多元线性回归
线性回归是一种预测实际值的技术。
令人困惑的是,这些需要预测真实值的问题被称为回归问题。
线性回归是一种使用直线对输入和输出值之间的关系进行建模的技术。在多于两个维度中,这条直线可以被认为是一个平面或超平面。
预测作为输入值的组合来预测输出值。
使用系数(B)对每个输入属性(X)进行加权,并且学习算法的目标是发现一组导致良好预测(Y)的系数。
y = b0 + b1 * x1 + b2 * x2 + ...
系数可以使用随机梯度下降来找到。
随机梯度下降
梯度下降是通过跟随成本函数的梯度最小化函数的过程。
这涉及到知道成本和导数的形式,以便从给定点知道梯度,并可以向该方向移动,例如,向最小值下坡。
在机器学习中,我们可以使用一种在每次迭代中评估和更新系数的技术,称为随机梯度下降,以最小化训练数据上的模型误差。
该优化算法的工作方式是将每个训练实例一次一个地显示给模型。该模型对训练实例进行预测,计算误差并更新模型,以减少下一次预测的误差。此过程重复固定的迭代次数。
该过程可用于在模型中找到在训练数据上导致该模型的最小误差的系数集。每次迭代,使用等式更新机器学习语言中的系数(B):
b = b - learning_rate * error * x
其中,b是正在优化的系数或权重,Learning_rate是你必须配置的学习率(例如0.01),error是归因于权重的模型在训练数据上的预测误差,x是输入值。
葡萄酒质量数据集
在我们开发了随机梯度下降的线性回归算法之后,我们将使用它来对葡萄酒质量数据集进行建模。
这个数据集由4898种白葡萄酒的细节组成,包括酸度和pH值等测量。我们的目标是使用这些客观的衡量标准在0到10的范围内预测葡萄酒的质量。
下面是此数据集中前5条记录的示例。
7,0.27,0.36,20.7,0.045,45,170,1.001,3,0.45,8.8,6 6.3,0.3,0.34,1.6,0.049,14,132,0.994,3.3,0.49,9.5,6 8.1,0.28,0.4,6.9,0.05,30,97,0.9951,3.26,0.44,10.1,6 7.2,0.23,0.32,8.5,0.058,47,186,0.9956,3.19,0.4,9.9,6 7.2,0.23,0.32,8.5,0.058,47,186,0.9956,3.19,0.4,9.9,6
数据集必须归一化为介于0和1之间的值,因为每个属性具有不同的单位,进而具有不同的比例。
通过预测归一化数据集上的平均值(零规则算法),基线均方误差可以达到0.148。
你可以在UCI机器学习存储库上了解有关数据集的更多信息。
你可以下载数据集并将其保存在当前工作目录中,名称为winequality-white.csv。必须从文件开头删除标题信息,并将“;”值分隔符转换为“,”以满足CSV格式。
教程
本教程分为3个部分:
- 作出预测。
- 估计系数。
- 葡萄酒质量预测。
这将为你在自己的预测建模问题上实现和应用具有随机梯度下降的线性回归提供基础。
1.作出预测。
第一步是开发一个可以进行预测的函数。
这在评估随机梯度下降中的候选系数值时以及在模型最终确定之后都是需要的,我们希望开始对测试数据或新数据进行预测。
下面是一个名为predict()的函数,它预测给定一组系数的行的输出值。
中的第一个系数始终是截距,也称为偏差或b0,因为它是独立的,不负责特定的输入值。
# Make a prediction with coefficients def predict(row, coefficients): yhat = coefficients[0] for i in range(len(row)-1): yhat += coefficients[i + 1] * row[i] return yhat
我们可以设计一个小数据集来测试我们的预测功能。
x, y 1, 1 2, 3 4, 3 3, 2 5, 5
下面是此数据集的曲线图。
我们还可以使用先前准备的系数对该数据集进行预测。
将所有这些放在一起,我们可以测试下面的predict()函数。
# Make a prediction with coefficients def predict(row, coefficients): yhat = coefficients[0] for i in range(len(row)-1): yhat += coefficients[i + 1] * row[i] return yhat dataset = [[1, 1], [2, 3], [4, 3], [3, 2], [5, 5]] coef = [0.4, 0.8] for row in dataset: yhat = predict(row, coef) print("Expected=%.3f, Predicted=%.3f" % (row[-1], yhat))
有一个输入值(x)和两个系数值(b0和b1)。我们为此问题建模的预测方程是:
y = b0 + b1 * x
或者,使用我们手动选择的特定系数值,如下所示:
y = 0.4 + 0.8 * x
运行此函数,我们可以得到相当接近预期输出(y)值的预测。
Expected=1.000, Predicted=1.200 Expected=3.000, Predicted=2.000 Expected=3.000, Predicted=3.600 Expected=2.000, Predicted=2.800 Expected=5.000, Predicted=4.400
现在我们准备实现随机梯度下降来优化我们的系数值。
2.估算系数
我们可以使用随机梯度下降来估计训练数据的系数值。
随机梯度下降需要两个参数:
- 学习率:用于限制每个系数每次更新时的修正量。
- 纪元:更新系数时运行训练数据的次数。
这些以及训练数据将作为函数的参数。
我们需要在函数中执行3个循环:
- 在每个纪元上循环。
- 循环遍历一个纪元的训练数据中的每一行。
- 循环遍历每个系数,并针对纪元中的一行进行更新。
如你所见,我们为训练数据中的每一行、每个时期更新了每个系数。
系数根据模型产生的误差进行更新。误差被计算为用候选系数进行的预测与预期输出值之间的差值。
error = prediction - expected
每个输入属性都有一个系数来加权,并且这些系数以一致的方式更新,例如:
b1(t+1) = b1(t) - learning_rate * error(t) * x1(t)
列表开头的特殊系数(也称为截距或偏差)以类似方式更新,但没有输入,因为它不与特定输入值相关联:
b0(t+1) = b0(t) - learning_rate * error(t)
现在我们可以把所有这些放在一起了。下面是一个名为coefficients_sgd()的函数,该函数使用随机梯度下降来计算训练数据集的系数值。
# Estimate linear regression coefficients using stochastic gradient descent def coefficients_sgd(train, l_rate, n_epoch): coef = [0.0 for i in range(len(train[0]))] for epoch in range(n_epoch): sum_error = 0 for row in train: yhat = predict(row, coef) error = yhat - row[-1] sum_error += error**2 coef[0] = coef[0] - l_rate * error for i in range(len(row)-1): coef[i + 1] = coef[i + 1] - l_rate * error * row[i] print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error)) return coef
你可以看到,此外,我们还跟踪每个历元的平方误差总和(正值),这样我们就可以在外部循环中打印出一条漂亮的消息。
我们可以在上面相同的人工设计的小数据集上测试此函数。
# Make a prediction with coefficients def predict(row, coefficients): yhat = coefficients[0] for i in range(len(row)-1): yhat += coefficients[i + 1] * row[i] return yhat # Estimate linear regression coefficients using stochastic gradient descent def coefficients_sgd(train, l_rate, n_epoch): coef = [0.0 for i in range(len(train[0]))] for epoch in range(n_epoch): sum_error = 0 for row in train: yhat = predict(row, coef) error = yhat - row[-1] sum_error += error**2 coef[0] = coef[0] - l_rate * error for i in range(len(row)-1): coef[i + 1] = coef[i + 1] - l_rate * error * row[i] print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error)) return coef # Calculate coefficients dataset = [[1, 1], [2, 3], [4, 3], [3, 2], [5, 5]] l_rate = 0.001 n_epoch = 50 coef = coefficients_sgd(dataset, l_rate, n_epoch) print(coef)
我们使用0.001的小学习率,并对模型进行50个历元的训练,或者对整个训练数据集进行50次系数曝光。
运行该示例将在每个历元打印一条消息,其中包含该历元和最终系数集的平方和误差。
>epoch=45, lrate=0.001, error=2.650 >epoch=46, lrate=0.001, error=2.627 >epoch=47, lrate=0.001, error=2.607 >epoch=48, lrate=0.001, error=2.589 >epoch=49, lrate=0.001, error=2.573 [0.22998234937311363, 0.8017220304137576]
你可以看到,即使在最后一个纪元,误差也是如何持续下降的。我们也许可以训练更长的时间(更多的纪元),或者增加我们在每个纪元更新系数的量(更高的学习率)。
试一试,看看你能想出什么。
现在,让我们将此算法应用于实际数据集。
3.酒质预测
在本节中,我们将在葡萄酒质量数据集上使用随机梯度下降训练线性回归模型。
该示例假设数据集的CSV副本位于当前工作目录中,文件名为winequality-white.csv。
首先加载数据集,将字符串值转换为数字,并将每列标准化为0到1范围内的值。这是通过辅助函数load_csv()和str_column_to_float()加载和准备数据集,以及使用dataset_minmax()和normalize_dataset()对其进行标准化来实现的。
我们将使用k重交叉验证来估计学习模型在未见数据上的性能。这意味着我们将构建和评估k个模型,并以平均模型误差来估计性能。将使用均方根误差来评估每个模型。这些行为在cross_validation_split()、rmse_metric()和evaluate_algorithm()帮助器函数中提供。
我们将使用上面创建的predict()、coefficients_sgd()和linear_regression_sgd()函数来训练模型。
下面是完整的示例。
# Linear Regression With Stochastic Gradient Descent for Wine Quality from random import seed from random import randrange from csv import reader from math import sqrt # Load a CSV file def load_csv(filename): dataset = list() with open(filename, 'r') as file: csv_reader = reader(file) for row in csv_reader: if not row: continue dataset.append(row) return dataset # Convert string column to float def str_column_to_float(dataset, column): for row in dataset: row[column] = float(row[column].strip()) # Find the min and max values for each column def dataset_minmax(dataset): minmax = list() for i in range(len(dataset[0])): col_values = [row[i] for row in dataset] value_min = min(col_values) value_max = max(col_values) minmax.append([value_min, value_max]) return minmax # Rescale dataset columns to the range 0-1 def normalize_dataset(dataset, minmax): for row in dataset: for i in range(len(row)): row[i] = (row[i] - minmax[i][0]) / (minmax[i][1] - minmax[i][0]) # Split a dataset into k folds def cross_validation_split(dataset, n_folds): dataset_split = list() dataset_copy = list(dataset) fold_size = int(len(dataset) / n_folds) for i in range(n_folds): fold = list() while len(fold) < fold_size: index = randrange(len(dataset_copy)) fold.append(dataset_copy.pop(index)) dataset_split.append(fold) return dataset_split # Calculate root mean squared error def rmse_metric(actual, predicted): sum_error = 0.0 for i in range(len(actual)): prediction_error = predicted[i] - actual[i] sum_error += (prediction_error ** 2) mean_error = sum_error / float(len(actual)) return sqrt(mean_error) # Evaluate an algorithm using a cross validation split def evaluate_algorithm(dataset, algorithm, n_folds, *args): folds = cross_validation_split(dataset, n_folds) scores = list() for fold in folds: train_set = list(folds) train_set.remove(fold) train_set = sum(train_set, []) test_set = list() for row in fold: row_copy = list(row) test_set.append(row_copy) row_copy[-1] = None predicted = algorithm(train_set, test_set, *args) actual = [row[-1] for row in fold] rmse = rmse_metric(actual, predicted) scores.append(rmse) return scores # Make a prediction with coefficients def predict(row, coefficients): yhat = coefficients[0] for i in range(len(row)-1): yhat += coefficients[i + 1] * row[i] return yhat # Estimate linear regression coefficients using stochastic gradient descent def coefficients_sgd(train, l_rate, n_epoch): coef = [0.0 for i in range(len(train[0]))] for epoch in range(n_epoch): for row in train: yhat = predict(row, coef) error = yhat - row[-1] coef[0] = coef[0] - l_rate * error for i in range(len(row)-1): coef[i + 1] = coef[i + 1] - l_rate * error * row[i] # print(l_rate, n_epoch, error) return coef # Linear Regression Algorithm With Stochastic Gradient Descent def linear_regression_sgd(train, test, l_rate, n_epoch): predictions = list() coef = coefficients_sgd(train, l_rate, n_epoch) for row in test: yhat = predict(row, coef) predictions.append(yhat) return(predictions) # Linear Regression on wine quality dataset seed(1) # load and prepare data filename = 'winequality-white.csv' dataset = load_csv(filename) for i in range(len(dataset[0])): str_column_to_float(dataset, i) # normalize minmax = dataset_minmax(dataset) normalize_dataset(dataset, minmax) # evaluate algorithm n_folds = 5 l_rate = 0.01 n_epoch = 50 scores = evaluate_algorithm(dataset, linear_regression_sgd, n_folds, l_rate, n_epoch) print('Scores: %s' % scores) print('Mean RMSE: %.3f' % (sum(scores)/float(len(scores))))
使用k值5进行交叉验证,给出每个折叠4,898/5=979.6或略低于1,000条记录以在每次迭代时进行评估。通过少量实验选择了0.01的学习率和50个训练周期。
你可以试试你自己的配置,看看能不能超过我的分数。
运行此示例将打印5个交叉验证折叠中每一个的分数,然后打印平均值RMSE。
我们可以看到,均方根误差(在归一化数据集上)为0.126,如果我们只预测平均值(使用零规则算法),则低于基准值0.148。
Scores: [0.12248058224159092, 0.13034017509167112, 0.12620370547483578, 0.12897687952843237, 0.12446990678682233] Mean RMSE: 0.126
延拓
本节列出了本教程的许多扩展,你可能希望研究这些扩展。
- 调整示例。调整学习率、纪元数甚至数据准备方法,在葡萄酒质量数据集上获得更高的分数。
- 批量随机梯度下降。更改随机梯度下降算法以累积每个历元的更新,并且仅在历元结束时批量更新系数。
- 其他回归问题。将该技术应用于UCI机器学习库上的其他回归问题。