Machine-Learning-Mastery-机器学习算法教程-四-

102 阅读53分钟

Machine Learning Mastery 机器学习算法教程(四)

原文:Machine Learning Mastery

协议:CC BY-NC-SA 4.0

如何利用 Python 从零开始实现逻辑回归

原文: machinelearningmastery.com/implement-logistic-regression-stochastic-gradient-descent-scratch-python/

逻辑回归是两类问题的首选线性分类算法。

它易于实现,易于理解,并且可以在各种各样的问题上获得很好的结果,即使这些方法对您的数据的期望受到侵犯也是如此。

在本教程中,您将了解如何使用 Python 从零开始随机梯度下降实现逻辑回归。

完成本教程后,您将了解:

  • 如何使用逻辑回归模型做出预测。
  • 如何使用随机梯度下降估计系数。
  • 如何将逻辑回归应用于实际预测问题。

让我们开始吧。

  • 2017 年 1 月更新:将 cross_validation_split()中的 fold_size 计算更改为始终为整数。修复了 Python 3 的问题。
  • 更新 Mar / 2018 :添加了备用链接以下载数据集,因为原始图像已被删除。
  • 更新 Aug / 2018 :经过测试和更新,可与 Python 3.6 配合使用。

How To Implement Logistic Regression With Stochastic Gradient Descent From Scratch With Python

如何使用随机梯度下降实现逻辑回归从 Python 照片 Ian Sane ,保留一些权利。

描述

本节将简要介绍逻辑回归技术,随机梯度下降和我们将在本教程中使用的 Pima Indians 糖尿病数据集。

逻辑回归

逻辑回归以在该方法的核心使用的函数命名,即逻辑函数。

逻辑回归使用方程作为表示,非常类似于线性回归。使用权重或系数值线性组合输入值( X )以预测输出值( y )。

与线性回归的主要区别在于,建模的输出值是二进制值(0 或 1)而不是数值。

yhat = e^(b0 + b1 * x1) / (1 + e^(b0 + b1 * x1))

这可以简化为:

yhat = 1.0 / (1.0 + e^(-(b0 + b1 * x1)))

其中 e 是自然对数(欧拉数)的基础, yhat 是预测输出, b0 是偏差或截距项和 b1 是单输入值的系数( x1 )。

yhat 预测是 0 到 1 之间的实数值,需要舍入为整数值并映射到预测的类值。

输入数据中的每一列都有一个相关的 b 系数(一个恒定的实际值),必须从训练数据中学习。您将存储在存储器或文件中的模型的实际表示是等式中的系数(β值或 b)。

逻辑回归算法的系数必须根据您的训练数据进行估算。

随机梯度下降

梯度下降是通过遵循成本函数的梯度来最小化函数的过程。

这包括了解成本的形式以及衍生物,以便从给定的点知道梯度并且可以在该方向上移动,例如,向下走向最小值。

在机器学习中,我们可以使用一种技术来评估和更新称为随机梯度下降的每次迭代的系数,以最小化模型对我们的训练数据的误差。

此优化算法的工作方式是每个训练实例一次显示给模型一个。该模型对训练实例做出预测,计算误差并更新模型以减少下一次预测的误差。

该过程可用于在模型中找到导致训练数据上模型的最小误差的系数集。每次迭代,机器学习语言中的系数(b)使用以下等式更新:

b = b + learning_rate * (y - yhat) * yhat * (1 - yhat) * x

b 是被优化的系数或权重, learning_rate 是你必须配置的学习率(例如 0.01),**(y - yhat)**是预测对于归因于权重的训练数据的模型的误差, yhat 是由系数做出的预测, x 是输入值。

皮马印第安人糖尿病数据集

Pima Indians 数据集涉及在皮马印第安人中预测 5 年内糖尿病的发病情况,并提供基本医疗细节。

这是一个二分类问题,其中预测是 0(无糖尿病)或 1(糖尿病)。

它包含 768 行和 9 列。文件中的所有值都是数字,特别是浮点值。下面是问题前几行的一小部分样本。

6,148,72,35,0,33.6,0.627,50,1
1,85,66,29,0,26.6,0.351,31,0
8,183,64,0,0,23.3,0.672,32,1
1,89,66,23,94,28.1,0.167,21,0
0,137,40,35,168,43.1,2.288,33,1
...

预测多数类(零规则算法),该问题的基线表现为 65.098%分类精度。

您可以在 UCI 机器学习库上了解有关此数据集的更多信息(更新:从此处下载)。

下载数据集并使用文件名 pima-indians-diabetes.csv 将其保存到当前工作目录。

教程

本教程分为 3 个部分。

  1. 做出预测。
  2. 估计系数。
  3. 糖尿病预测。

这将为您自己的预测性建模问题提供实现和应用具有随机梯度下降的逻辑回归所需的基础。

1.做出预测

第一步是开发一个可以做出预测的功能。

在随机梯度下降中的候选系数值的评估中以及在模型完成之后,我们希望开始对测试数据或新数据做出预测。

下面是一个名为 **predict()**的函数,它预测给定一组系数的行的输出值。

第一个系数 in 总是截距,也称为偏差或 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 1.0 / (1.0 + exp(-yhat))

我们可以设计一个小数据集来测试我们的 **predict()**函数。

X1		X2		Y
2.7810836	2.550537003	0
1.465489372	2.362125076	0
3.396561688	4.400293529	0
1.38807019	1.850220317	0
3.06407232	3.005305973	0
7.627531214	2.759262235	1
5.332441248	2.088626775	1
6.922596716	1.77106367	1
8.675418651	-0.242068655	1
7.673756466	3.508563011	1

下面是使用不同颜色的数据集的图,以显示每个点的不同类。

Small Contrived Classification Dataset

小型受控分类数据集

我们还可以使用先前准备的系数来对该数据集做出预测。

综合这些,我们可以测试下面的 **predict()**函数。

# Make a prediction
from math import exp

# 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 1.0 / (1.0 + exp(-yhat))

# test predictions
dataset = [[2.7810836,2.550537003,0],
	[1.465489372,2.362125076,0],
	[3.396561688,4.400293529,0],
	[1.38807019,1.850220317,0],
	[3.06407232,3.005305973,0],
	[7.627531214,2.759262235,1],
	[5.332441248,2.088626775,1],
	[6.922596716,1.77106367,1],
	[8.675418651,-0.242068655,1],
	[7.673756466,3.508563011,1]]
coef = [-0.406605464, 0.852573316, -1.104746259]
for row in dataset:
	yhat = predict(row, coef)
	print("Expected=%.3f, Predicted=%.3f [%d]" % (row[-1], yhat, round(yhat)))

有两个输入值(X1 和 X2)和三个系数值(b0,b1 和 b2)。我们为这个问题建模的预测方程是:

y = 1.0 / (1.0 + e^(-(b0 + b1 * X1 + b2 * X2)))

或者,我们手动选择的具体系数值为:

y = 1.0 / (1.0 + e^(-(-0.406605464 + 0.852573316 * X1 + -1.104746259 * X2)))

运行此函数,我们可以得到与预期输出(y)值相当接近的预测,并在舍入时对类进行正确的预测。

Expected=0.000, Predicted=0.299 [0]
Expected=0.000, Predicted=0.146 [0]
Expected=0.000, Predicted=0.085 [0]
Expected=0.000, Predicted=0.220 [0]
Expected=0.000, Predicted=0.247 [0]
Expected=1.000, Predicted=0.955 [1]
Expected=1.000, Predicted=0.862 [1]
Expected=1.000, Predicted=0.972 [1]
Expected=1.000, Predicted=0.999 [1]
Expected=1.000, Predicted=0.905 [1]

现在我们准备实现随机梯度下降来优化我们的系数值。

2.估计系数

我们可以使用随机梯度下降来估计训练数据的系数值。

随机梯度下降需要两个参数:

  • 学习率:用于限制每次更新时每个系数的校正量。
  • 时期:更新系数时运行训练数据的次数。

这些以及训练数据将是该函数的参数。

我们需要在函数中执行 3 个循环:

  1. 循环每个时代。
  2. 循环遍历训练数据中的每一行以获得一个迭代。
  3. 循环遍历每个系数并将其更新为一个迭代中的一行。

如您所见,我们更新训练数据中每一行的每个系数,每个时期。

系数根据模型产生的误差进行更新。该误差被计算为预期输出值与利用候选系数进行的预测之间的差异。

有一个系数可以对每个输入属性进行加权,并且这些系数以一致的方式更新,例如:

b1(t+1) = b1(t) + learning_rate * (y(t) - yhat(t)) * yhat(t) * (1 - yhat(t)) * x1(t)

列表开头的特殊系数(也称为截距)以类似的方式更新,除非没有输入,因为它与特定输入值无关:

b0(t+1) = b0(t) + learning_rate * (y(t) - yhat(t)) * yhat(t) * (1 - yhat(t))

现在我们可以将所有这些放在一起。下面是一个名为 **coefficients_sgd()**的函数,它使用随机梯度下降计算训练数据集的系数值。

# Estimate logistic 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 = row[-1] - yhat
			sum_error += error**2
			coef[0] = coef[0] + l_rate * error * yhat * (1.0 - yhat)
			for i in range(len(row)-1):
				coef[i + 1] = coef[i + 1] + l_rate * error * yhat * (1.0 - yhat) * row[i]
		print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))
	return coef

你可以看到,此外,我们跟踪每个时期的平方误差(正值)的总和,这样我们就可以在每个外环中打印出一条好消息。

我们可以在上面的同样小的人为数据集上测试这个函数。

from math import exp

# 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 1.0 / (1.0 + exp(-yhat))

# Estimate logistic 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 = row[-1] - yhat
			sum_error += error**2
			coef[0] = coef[0] + l_rate * error * yhat * (1.0 - yhat)
			for i in range(len(row)-1):
				coef[i + 1] = coef[i + 1] + l_rate * error * yhat * (1.0 - yhat) * row[i]
		print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))
	return coef

# Calculate coefficients
dataset = [[2.7810836,2.550537003,0],
	[1.465489372,2.362125076,0],
	[3.396561688,4.400293529,0],
	[1.38807019,1.850220317,0],
	[3.06407232,3.005305973,0],
	[7.627531214,2.759262235,1],
	[5.332441248,2.088626775,1],
	[6.922596716,1.77106367,1],
	[8.675418651,-0.242068655,1],
	[7.673756466,3.508563011,1]]
l_rate = 0.3
n_epoch = 100
coef = coefficients_sgd(dataset, l_rate, n_epoch)
print(coef)

我们使用更大的学习率 0.3 并且将模型训练 100 个时期,或者将系数的 100 次曝光训练到整个训练数据集。

运行该示例在每个时期打印一条消息,该消息包含该时期的总和平方误差和最后一组系数。

>epoch=95, lrate=0.300, error=0.023
>epoch=96, lrate=0.300, error=0.023
>epoch=97, lrate=0.300, error=0.023
>epoch=98, lrate=0.300, error=0.023
>epoch=99, lrate=0.300, error=0.022
[-0.8596443546618897, 1.5223825112460005, -2.218700210565016]

你可以看到即使在最后一个时代,错误仍会继续下降。我们可以训练更长时间(更多迭代)或增加每个时期更新系数的量(更高的学习率)。

试验并看看你提出了什么。

现在,让我们将这个算法应用于真实数据集。

3.糖尿病预测

在本节中,我们将使用糖尿病数据集上的随机梯度下降来训练逻辑回归模型。

该示例假定数据集的 CSV 副本位于当前工作目录中,文件名为 pima-indians-diabetes.csv

首先加载数据集,将字符串值转换为数字,并将每列标准化为 0 到 1 范围内的值。这是通过辅助函数 **load_csv()**和 str_column_to_float()实现的。 加载并准备数据集和 **dataset_minmax()**和 **normalize_dataset()**来规范化它。

我们将使用 k-fold 交叉验证来估计学习模型在看不见的数据上的表现。这意味着我们将构建和评估 k 模型并将表现估计为平均模型表现。分类精度将用于评估每个模型。这些行为在 cross_validation_split(), **accuracy_metric()**和 **evaluate_algorithm()**辅助函数中提供。

我们将使用上面创建的 predict(), **coefficient_sgd()**函数和新的 **logistic_regression()**函数来训练模型。

以下是完整的示例。

# Logistic Regression on Diabetes Dataset
from random import seed
from random import randrange
from csv import reader
from math import exp

# 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 accuracy percentage
def accuracy_metric(actual, predicted):
	correct = 0
	for i in range(len(actual)):
		if actual[i] == predicted[i]:
			correct += 1
	return correct / float(len(actual)) * 100.0

# 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]
		accuracy = accuracy_metric(actual, predicted)
		scores.append(accuracy)
	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 1.0 / (1.0 + exp(-yhat))

# Estimate logistic 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 = row[-1] - yhat
			coef[0] = coef[0] + l_rate * error * yhat * (1.0 - yhat)
			for i in range(len(row)-1):
				coef[i + 1] = coef[i + 1] + l_rate * error * yhat * (1.0 - yhat) * row[i]
	return coef

# Linear Regression Algorithm With Stochastic Gradient Descent
def logistic_regression(train, test, l_rate, n_epoch):
	predictions = list()
	coef = coefficients_sgd(train, l_rate, n_epoch)
	for row in test:
		yhat = predict(row, coef)
		yhat = round(yhat)
		predictions.append(yhat)
	return(predictions)

# Test the logistic regression algorithm on the diabetes dataset
seed(1)
# load and prepare data
filename = 'pima-indians-diabetes.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.1
n_epoch = 100
scores = evaluate_algorithm(dataset, logistic_regression, n_folds, l_rate, n_epoch)
print('Scores: %s' % scores)
print('Mean Accuracy: %.3f%%' % (sum(scores)/float(len(scores))))

k 值为 5 用于交叉验证,每次迭代时评估每个折叠 768/5 = 153.6 或仅超过 150 个记录。通过一些实验选择了 0.1 和 100 个训练时期的学习率。

您可以尝试自己的配置,看看是否可以打败我的分数。

运行此示例将打印 5 个交叉验证折叠中每个折叠的分数,然后打印平均分类精度。

我们可以看到,如果我们使用零规则算法预测大多数类,精度约为 77%,高于基线值 65%。

Scores: [73.8562091503268, 78.43137254901961, 81.69934640522875, 75.81699346405229, 75.81699346405229]
Mean Accuracy: 77.124%

扩展

本节列出了本教程的一些扩展,您可能希望考虑这些扩展。

  • 调整示例。调整学习率,时期数甚至数据准备方法以获得数据集上的改进分数。
  • 批随机梯度下降。改变随机梯度下降算法以在每个时期累积更新,并且仅在时期结束时批量更新系数。
  • 其他分类问题。将该技术应用于 UCI 机器学习库中的其他二进制(2 类)分类问题。

你有没有探索过这些扩展? 请在下面的评论中告诉我。

评论

在本教程中,您了解了如何使用 Python 从零开始使用随机梯度下降来实现逻辑回归。

你学到了

  • 如何预测多变量分类问题。
  • 如何使用随机梯度下降来优化一组系数。
  • 如何将该技术应用于真实的分类预测性建模问题。

你有什么问题吗? 在下面的评论中提出您的问题,我会尽力回答。

如何用 Python 从零开始实现机器学习算法指标

原文: machinelearningmastery.com/implement-machine-learning-algorithm-performance-metrics-scratch-python/

在做出预测之后,你需要知道它们是否有用。

我们可以使用标准度量来总结一组预测实际上有多好。

了解一组预测有多好,可以让您估算出问题的给定机器学习模型的好坏,

在本教程中,您将了解如何在 Python 中从零开始实现四个标准预测评估指标。

阅读本教程后,您将了解:

  • 如何实现分类准确率。
  • 如何实现和解释混淆矩阵。
  • 如何实现回归的均值绝对误差。
  • 如何实现回归的均方根误差。

让我们开始吧。

  • 更新 Aug / 2018 :经过测试和更新,可与 Python 3.6 配合使用。

How To Implement Machine Learning Algorithm Performance Metrics From Scratch In Python

如何使用 Python 从零开始实现机器学习算法表现指标 照片由HernánPiñera,保留一些权利。

描述

在训练机器学习模型时,您必须估计一组预测的质量。

分类准确度和均方根误差等表现指标可以让您清楚地了解一组预测的好坏,以及生成它们的模型有多好。

这很重要,因为它可以让您区分并选择:

  • 用于训练相同机器学习模型的数据的不同变换。
  • 不同的机器学习模型训练相同的数据。
  • 针对相同数据训练的机器学习模型的不同配置。

因此,表现指标是从零开始实现机器学习算法的必要构建块。

教程

本教程分为 4 个部分:

  • 1.分类准确率。
  • 2.混淆矩阵。
  • 3.平均绝对误差。
  • 4.均方根误差。

这些步骤将为您提供处理机器学习算法预测评估所需的基础。

1.分类准确率

评估分类问题的一组预测的快速方法是使用准确率。

分类准确率是所有预测中正确预测数量的比率。

它通常以最差可能精度的 0%和最佳精度的 100%之间的百分比表示。

accuracy = correct predictions / total predictions * 100

我们可以在一个将预期结果和预测作为参数的函数中实现它。

下面是名为 **accuracy_metric()**的函数,它以百分比形式返回分类精度。请注意,我们使用“==”来比较实际值与预测值的相等性。这允许我们比较整数或字符串,我们在加载分类数据时可能选择使用的两种主要数据类型。

# Calculate accuracy percentage between two lists
def accuracy_metric(actual, predicted):
	correct = 0
	for i in range(len(actual)):
		if actual[i] == predicted[i]:
			correct += 1
	return correct / float(len(actual)) * 100.0

我们可以设计一个小数据集来测试这个功能。下面是一组 10 个实际和预测的整数值。预测集中有两个错误。

actual          predicted
0		0
0		1
0		0
0		0
0		0
1		1
1		0
1		1
1		1
1		1

下面是该数据集的完整示例,用于测试 **accuracy_metric()**函数。

# Calculate accuracy percentage between two lists
def accuracy_metric(actual, predicted):
	correct = 0
	for i in range(len(actual)):
		if actual[i] == predicted[i]:
			correct += 1
	return correct / float(len(actual)) * 100.0

# Test accuracy
actual = [0,0,0,0,0,1,1,1,1,1]
predicted = [0,1,0,0,0,1,0,1,1,1]
accuracy = accuracy_metric(actual, predicted)
print(accuracy)

运行此示例可以产生 80%或 8/10 的预期准确度。

80.0

当您具有少量类值(例如 2)时,准确度是一个很好的度量标准,也称为二分类问题。

当你有更多的类值时,准确率开始失去意义,你可能需要审查结果的不同观点,例如混淆矩阵。

2.混淆矩阵

混淆矩阵提供了与预期实际值相比所做的所有预测的摘要。

结果以矩阵形式呈现,每个细胞中有计数。水平汇总实际类值的计数,而垂直显示每个类值的预测计数。

一组完美的预测显示为矩阵左上角到右下角的对角线。

分类问题的混淆矩阵的值是,您可以清楚地看到哪些预测是错误的,以及所犯的错误类型。

让我们创建一个计算混淆矩阵的函数。

我们可以从定义函数开始,在给定实际类值列表和预测列表的情况下计算混淆矩阵。

该功能如下所示,名为 confusion_matrix()。它首先列出所有唯一类值,并将每个类值分配给混淆矩阵中的唯一整数或索引。

混淆矩阵始终是正方形,类值的数量表示所需的行数和列数。

这里,矩阵的第一个索引是实际值的行,第二个是预测值的列。在创建方形混淆矩阵并在每个单元格中初始化为零计数之后,循环所有预测并递增每个单元格中的计数。

该函数返回两个对象。第一个是唯一类值的集合,以便在绘制混淆矩阵时显示它们。第二个是混淆矩阵本身与每个单元格中的计数。

# calculate a confusion matrix
def confusion_matrix(actual, predicted):
	unique = set(actual)
	matrix = [list() for x in range(len(unique))]
	for i in range(len(unique)):
		matrix[i] = [0 for x in range(len(unique))]
	lookup = dict()
	for i, value in enumerate(unique):
		lookup[value] = i
	for i in range(len(actual)):
		x = lookup[actual[i]]
		y = lookup[predicted[i]]
		matrix[y][x] += 1
	return unique, matrix

让我们以一个例子来具体化。

下面是另一个人为的数据集,这次有 3 个错误。

actual     	predicted
0		0
0		1
0		1
0		0
0		0
1		1
1		0
1		1
1		1
1		1

我们可以计算并打印此数据集的混淆矩阵,如下所示:

# Example of Calculating a Confusion Matrix

# calculate a confusion matrix
def confusion_matrix(actual, predicted):
	unique = set(actual)
	matrix = [list() for x in range(len(unique))]
	for i in range(len(unique)):
		matrix[i] = [0 for x in range(len(unique))]
	lookup = dict()
	for i, value in enumerate(unique):
		lookup[value] = i
	for i in range(len(actual)):
		x = lookup[actual[i]]
		y = lookup[predicted[i]]
		matrix[y][x] += 1
	return unique, matrix

# Test confusion matrix with integers
actual = [0,0,0,0,0,1,1,1,1,1]
predicted = [0,1,1,0,0,1,0,1,1,1]
unique, matrix = confusion_matrix(actual, predicted)
print(unique)
print(matrix)

运行该示例将生成以下输出。该示例首先打印唯一值列表,然后打印混淆矩阵。

{0, 1}
[[3, 1], [2, 4]]

用这种方式解释结果很难。如果我们可以按行和列显示矩阵,将会有所帮助。

以下是正确显示矩阵的功能。

该函数名为 print_confusion_matrix()。它将列命名为 P 表示预测,行指定为 A 表示实际。每个列和行都以其对应的类值命名。

矩阵的布局期望每个类标签是单个字符或单个数字整数,并且计数也是单个数字整数。您可以将其扩展为处理大类标签或预测计数作为练习。

# pretty print a confusion matrix
def print_confusion_matrix(unique, matrix):
	print('(A)' + ' '.join(str(x) for x in unique))
	print('(P)---')
	for i, x in enumerate(unique):
		print("%s| %s" % (x, ' '.join(str(x) for x in matrix[i])))

我们可以拼凑所有功能并显示人类可读的混淆矩阵。

# Example of Calculating and Displaying a Pretty Confusion Matrix

# calculate a confusion matrix
def confusion_matrix(actual, predicted):
	unique = set(actual)
	matrix = [list() for x in range(len(unique))]
	for i in range(len(unique)):
		matrix[i] = [0 for x in range(len(unique))]
	lookup = dict()
	for i, value in enumerate(unique):
		lookup[value] = i
	for i in range(len(actual)):
		x = lookup[actual[i]]
		y = lookup[predicted[i]]
		matrix[y][x] += 1
	return unique, matrix

# pretty print a confusion matrix
def print_confusion_matrix(unique, matrix):
	print('(A)' + ' '.join(str(x) for x in unique))
	print('(P)---')
	for i, x in enumerate(unique):
		print("%s| %s" % (x, ' '.join(str(x) for x in matrix[i])))

# Test confusion matrix with integers
actual = [0,0,0,0,0,1,1,1,1,1]
predicted = [0,1,1,0,0,1,0,1,1,1]
unique, matrix = confusion_matrix(actual, predicted)
print_confusion_matrix(unique, matrix)

运行该示例将生成以下输出。我们可以在顶部和底部看到 0 和 1 的类标签。从左上角到右下角向下看矩阵的对角线,我们可以看到 3 个 0 的预测是正确的,4 个预测的 1 个是正确的。

查看其他单元格,我们可以看到 2 + 1 或 3 个预测错误。我们可以看到 2 个预测是 1,实际上实际上是 0 类值。我们可以看到 1 个预测是 0,实际上实际上是 1。

(A)0 1
(P)---
0| 3 1
1| 2 4

除了分类准确率之外,混淆矩阵始终是一个好主意,以帮助解释预测。

3.平均绝对误差

回归问题是预测实际价值的问题。

要考虑的一个简单指标是预测值与预期值相比的误差。

平均绝对误差或简称 MAE 是一个很好的第一个误差度量标准。

它被计算为绝对误差值的平均值,其中“绝对值”表示“为正”,以便它们可以加在一起。

MAE = sum( abs(predicted_i - actual_i) ) / total predictions

下面是一个名为 **mae_metric()**的函数,它实现了这个指标。如上所述,它需要一个实际结果值列表和一个预测列表。我们使用内置的 abs() Python 函数来计算求和的绝对误差值。

def mae_metric(actual, predicted):
	sum_error = 0.0
	for i in range(len(actual)):
		sum_error += abs(predicted[i] - actual[i])

我们可以设计一个小的回归数据集来测试这个函数。

actual 		predicted
0.1		0.11
0.2		0.19
0.3		0.29
0.4		0.41
0.5		0.5

只有一个预测(0.5)是正确的,而所有其他预测都是错误的 0.01。因此,我们预计这些预测的平均绝对误差(或平均正误差)略小于 0.01。

下面是一个用设想的数据集测试 **mae_metric()**函数的例子。

# Calculate mean absolute error
def mae_metric(actual, predicted):
	sum_error = 0.0
	for i in range(len(actual)):
		sum_error += abs(predicted[i] - actual[i])
	return sum_error / float(len(actual))

# Test RMSE
actual = [0.1, 0.2, 0.3, 0.4, 0.5]
predicted = [0.11, 0.19, 0.29, 0.41, 0.5]
mae = mae_metric(actual, predicted)
print(mae)

运行此示例将打印下面的输出。我们可以看到,正如预期的那样,MAE 约为 0.008,小值略低于 0.01。

0.007999999999999993

4.均方根误差

在一组回归预测中计算误差的另一种流行方法是使用均方根误差。

缩写为 RMSE,该度量有时称为均方误差或 MSE,从计算和名称中删除根部分。

RMSE 计算为实际结果和预测之间的平方差异的平均值的平方根。

平方每个错误会强制值为正,并且均方误差的平方根将误差度量返回到原始单位以进行比较。

RMSE = sqrt( sum( (predicted_i - actual_i)² ) / total predictions)

下面是一个名为 **rmse_metric()**的函数的实现。它使用数学模块中的 **sqrt()函数,并使用运算符将误差提高到 2 次幂。

# 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)

我们可以在用于测试上面的平均绝对误差计算的相同数据集上测试该度量。

以下是一个完整的例子。同样,我们希望误差值通常接近 0.01。

from math import sqrt

# 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)

# Test RMSE
actual = [0.1, 0.2, 0.3, 0.4, 0.5]
predicted = [0.11, 0.19, 0.29, 0.41, 0.5]
rmse = rmse_metric(actual, predicted)
print(rmse)

运行该示例,我们看到下面的结果。结果略高于 0.0089。

RMSE 值总是略高于 MSE 值,随着预测误差的增加,MSE 值变得更加明显。这是使用 RMSE 而不是 MSE 的好处,因为它会以较差的分数惩罚较大的错误。

0.00894427190999915

扩展

您只看到了最广泛使用的表现指标的一小部分样本。

您可能还需要许多其他表现指标。

下面列出了 5 个额外的表现指标,您可能希望实现这些指标以扩展本教程

  • 分类精度。
  • 回想一下分类。
  • F1 进行分类。
  • ROC 曲线下的面积或分类的 AUC。
  • 拟合优度或 R ^ 2(R 平方)用于回归。

您是否实现了这些扩展? 在下面的评论中分享您的经验。

评论

在本教程中,您了解了如何在 Python 中从零开始实现算法预测表现指标。

具体来说,你学到了:

  • 如何实现和解释分类准确率。
  • 如何实现和解释分类问题的混淆矩阵。
  • 如何实现和解释回归的平均绝对误差。
  • 如何实现和解释回归的均方根误差。

你有什么问题吗? 在评论中提出您的问题,我会尽力回答。

如何在 Python 中从零开始实现感知机算法

原文: machinelearningmastery.com/implement-perceptron-algorithm-scratch-python/

Perceptron 算法是最简单的人工神经网络。

它是单个神经元的模型,可用于两类分类问题,并为以后开发更大的网络提供基础。

在本教程中,您将了解如何使用 Python 从零开始实现 Perceptron 算法。

完成本教程后,您将了解:

  • 如何训练 Perceptron 的网络权重。
  • 如何使用 Perceptron 做出预测。
  • 如何针对真实世界的分类问题实现 Perceptron 算法。

让我们开始吧。

  • 2017 年 1 月更新:将 cross_validation_split()中的 fold_size 计算更改为始终为整数。修复了 Python 3 的问题。
  • 更新 Aug / 2018 :经过测试和更新,可与 Python 3.6 配合使用。

How To Implement The Perceptron Algorithm From Scratch In Python

如何在 Python 中从零开始实现感知机算法 照片由 Les Haines ,保留一些权利。

描述

本节简要介绍 Perceptron 算法和我们稍后将应用它的 Sonar 数据集。

感知机算法

Perceptron 的灵感来自于称为神经元的单个神经细胞的信息处理。

神经元通过其树突接受输入信号,树突将电信号传递到细胞体。

以类似的方式,Perceptron 从训练数据的示例接收输入信号,我们对这些训练数据进行加权并组合成称为激活的线性方程。

activation = sum(weight_i * x_i) + bias

然后使用诸如步进传递函数的传递函数将激活变换为输出值或预测。

prediction = 1.0 if activation >= 0.0 else 0.0

通过这种方式,Perceptron 是一个分类算法,用于解决两个类(0 和 1)的问题,其中线性方程(如超平面)可以用来分离这两个类。

它与线性回归和逻辑回归密切相关,以类似的方式做出预测(例如输入的加权和)。

必须使用随机梯度下降从训练数据中估计 Perceptron 算法的权重。

随机梯度下降

梯度下降是通过遵循成本函数的梯度来最小化函数的过程。

这包括了解成本的形式以及衍生物,以便从给定的点知道梯度并且可以在该方向上移动,例如,向下走向最小值。

在机器学习中,我们可以使用一种技术来评估和更新称为随机梯度下降的每次迭代的权重,以最小化模型对我们的训练数据的误差。

此优化算法的工作方式是每个训练实例一次显示给模型一个。该模型对训练实例做出预测,计算误差并更新模型以减少下一次预测的误差。

此过程可用于在模型中查找权重集,从而导致训练数据上模型的最小误差。

对于 Perceptron 算法,每次迭代使用以下等式更新权重( w ):

w = w + learning_rate * (expected - predicted) * x

w 的权重被优化, learning_rate 是你必须配置的学习率(例如 0.01),**(预期 - 预测)**是预测误差归因于重量和 x 的训练数据的模型是输入值。

声纳数据集

我们将在本教程中使用的数据集是 Sonar 数据集。

这是一个描述声纳啁啾返回弹跳不同服务的数据集。 60 个输入变量是不同角度的回报强度。这是一个二分类问题,需要一个模型来区分岩石和金属圆柱。

这是一个众所周知的数据集。所有变量都是连续的,通常在 0 到 1 的范围内。因此我们不必对输入数据进行标准化,这通常是 Perceptron 算法的一个好习惯。输出变量是我的字符串“M”和摇滚的“R”,需要将其转换为整数 1 和 0。

通过预测数据集(M 或矿)中具有最多观测值的类,零规则算法可以实现 53%的准确度。

您可以在 UCI 机器学习库中了解有关此数据集的更多信息。您可以免费下载数据集并将其放在工作目录中,文件名为 sonar.all-data.csv

教程

本教程分为 3 个部分:

  1. 做出预测。
  2. 训练网络权重。
  3. 声纳数据集建模。

这些步骤将为您提供实现 Perceptron 算法并将其应用于您自己的分类预测性建模问题的基础。

1.做出预测

第一步是开发一个可以做出预测的功能。

这在随机梯度下降中的候选权重值的评估中以及在模型完成之后并且我们希望开始对测试数据或新数据做出预测时都需要。

下面是一个名为 **predict()**的函数,它预测给定一组权重的行的输出值。

第一个权重始终是偏差,因为它是独立的,不负责特定的输入值。

# Make a prediction with weights
def predict(row, weights):
	activation = weights[0]
	for i in range(len(row)-1):
		activation += weights[i + 1] * row[i]
	return 1.0 if activation >= 0.0 else 0.0

我们可以设计一个小数据集来测试我们的预测函数。

X1			X2			Y
2.7810836		2.550537003		0
1.465489372		2.362125076		0
3.396561688		4.400293529		0
1.38807019		1.850220317		0
3.06407232		3.005305973		0
7.627531214		2.759262235		1
5.332441248		2.088626775		1
6.922596716		1.77106367		1
8.675418651		-0.242068655		1
7.673756466		3.508563011		1

我们还可以使用先前准备的权重来对此数据集做出预测。

综合这些,我们可以测试下面的 **predict()**函数。

# Make a prediction with weights
def predict(row, weights):
	activation = weights[0]
	for i in range(len(row)-1):
		activation += weights[i + 1] * row[i]
	return 1.0 if activation >= 0.0 else 0.0

# test predictions
dataset = [[2.7810836,2.550537003,0],
	[1.465489372,2.362125076,0],
	[3.396561688,4.400293529,0],
	[1.38807019,1.850220317,0],
	[3.06407232,3.005305973,0],
	[7.627531214,2.759262235,1],
	[5.332441248,2.088626775,1],
	[6.922596716,1.77106367,1],
	[8.675418651,-0.242068655,1],
	[7.673756466,3.508563011,1]]
weights = [-0.1, 0.20653640140000007, -0.23418117710000003]
for row in dataset:
	prediction = predict(row, weights)
	print("Expected=%d, Predicted=%d" % (row[-1], prediction))

有两个输入值( X1X2 )和三个权重值(偏差w1w2 )。我们为此问题建模的激活方程是:

activation = (w1 * X1) + (w2 * X2) + bias

或者,我们手动选择具体的重量值:

activation = (0.206 * X1) + (-0.234 * X2) + -0.1

运行此函数,我们得到与预期输出( y )值匹配的预测。

Expected=0, Predicted=0
Expected=0, Predicted=0
Expected=0, Predicted=0
Expected=0, Predicted=0
Expected=0, Predicted=0
Expected=1, Predicted=1
Expected=1, Predicted=1
Expected=1, Predicted=1
Expected=1, Predicted=1
Expected=1, Predicted=1

现在我们准备实现随机梯度下降来优化我们的重量值。

2.训练网络权重

我们可以使用随机梯度下降来估计训练数据的权重值。

随机梯度下降需要两个参数:

  • 学习率:用于限制每次更新时每个重量的校正量。
  • Epochs :更新体重时运行训练数据的次数。

这些以及训练数据将是该函数的参数。

我们需要在函数中执行 3 个循环:

  1. 循环每个时代。
  2. 循环遍历训练数据中的每一行以获得一个迭代。
  3. 循环遍历每个权重并将其更新为一个迭代中的一行。

如您所见,我们更新训练数据中每一行的每个权重,每个时期。

权重根据模型产生的错误进行更新。该误差被计算为预期输出值与用候选权重进行的预测之间的差异。

每个输入属性都有一个权重,这些权重以一致的方式更新,例如:

w(t+1)= w(t) + learning_rate * (expected(t) - predicted(t)) * x(t)

偏差以类似的方式更新,除非没有输入,因为它与特定输入值无关:

bias(t+1) = bias(t) + learning_rate * (expected(t) - predicted(t))

现在我们可以将所有这些放在一起。下面是一个名为 **train_weights()**的函数,它使用随机梯度下降计算训练数据集的权重值。

# Estimate Perceptron weights using stochastic gradient descent
def train_weights(train, l_rate, n_epoch):
	weights = [0.0 for i in range(len(train[0]))]
	for epoch in range(n_epoch):
		sum_error = 0.0
		for row in train:
			prediction = predict(row, weights)
			error = row[-1] - prediction
			sum_error += error**2
			weights[0] = weights[0] + l_rate * error
			for i in range(len(row)-1):
				weights[i + 1] = weights[i + 1] + l_rate * error * row[i]
		print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))
	return weights

您可以看到我们还跟踪每个时期的平方误差(正值)的总和,以便我们可以在每个外部循环中打印出一条好消息。

我们可以在上面的同样小的人为数据集上测试这个函数。

# Make a prediction with weights
def predict(row, weights):
	activation = weights[0]
	for i in range(len(row)-1):
		activation += weights[i + 1] * row[i]
	return 1.0 if activation >= 0.0 else 0.0

# Estimate Perceptron weights using stochastic gradient descent
def train_weights(train, l_rate, n_epoch):
	weights = [0.0 for i in range(len(train[0]))]
	for epoch in range(n_epoch):
		sum_error = 0.0
		for row in train:
			prediction = predict(row, weights)
			error = row[-1] - prediction
			sum_error += error**2
			weights[0] = weights[0] + l_rate * error
			for i in range(len(row)-1):
				weights[i + 1] = weights[i + 1] + l_rate * error * row[i]
		print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))
	return weights

# Calculate weights
dataset = [[2.7810836,2.550537003,0],
	[1.465489372,2.362125076,0],
	[3.396561688,4.400293529,0],
	[1.38807019,1.850220317,0],
	[3.06407232,3.005305973,0],
	[7.627531214,2.759262235,1],
	[5.332441248,2.088626775,1],
	[6.922596716,1.77106367,1],
	[8.675418651,-0.242068655,1],
	[7.673756466,3.508563011,1]]
l_rate = 0.1
n_epoch = 5
weights = train_weights(dataset, l_rate, n_epoch)
print(weights)

我们使用 0.1 的学习率并且仅将模型训练 5 个时期,或者将权重的 5 次暴露训练到整个训练数据集。

运行该示例在每个迭代打印一条消息,其中包含该迭代和最终权重集的总和平方误差。

>epoch=0, lrate=0.100, error=2.000
>epoch=1, lrate=0.100, error=1.000
>epoch=2, lrate=0.100, error=0.000
>epoch=3, lrate=0.100, error=0.000
>epoch=4, lrate=0.100, error=0.000
[-0.1, 0.20653640140000007, -0.23418117710000003]

您可以通过算法快速了解问题是如何学习的。

现在,让我们将这个算法应用于真实数据集。

3.对声纳数据集进行建模

在本节中,我们将使用 Sonar 数据集上的随机梯度下降来训练 Perceptron 模型。

该示例假定数据集的 CSV 副本位于当前工作目录中,文件名为 sonar.all-data.csv

首先加载数据集,将字符串值转换为数字,并将输出列从字符串转换为 0 到 1 的整数值。这可以通过辅助函数 load_csv(), **str_column_to_float( )**和 **str_column_to_int()**加载和准备数据集。

我们将使用 k-fold 交叉验证来估计学习模型在看不见的数据上的表现。这意味着我们将构建和评估 k 模型并将表现估计为平均模型误差。分类精度将用于评估每个模型。这些行为在 cross_validation_split(), **accuracy_metric()**和 **evaluate_algorithm()**辅助函数中提供。

我们将使用上面创建的 predict()和 **train_weights()**函数来训练模型和新的 **perceptron()**函数将它们绑定在一起。

以下是完整的示例。

# Perceptron Algorithm on the Sonar Dataset
from random import seed
from random import randrange
from csv import reader

# 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())

# Convert string column to integer
def str_column_to_int(dataset, column):
	class_values = [row[column] for row in dataset]
	unique = set(class_values)
	lookup = dict()
	for i, value in enumerate(unique):
		lookup[value] = i
	for row in dataset:
		row[column] = lookup[row[column]]
	return lookup

# 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 accuracy percentage
def accuracy_metric(actual, predicted):
	correct = 0
	for i in range(len(actual)):
		if actual[i] == predicted[i]:
			correct += 1
	return correct / float(len(actual)) * 100.0

# 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]
		accuracy = accuracy_metric(actual, predicted)
		scores.append(accuracy)
	return scores

# Make a prediction with weights
def predict(row, weights):
	activation = weights[0]
	for i in range(len(row)-1):
		activation += weights[i + 1] * row[i]
	return 1.0 if activation >= 0.0 else 0.0

# Estimate Perceptron weights using stochastic gradient descent
def train_weights(train, l_rate, n_epoch):
	weights = [0.0 for i in range(len(train[0]))]
	for epoch in range(n_epoch):
		for row in train:
			prediction = predict(row, weights)
			error = row[-1] - prediction
			weights[0] = weights[0] + l_rate * error
			for i in range(len(row)-1):
				weights[i + 1] = weights[i + 1] + l_rate * error * row[i]
	return weights

# Perceptron Algorithm With Stochastic Gradient Descent
def perceptron(train, test, l_rate, n_epoch):
	predictions = list()
	weights = train_weights(train, l_rate, n_epoch)
	for row in test:
		prediction = predict(row, weights)
		predictions.append(prediction)
	return(predictions)

# Test the Perceptron algorithm on the sonar dataset
seed(1)
# load and prepare data
filename = 'sonar.all-data.csv'
dataset = load_csv(filename)
for i in range(len(dataset[0])-1):
	str_column_to_float(dataset, i)
# convert string class to integers
str_column_to_int(dataset, len(dataset[0])-1)
# evaluate algorithm
n_folds = 3
l_rate = 0.01
n_epoch = 500
scores = evaluate_algorithm(dataset, perceptron, n_folds, l_rate, n_epoch)
print('Scores: %s' % scores)
print('Mean Accuracy: %.3f%%' % (sum(scores)/float(len(scores))))

k 值为 3 用于交叉验证,每次迭代时评估每个折叠 208/3 = 69.3 或略低于 70 的记录。通过一些实验选择了 0.1 和 500 个训练时期的学习率。

您可以尝试自己的配置,看看是否可以打败我的分数。

运行此示例将打印 3 个交叉验证折叠中每个折叠的分数,然后打印平均分类精度。

我们可以看到,如果我们仅使用零规则算法预测大多数类,则准确度约为 72%,高于仅超过 50%的基线值。

Scores: [76.81159420289855, 69.56521739130434, 72.46376811594203]
Mean Accuracy: 72.947%

扩展

本节列出了您可能希望考虑探索的本教程的扩展。

  • 调整示例。调整学习率,时期数甚至数据准备方法以获得数据集上的改进分数。
  • 批随机梯度下降。更改随机梯度下降算法以在每个时期累积更新,并且仅在时期结束时批量更新权重。
  • 其他回归问题。将该技术应用于 UCI 机器学习库中的其他分类问题。

你有没有探索过这些扩展? 请在下面的评论中告诉我。

评论

在本教程中,您了解了如何使用 Python 从零开始使用随机梯度下降来实现 Perceptron 算法。

你学到了

  • 如何预测二分类问题。
  • 如何使用随机梯度下降来优化一组权重。
  • 如何将该技术应用于真实的分类预测性建模问题。

你有什么问题吗? 在下面的评论中提出您的问题,我会尽力回答。

如何在 Python 中从零开始实现随机森林

原文: machinelearningmastery.com/implement-random-forest-scratch-python/

决策树可能遭受高度变化,这使得它们的结果对于所使用的特定训练数据而言是脆弱的。

从训练数据样本中构建多个模型(称为装袋)可以减少这种差异,但树木具有高度相关性。

随机森林是套袋的扩展,除了根据训练数据的多个样本构建树木之外,它还限制了可用于构建树木的特征,迫使树木变得不同。反过来,这可以提升表现。

在本教程中,您将了解如何在 Python 中从零开始实现随机森林算法。

完成本教程后,您将了解:

  • 袋装决策树与随机森林算法的区别。
  • 如何构造具有更多方差的袋装决策树。
  • 如何将随机森林算法应用于预测性建模问题。

让我们开始吧。

  • 2017 年 1 月更新:将 cross_validation_split()中的 fold_size 计算更改为始终为整数。修复了 Python 3 的问题。
  • 2017 年 2 月更新:修复了 build_tree 中的错误。
  • 2017 年 8 月更新:修正了基尼计算中的一个错误,根据组大小添加了组基尼评分缺失的权重(感谢迈克尔!)。
  • 更新 Aug / 2018 :经过测试和更新,可与 Python 3.6 配合使用。

How to Implement Random Forest From Scratch in Python

如何在 Python 中从零开始实现随机森林 照片由 InspireFate Photography ,保留一些权利。

描述

本节简要介绍随机森林算法和本教程中使用的 Sonar 数据集。

随机森林算法

决策树涉及在每个步骤中从数据集中贪婪地选择最佳分割点。

如果没有修剪,该算法使决策树易受高方差影响。通过使用训练数据集的不同样本(问题的不同视图)创建多个树并组合它们的预测,可以利用和减少这种高方差。这种方法简称为 bootstrap 聚合或装袋。

套袋的限制是使用相同的贪婪算法来创建每个树,这意味着可能在每个树中选择相同或非常相似的分裂点,使得不同的树非常相似(树将相关)。反过来,这使他们的预测相似,减轻了最初寻求的差异。

我们可以通过限制贪婪算法在创建树时在每个分割点处可以评估的特征(行)来强制决策树不同。这称为随机森林算法。

与装袋一样,采集训练数据集的多个样本,并对每个样本进行不同的训练。不同之处在于,在每个点处对数据进行拆分并添加到树中,只能考虑固定的属性子集。

对于分类问题,我们将在本教程中看到的问题类型,要考虑拆分的属性数量限制为输入要素数量的平方根。

num_features_for_split = sqrt(total_input_features)

这一个小变化的结果是彼此更加不同的树(不相关)导致更多样化的预测和组合预测,其通常具有单个树或单独装袋的更好表现。

声纳数据集

我们将在本教程中使用的数据集是 Sonar 数据集。

这是一个描述声纳啁啾返回从不同表面反弹的数据集。 60 个输入变量是不同角度的回报强度。这是一个二分类问题,需要一个模型来区分岩石和金属圆柱。共有 208 个观测结果。

这是一个众所周知的数据集。所有变量都是连续的,通常在 0 到 1 的范围内。输出变量是我的字符串“M”和摇滚的“R”,需要将其转换为整数 1 和 0。

通过预测数据集(M 或矿)中具有最多观测值的类,零规则算法可以实现 53%的准确度。

您可以在 UCI 机器学习库中了解有关此数据集的更多信息。

免费下载数据集并将其放在工作目录中,文件名为 sonar.all-data.csv

教程

本教程分为两个步骤。

  1. 计算拆分。
  2. 声纳数据集案例研究。

这些步骤提供了实现和应用随机森林算法到您自己的预测性建模问题所需的基础。

1.计算拆分

在决策树中,通过查找导致成本最低的属性和该属性的值来选择拆分点。

对于分类问题,此成本函数通常是 Gini 索引,用于计算分割点创建的数据组的纯度。基尼系数为 0 是完美纯度,在两类分类问题的情况下,类值完全分为两组。

在决策树中查找最佳分割点涉及评估每个输入变量的训练数据集中每个值的成本。

对于装袋和随机森林,此过程在训练数据集的样本上执行,由替换完成。对替换进行采样意味着可以选择相同的行并将其多次添加到样本中。

我们可以更新随机森林的这个程序。如果具有最低成本的拆分,我们可以创建要考虑的输入属性的样本,而不是在搜索中枚举输入属性的所有值。

此输入属性样本可以随机选择而无需替换,这意味着在查找成本最低的分割点时,每个输入属性只需要考虑一次。

下面是一个函数名 get_split(),它实现了这个过程。它将数据集和固定数量的输入要素作为输入参数进行评估,其中数据集可以是实际训练数据集的样本。

辅助函数 **test_split()**用于通过候选分割点分割数据集, **gini_index()**用于评估创建的行组的给定分割的成本。

我们可以看到通过随机选择特征索引并将它们添加到列表(称为特征)来创建特征列表,然后枚举该特征列表并将训练数据集中的特定值评估为分割点。

# Select the best split point for a dataset
def get_split(dataset, n_features):
	class_values = list(set(row[-1] for row in dataset))
	b_index, b_value, b_score, b_groups = 999, 999, 999, None
	features = list()
	while len(features) < n_features:
		index = randrange(len(dataset[0])-1)
		if index not in features:
			features.append(index)
	for index in features:
		for row in dataset:
			groups = test_split(index, row[index], dataset)
			gini = gini_index(groups, class_values)
			if gini < b_score:
				b_index, b_value, b_score, b_groups = index, row[index], gini, groups
	return {'index':b_index, 'value':b_value, 'groups':b_groups}

现在我们知道如何修改决策树算法以与随机森林算法一起使用,我们可以将其与包装的实现结合起来并将其应用于真实世界的数据集。

2.声纳数据集案例研究

在本节中,我们将随机森林算法应用于 Sonar 数据集。

该示例假定数据集的 CSV 副本位于当前工作目录中,文件名为 sonar.all-data.csv

首先加载数据集,将字符串值转换为数字,然后将输出列从字符串转换为 0 和 1 的整数值。这可以通过辅助函数 load_csv(), **str_column_to_float( )**和 **str_column_to_int()**加载和准备数据集。

我们将使用 k-fold 交叉验证来估计学习模型在看不见的数据上的表现。这意味着我们将构建和评估 k 模型并将表现估计为平均模型误差。分类精度将用于评估每个模型。这些行为在 cross_validation_split(), **accuracy_metric()**和 **evaluate_algorithm()**辅助函数中提供。

我们还将使用适用于装袋的分类和回归树(CART)算法的实现,包括辅助函数 **test_split()**将数据集分成组, **gini_index()**来评估分裂点,我们在上一步中讨论的修改后的 **get_split()**函数, to_terminal(), **split()**和 **build_tree()[HTG11 用于创建单个决策树,**预测()**使用决策树做出预测, **subsample()**制作训练数据集的子样本和 **bagging_predict( )使用决策树列表做出预测。

开发了一个新的函数名 random_forest(),它首先从训练数据集的子样本创建决策树列表,然后使用它们做出预测。

如上所述,随机森林和袋装决策树之间的关键区别是树木创建方式的一个小变化,这里是 **get_split()**函数。

下面列出了完整的示例。

# Random Forest Algorithm on Sonar Dataset
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())

# Convert string column to integer
def str_column_to_int(dataset, column):
	class_values = [row[column] for row in dataset]
	unique = set(class_values)
	lookup = dict()
	for i, value in enumerate(unique):
		lookup[value] = i
	for row in dataset:
		row[column] = lookup[row[column]]
	return lookup

# 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 accuracy percentage
def accuracy_metric(actual, predicted):
	correct = 0
	for i in range(len(actual)):
		if actual[i] == predicted[i]:
			correct += 1
	return correct / float(len(actual)) * 100.0

# 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]
		accuracy = accuracy_metric(actual, predicted)
		scores.append(accuracy)
	return scores

# Split a dataset based on an attribute and an attribute value
def test_split(index, value, dataset):
	left, right = list(), list()
	for row in dataset:
		if row[index] < value:
			left.append(row)
		else:
			right.append(row)
	return left, right

# Calculate the Gini index for a split dataset
def gini_index(groups, classes):
	# count all samples at split point
	n_instances = float(sum([len(group) for group in groups]))
	# sum weighted Gini index for each group
	gini = 0.0
	for group in groups:
		size = float(len(group))
		# avoid divide by zero
		if size == 0:
			continue
		score = 0.0
		# score the group based on the score for each class
		for class_val in classes:
			p = [row[-1] for row in group].count(class_val) / size
			score += p * p
		# weight the group score by its relative size
		gini += (1.0 - score) * (size / n_instances)
	return gini

# Select the best split point for a dataset
def get_split(dataset, n_features):
	class_values = list(set(row[-1] for row in dataset))
	b_index, b_value, b_score, b_groups = 999, 999, 999, None
	features = list()
	while len(features) < n_features:
		index = randrange(len(dataset[0])-1)
		if index not in features:
			features.append(index)
	for index in features:
		for row in dataset:
			groups = test_split(index, row[index], dataset)
			gini = gini_index(groups, class_values)
			if gini < b_score:
				b_index, b_value, b_score, b_groups = index, row[index], gini, groups
	return {'index':b_index, 'value':b_value, 'groups':b_groups}

# Create a terminal node value
def to_terminal(group):
	outcomes = [row[-1] for row in group]
	return max(set(outcomes), key=outcomes.count)

# Create child splits for a node or make terminal
def split(node, max_depth, min_size, n_features, depth):
	left, right = node['groups']
	del(node['groups'])
	# check for a no split
	if not left or not right:
		node['left'] = node['right'] = to_terminal(left + right)
		return
	# check for max depth
	if depth >= max_depth:
		node['left'], node['right'] = to_terminal(left), to_terminal(right)
		return
	# process left child
	if len(left) <= min_size:
		node['left'] = to_terminal(left)
	else:
		node['left'] = get_split(left, n_features)
		split(node['left'], max_depth, min_size, n_features, depth+1)
	# process right child
	if len(right) <= min_size:
		node['right'] = to_terminal(right)
	else:
		node['right'] = get_split(right, n_features)
		split(node['right'], max_depth, min_size, n_features, depth+1)

# Build a decision tree
def build_tree(train, max_depth, min_size, n_features):
	root = get_split(train, n_features)
	split(root, max_depth, min_size, n_features, 1)
	return root

# Make a prediction with a decision tree
def predict(node, row):
	if row[node['index']] < node['value']:
		if isinstance(node['left'], dict):
			return predict(node['left'], row)
		else:
			return node['left']
	else:
		if isinstance(node['right'], dict):
			return predict(node['right'], row)
		else:
			return node['right']

# Create a random subsample from the dataset with replacement
def subsample(dataset, ratio):
	sample = list()
	n_sample = round(len(dataset) * ratio)
	while len(sample) < n_sample:
		index = randrange(len(dataset))
		sample.append(dataset[index])
	return sample

# Make a prediction with a list of bagged trees
def bagging_predict(trees, row):
	predictions = [predict(tree, row) for tree in trees]
	return max(set(predictions), key=predictions.count)

# Random Forest Algorithm
def random_forest(train, test, max_depth, min_size, sample_size, n_trees, n_features):
	trees = list()
	for i in range(n_trees):
		sample = subsample(train, sample_size)
		tree = build_tree(sample, max_depth, min_size, n_features)
		trees.append(tree)
	predictions = [bagging_predict(trees, row) for row in test]
	return(predictions)

# Test the random forest algorithm
seed(2)
# load and prepare data
filename = 'sonar.all-data.csv'
dataset = load_csv(filename)
# convert string attributes to integers
for i in range(0, len(dataset[0])-1):
	str_column_to_float(dataset, i)
# convert class column to integers
str_column_to_int(dataset, len(dataset[0])-1)
# evaluate algorithm
n_folds = 5
max_depth = 10
min_size = 1
sample_size = 1.0
n_features = int(sqrt(len(dataset[0])-1))
for n_trees in [1, 5, 10]:
	scores = evaluate_algorithm(dataset, random_forest, n_folds, max_depth, min_size, sample_size, n_trees, n_features)
	print('Trees: %d' % n_trees)
	print('Scores: %s' % scores)
	print('Mean Accuracy: %.3f%%' % (sum(scores)/float(len(scores))))

k 值为 5 用于交叉验证,每次迭代时评估每个折叠 208/5 = 41.6 或仅超过 40 个记录。

构建深度树,最大深度为 10,每个节点为 1 的最小训练行数。训练数据集的样本创建的大小与原始数据集相同,这是随机森林算法的默认期望值。

在每个分割点处考虑的要素数设置为 sqrt(num_features)或 sqrt(60)= 7.74 四舍五入为 7 个要素。

评估了一组 3 种不同数量的树木进行比较,显示随着更多树木的增加,技能越来越高。

运行该示例打印每个折叠的分数和每个配置的平均分数。

Trees: 1
Scores: [56.09756097560976, 63.41463414634146, 60.97560975609756, 58.536585365853654, 73.17073170731707]
Mean Accuracy: 62.439%

Trees: 5
Scores: [70.73170731707317, 58.536585365853654, 85.36585365853658, 75.60975609756098, 63.41463414634146]
Mean Accuracy: 70.732%

Trees: 10
Scores: [75.60975609756098, 80.48780487804879, 92.6829268292683, 73.17073170731707, 70.73170731707317]
Mean Accuracy: 78.537%

扩展

本节列出了您可能有兴趣探索的本教程的扩展。

  • 算法调整。发现本教程中使用的配置有一些试验和错误,但没有进行优化。尝试使用更多树,不同数量的功能甚至不同的树配置来提高表现。
  • 更多问题。将该技术应用于其他分类问题,甚至使用新的成本函数和用于组合树木预测的新方法使其适应回归。

你有没有试过这些扩展? 在下面的评论中分享您的经验。

评论

在本教程中,您了解了如何从零开始实现随机森林算法。

具体来说,你学到了:

  • 随机森林与袋装决策树的区别。
  • 如何更新决策树的创建以适应随机森林过程。
  • 如何将随机森林算法应用于现实世界的预测性建模问题。

你有什么问题吗? 在下面的评论中提出您的问题,我会尽力回答。

如何在 Python 中从零开始实现重采样方法

原文: machinelearningmastery.com/implement-resampling-methods-scratch-python/

预测性建模的目标是创建能够对新数据进行良好预测的模型。

我们在训练时无法访问这些新数据,因此我们必须使用统计方法来估计模型在新数据上的表现。

这类方法称为重采样方法,因为它们重采样您可用的训练数据。

在本教程中,您将了解如何在 Python 中从零开始实现重采样方法。

完成本教程后,您将了解:

  • 如何实现训练并测试您的数据分割。
  • 如何实现数据的 k 折交叉验证拆分。

让我们开始吧。

  • 2017 年 1 月更新:将 cross_validation_split()中的 fold_size 计算更改为始终为整数。修复了 Python 3 的问题。
  • 更新 May / 2018 :修正了错误的 LOOCV。
  • 更新 Aug / 2018 :经过测试和更新,可与 Python 3.6 配合使用。

How to Implement Resampling Methods From Scratch In Python

如何在 Python 中实现重新取样方法 照片由 Andrew Lynch ,保留一些权利。

描述

重采样方法的目标是充分利用您的训练数据,以便准确地估计模型在新的未见数据上的表现。

然后可以使用准确的表现估计来帮助您选择要使用的模型参数集或要选择的模型。

选择模型后,您可以在整个训练数据集上训练最终模型,并开始使用它来做出预测。

您可以使用两种常见的重采样方法:

  • 训练和测试分割您的数据。
  • k 折交叉验证。

在本教程中,我们将介绍使用 each 和 when 使用一种方法而不是另一种方法。

教程

本教程分为 3 个部分:

  1. 训练和测试分裂。
  2. k-fold 交叉验证拆分。
  3. 如何选择重采样方法。

这些步骤将为您处理重采样数据集以估计新数据的算法表现提供所需的基础。

1.训练和测试分裂

训练和测试分割是最简单的重采样方法。

因此,它是最广泛使用的。

训练和测试拆分涉及将数据集分成两部分:

  • 训练数据集。
  • 测试数据集。

训练数据集由机器学习算法用于训练模型。保留测试数据集并用于评估模型的表现。

分配给每个数据集的行是随机选择的。这是为了确保模型的训练和评估是客观的。

如果比较多个算法或比较相同算法的多个配置,则应使用相同的训练和数据集的测试分割。这是为了确保表现的比较是一致的或是苹果对苹果。

我们可以通过在分割数据之前以相同的方式为随机数生成器播种,或者通过保持数据集的相同分割以供多个算法使用来实现此目的。

我们可以在单个函数中实现数据集的训练和测试分割。

下面是一个名为 **train_test_split()**的函数,用于将数据集拆分为训练并进行测试拆分。它接受两个参数,即要作为列表列表拆分的数据集和可选的拆分百分比。

使用默认分割百分比 0.6 或 60%。这将为训练数据集分配 60%的数据集,并将剩余的 40%留给测试数据集。训练/测试的 60/40 是数据的良好默认分割。

该函数首先根据提供的数据集计算训练集所需的行数。制作原始数据集的副本。从复制的数据集中选择并删除随机行,并将其添加到训练数据集,直到训练数据集包含目标行数。

然后,将保留在数据集副本中的行作为测试数据集返回。

随机模型中的 **randrange()**函数用于生成 0 到列表大小范围内的随机整数。

from random import randrange

# Split a dataset into a train and test set
def train_test_split(dataset, split=0.60):
	train = list()
	train_size = split * len(dataset)
	dataset_copy = list(dataset)
	while len(train) < train_size:
		index = randrange(len(dataset_copy))
		train.append(dataset_copy.pop(index))
	return train, dataset_copy

我们可以使用 10 行的人为数据集来测试这个函数,每个行都有一个列。

下面列出了完整的示例。

from random import seed
from random import randrange

# Split a dataset into a train and test set
def train_test_split(dataset, split=0.60):
	train = list()
	train_size = split * len(dataset)
	dataset_copy = list(dataset)
	while len(train) < train_size:
		index = randrange(len(dataset_copy))
		train.append(dataset_copy.pop(index))
	return train, dataset_copy

# test train/test split
seed(1)
dataset = [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]]
train, test = train_test_split(dataset)
print(train)
print(test)

该示例在拆分训练数据集之前修复随机种子。这是为了确保每次执行代码时都进行完全相同的数据分割。如果我们想多次使用相同的拆分来评估和比较不同算法的表现,这很方便。

运行该示例将生成以下输出。

打印训练和测试集中的数据,显示 6/10 或 60%的记录分配给训练数据集,4/10 或 40%的记录分配给测试集。

[[3], [2], [7], [1], [8], [9]]
[[4], [5], [6], [10]]

2. k-fold 交叉验证拆分

使用训练和测试分割方法的局限性在于您获得了算法表现的噪声估计。

k 折交叉验证方法(也称为交叉验证)是一种重采样方法,可提供更准确的算法表现估计。

它通过首先将数据分成 k 组来完成此操作。然后训练该算法并评估 k 次,并通过取平均表现得分来总结表现。每组数据称为折叠,因此名称为 k-fold 交叉验证。

它的工作原理是首先在 k-1 组数据上训练算法,然后在第 k 个保持组上作为测试集进行评估。重复这一过程,使得 k 组中的每一组都有机会被伸出并用作测试装置。

因此,k 的值应该可以被训练数据集中的行数整除,以确保每个 k 组具有相同的行数。

您应该为 k 选择一个值,该值将数据拆分为具有足够行的组,每个组仍然代表原始数据集。对于较小的数据集,使用的良好默认值是 k = 3,对于较大的数据集,k = 10。检查折叠尺寸是否具有代表性的快速方法是计算汇总统计量,例如平均值和标准差,并查看值与整个数据集的相同统计量的差异。

我们可以重复我们在上一节中学习的内容,在实现 k-fold 交叉验证时创建一个列和测试分割。

我们必须返回 k-folds 或 k 组数据,而不是两组。

下面是一个名为 **cross_validation_split()**的函数,它实现了数据的交叉验证拆分。

和以前一样,我们创建了一个数据集的副本,从中可以绘制随机选择的行。

我们计算每个折叠的大小,作为数据集的大小除以所需的折叠数。

fold size = total rows / total folds

如果数据集没有干净地除以折叠数,则可能会有一些剩余行,并且它们不会在拆分中使用。

然后,我们创建一个具有所需大小的行列表,并将它们添加到折叠列表中,然后在最后返回。

from random import randrange

# Split a dataset into k folds
def cross_validation_split(dataset, folds=3):
	dataset_split = list()
	dataset_copy = list(dataset)
	fold_size = int(len(dataset) / folds)
	for i in range(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

我们可以在与上面相同的小型人工数据集上测试这种重采样方法。每行只有一个列值,但我们可以想象这可能如何扩展到标准机器学习数据集。

The complete example is listed below.

和以前一样,我们为随机数生成器修复种子,以确保每次执行代码时在相同的折叠中使用相同的行。

k 值为 4 用于演示目的。我们可以预期,将 10 行划分为 4 行将导致每行 2 行,剩余 2 将不会用于拆分。

from random import seed
from random import randrange

# Split a dataset into k folds
def cross_validation_split(dataset, folds=3):
	dataset_split = list()
	dataset_copy = list(dataset)
	fold_size = int(len(dataset) / folds)
	for i in range(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

# test cross validation split
seed(1)
dataset = [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]]
folds = cross_validation_split(dataset, 4)
print(folds)

运行该示例将生成以下输出。打印折叠列表,显示确实如预期的那样每个折叠有两行。

[[[3], [2]], [[7], [1]], [[8], [9]], [[10], [6]]]

3.如何选择重采样方法

用于估计机器学习算法在新数据上的表现的黄金标准是 k 折交叉验证。

当配置良好时,与其他方法(如训练和测试分割)相比,k 折交叉验证可提供稳健的表现估计。

交叉验证的缺点是运行起来可能非常耗时,需要训练和评估 k 个不同的模型。如果您有一个非常大的数据集,或者您正在评估需要很长时间训练的模型,则会出现问题。

训练和测试分割重采样方法是最广泛使用的。这是因为它易于理解和实现,并且因为它可以快速估算算法表现。

只构建和评估单个模型。

尽管训练和测试分割方法可以对新数据的模型表现进行噪声或不可靠估计,但如果您拥有非常大的数据集,则这不会成为问题。

大型数据集是数十万或数百万条记录中的数据集,大到足以将其分成两半,导致两个数据集具有几乎相同的统计属性。

在这种情况下,可能几乎不需要使用 k 折交叉验证作为算法的评估,并且训练和测试分裂可能同样可靠。

扩展

在本教程中,我们研究了两种最常见的重采样方法。

您可能需要调查和实现其他方法作为本教程的扩展。

例如:

  • 重复训练和测试。这是使用训练和测试分割的地方,但过程重复多次。
  • LOOCV 或 Leave One Out Cross Validation 。这是 k 折交叉验证的一种形式,其中 k 的值固定为 n(训练样本的数量)。
  • 分层。在分类问题中,这是每组中类值的平衡被迫与原始数据集匹配的地方。

你实现了扩展吗? 在下面的评论中分享您的经历。

评论

在本教程中,您了解了如何从零开始在 Python 中实现重采样方法。

具体来说,你学到了:

  • 如何实现训练和测试分割方法。
  • 如何实现 k-fold 交叉验证方法。
  • 何时使用每种方法。

您对重采样方法或此帖有任何疑问吗? 在评论中提出您的问题,我会尽力回答。

如何用 Python 从零开始实现简单线性回归

原文: machinelearningmastery.com/implement-simple-linear-regression-scratch-python/

线性回归是一种超过 200 年的预测方法。

简单线性回归是一个很好的第一个机器学习算法,因为它需要你从训练数据集中估计属性,但是对于初学者来说很简单。

在本教程中,您将了解如何在 Python 中从零开始实现简单的线性回归算法。

完成本教程后,您将了解:

  • 如何从训练数据中估算统计量。
  • 如何从数据中估计线性回归系数。
  • 如何使用线性回归对新数据做出预测。

让我们开始吧。

  • 更新 Aug / 2018 :经过测试和更新,可与 Python 3.6 配合使用。
  • 2002 年 2 月更新:对保险数据集的预期默认 RMSE 进行小幅更新。

How To Implement Simple Linear Regression From Scratch With Python

如何使用 Python 实现简单的线性回归 照片由 Kamyar Adl ,保留一些权利。

描述

本节分为两部分,简单线性回归技术的描述和我们稍后将应用它的数据集的描述。

简单线性回归

线性回归假设输入变量(X)和单个输出变量(y)之间存在线性或直线关系。

更具体地,可以从输入变量(X)的线性组合计算输出(y)。当存在单个输入变量时,该方法称为简单线性回归。

在简单线性回归中,我们可以使用训练数据的统计量来估计模型所需的系数,以对新数据做出预测。

简单线性回归模型的行可以写成:

y = b0 + b1 * x

其中 b0 和 b1 是我们必须根据训练数据估计的系数。

一旦系数已知,我们可以使用此公式来估计给定 x 的新输入示例的 y 的输出值。

它要求您根据数据计算统计特性,例如均值,方差和协方差。

我们已经处理了所有的代数,我们留下了一些算法来实现估计简单的线性回归系数。

简而言之,我们可以估算系数如下:

B1 = sum((x(i) - mean(x)) * (y(i) - mean(y))) / sum( (x(i) - mean(x))² )
B0 = mean(y) - B1 * mean(x)

其中 i 指的是输入 x 或输出 y 的第 i 个值。

如果现在还不清楚,请不要担心,这些功能将在教程中实现。

瑞典保险数据集

我们将使用真实数据集来演示简单的线性回归。

该数据集被称为“瑞典汽车保险”数据集,并涉及根据索赔总数(x)预测数千瑞典克朗(y)中所有索赔的总付款额。

这意味着对于新的索赔(x),我们将能够预测索赔的总支付额(y)。

以下是数据集前 5 个记录的一小部分样本。

108,392.5
19,46.2
13,15.7
124,422.2
40,119.4

使用零规则算法(预测平均值)预期的均方根误差或 RMSE 约为 81(千克朗)。

下面是整个数据集的散点图。

Swedish Insurance Dataset

瑞典保险数据集

您可以在 或 下载的原始数据集。

将其保存到本地工作目录中的 CSV 文件,名称为“ insurance.csv ”。

注意,您可能需要将欧洲“,”转换为小数“。”。您还需要将文件从空格分隔的变量更改为 CSV 格式。

教程

本教程分为五个部分:

  1. 计算均值和方差。
  2. 计算协方差。
  3. 估算系数。
  4. 作出预测。
  5. 预测保险。

这些步骤将为您提供实现和训练简单线性回归模型所需的基础,以满足您自己的预测问题。

1.计算均值和方差

第一步是从训练数据中估计输入和输出变量的均值和方差。

数字列表的平均值可以计算为:

mean(x) = sum(x) / count(x)

下面是一个名为 **mean()**的函数,它为数字列表实现了这种行为。

# Calculate the mean value of a list of numbers
def mean(values):
	return sum(values) / float(len(values))

方差是每个值与平均值的总和平方差。

数字列表的差异可以计算为:

variance = sum( (x - mean(x))² )

下面是一个名为 **variance()**的函数,它计算数字列表的方差。它要求将列表的均值作为参数提供,这样我们就不必多次计算它。

# Calculate the variance of a list of numbers
def variance(values, mean):
	return sum([(x-mean)**2 for x in values])

我们可以将这两个函数放在一起,并在一个小的设计数据集上进行测试。

下面是 x 和 y 值的小数据集。

注意:如果将其保存到.CSV 文件以与最终代码示例一起使用,则从该数据中删除列标题。

x, y
1, 1
2, 3
4, 3
3, 2
5, 5

我们可以在散点图上绘制此数据集,如下所示:

Small Contrived Dataset For Simple Linear Regression

简单线性回归的小受控数据集

我们可以在下面的例子中计算 x 和 y 值的均值和方差。

# Estimate Mean and Variance

# Calculate the mean value of a list of numbers
def mean(values):
	return sum(values) / float(len(values))

# Calculate the variance of a list of numbers
def variance(values, mean):
	return sum([(x-mean)**2 for x in values])

# calculate mean and variance
dataset = [[1, 1], [2, 3], [4, 3], [3, 2], [5, 5]]
x = [row[0] for row in dataset]
y = [row[1] for row in dataset]
mean_x, mean_y = mean(x), mean(y)
var_x, var_y = variance(x, mean_x), variance(y, mean_y)
print('x stats: mean=%.3f variance=%.3f' % (mean_x, var_x))
print('y stats: mean=%.3f variance=%.3f' % (mean_y, var_y))

运行此示例会打印出两列的均值和方差。

x stats: mean=3.000 variance=10.000
y stats: mean=2.800 variance=8.800

这是我们的第一步,接下来我们需要将这些值用于计算协方差。

2.计算协方差

两组数字的协方差描述了这些数字如何一起变化。

协方差是相关性的推广。相关性描述了两组数字之间的关系,而协方差可以描述两组或更多组数字之间的关系。

另外,可以对协方差进行归一化以产生相关值。

不过,我们可以计算两个变量之间的协方差如下:

covariance = sum((x(i) - mean(x)) * (y(i) - mean(y)))

下面是一个名为 **covariance()**的函数,它实现了这个统计量。它建立在前一步骤的基础上,并将 x 和 y 值的列表以及这些值的平均值作为参数。

# Calculate covariance between x and y
def covariance(x, mean_x, y, mean_y):
	covar = 0.0
	for i in range(len(x)):
		covar += (x[i] - mean_x) * (y[i] - mean_y)
	return covar

我们可以在与前一节相同的小型设计数据集上测试协方差的计算。

总而言之,我们得到以下示例。

# Calculate Covariance

# Calculate the mean value of a list of numbers
def mean(values):
	return sum(values) / float(len(values))

# Calculate covariance between x and y
def covariance(x, mean_x, y, mean_y):
	covar = 0.0
	for i in range(len(x)):
		covar += (x[i] - mean_x) * (y[i] - mean_y)
	return covar

# calculate covariance
dataset = [[1, 1], [2, 3], [4, 3], [3, 2], [5, 5]]
x = [row[0] for row in dataset]
y = [row[1] for row in dataset]
mean_x, mean_y = mean(x), mean(y)
covar = covariance(x, mean_x, y, mean_y)
print('Covariance: %.3f' % (covar))

运行此示例将打印 x 和 y 变量的协方差。

Covariance: 8.000

我们现在已经准备好所有部件来计算模型的系数。

3.估算系数

我们必须在简单线性回归中估计两个系数的值。

第一个是 B1,可以估算为:

B1 = sum((x(i) - mean(x)) * (y(i) - mean(y))) / sum( (x(i) - mean(x))² )

我们已经学到了上面的一些东西,可以简化这个算法:

B1 = covariance(x, y) / variance(x)

我们已经有了计算**协方差()方差()**的函数。

接下来,我们需要估计 B0 的值,也称为截距,因为它控制与 y 轴相交的直线的起点。

B0 = mean(y) - B1 * mean(x)

同样,我们知道如何估计 B1,我们有一个函数来估计 mean()

我们可以将所有这些放在一个名为 **coefficients()**的函数中,该函数将数据集作为参数并返回系数。

# Calculate coefficients
def coefficients(dataset):
	x = [row[0] for row in dataset]
	y = [row[1] for row in dataset]
	x_mean, y_mean = mean(x), mean(y)
	b1 = covariance(x, x_mean, y, y_mean) / variance(x, x_mean)
	b0 = y_mean - b1 * x_mean
	return [b0, b1]

我们可以将它与前两个步骤中的所有函数放在一起,并测试系数的计算。

# Calculate Coefficients

# Calculate the mean value of a list of numbers
def mean(values):
	return sum(values) / float(len(values))

# Calculate covariance between x and y
def covariance(x, mean_x, y, mean_y):
	covar = 0.0
	for i in range(len(x)):
		covar += (x[i] - mean_x) * (y[i] - mean_y)
	return covar

# Calculate the variance of a list of numbers
def variance(values, mean):
	return sum([(x-mean)**2 for x in values])

# Calculate coefficients
def coefficients(dataset):
	x = [row[0] for row in dataset]
	y = [row[1] for row in dataset]
	x_mean, y_mean = mean(x), mean(y)
	b1 = covariance(x, x_mean, y, y_mean) / variance(x, x_mean)
	b0 = y_mean - b1 * x_mean
	return [b0, b1]

# calculate coefficients
dataset = [[1, 1], [2, 3], [4, 3], [3, 2], [5, 5]]
b0, b1 = coefficients(dataset)
print('Coefficients: B0=%.3f, B1=%.3f' % (b0, b1))

运行此示例计算并打印系数。

Coefficients: B0=0.400, B1=0.800

现在我们知道如何估计系数,下一步就是使用它们。

4.做出预测

简单线性回归模型是由训练数据估计的系数定义的线。

一旦估计了系数,我们就可以使用它们做出预测。

使用简单线性回归模型做出预测的等式如下:

y = b0 + b1 * x

下面是一个名为 **simple_linear_regression()**的函数,它实现预测方程以对测试数据集做出预测。它还将来自上述步骤的训练数据的系数估计联系在一起。

从训练数据准备的系数用于对测试数据做出预测,然后返回。

def simple_linear_regression(train, test):
	predictions = list()
	b0, b1 = coefficients(train)
	for row in test:
		yhat = b0 + b1 * row[0]
		predictions.append(yhat)
	return predictions

让我们将我们学到的所有内容汇集在一起​​,并为我们简单的人为数据集做出预测。

作为此示例的一部分,我们还将添加一个函数来管理名为 **evaluate_algorithm()**的预测评估,并添加另一个函数来估计名为 rmse_metric()的预测的均方根误差

下面列出了完整的示例。

# Standalone simple linear regression example
from math import sqrt

# 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 regression algorithm on training dataset
def evaluate_algorithm(dataset, algorithm):
	test_set = list()
	for row in dataset:
		row_copy = list(row)
		row_copy[-1] = None
		test_set.append(row_copy)
	predicted = algorithm(dataset, test_set)
	print(predicted)
	actual = [row[-1] for row in dataset]
	rmse = rmse_metric(actual, predicted)
	return rmse

# Calculate the mean value of a list of numbers
def mean(values):
	return sum(values) / float(len(values))

# Calculate covariance between x and y
def covariance(x, mean_x, y, mean_y):
	covar = 0.0
	for i in range(len(x)):
		covar += (x[i] - mean_x) * (y[i] - mean_y)
	return covar

# Calculate the variance of a list of numbers
def variance(values, mean):
	return sum([(x-mean)**2 for x in values])

# Calculate coefficients
def coefficients(dataset):
	x = [row[0] for row in dataset]
	y = [row[1] for row in dataset]
	x_mean, y_mean = mean(x), mean(y)
	b1 = covariance(x, x_mean, y, y_mean) / variance(x, x_mean)
	b0 = y_mean - b1 * x_mean
	return [b0, b1]

# Simple linear regression algorithm
def simple_linear_regression(train, test):
	predictions = list()
	b0, b1 = coefficients(train)
	for row in test:
		yhat = b0 + b1 * row[0]
		predictions.append(yhat)
	return predictions

# Test simple linear regression
dataset = [[1, 1], [2, 3], [4, 3], [3, 2], [5, 5]]
rmse = evaluate_algorithm(dataset, simple_linear_regression)
print('RMSE: %.3f' % (rmse))

运行此示例将显示以下输出,该输出首先列出这些预测的预测和 RMSE。

[1.1999999999999995, 1.9999999999999996, 3.5999999999999996, 2.8, 4.3999999999999995]
RMSE: 0.693

最后,我们可以将预测绘制为一条线并将其与原始数据集进行比较。

Predictions For Small Contrived Dataset For Simple Linear Regression

简单线性回归的小参数数据集预测

5.预测保险

我们现在知道如何实现简单的线性回归模型。

我们将它应用于瑞典保险数据集。

本节假定您已将数据集下载到文件 insurance.csv ,并且它在当前工作目录中可用。

我们将为前面步骤中的简单线性回归添加一些便利函数。

特别是加载称为 **load_csv()**的 CSV 文件的函数,该函数将加载的数据集转换为称为 **str_column_to_float()**的数字,这是一个使用训练和测试来评估算法的函数设置调用 **train_test_split()**一个函数来计算称为 **rmse_metric()**的 RMSE 和一个函数来评估一个叫做 **evaluate_algorithm()**的算法。

下面列出了完整的示例。

使用 60%数据的训练数据集来准备模型,并对剩余的 40%做出预测。

# Simple Linear Regression on the Swedish Insurance Dataset
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())

# Split a dataset into a train and test set
def train_test_split(dataset, split):
	train = list()
	train_size = split * len(dataset)
	dataset_copy = list(dataset)
	while len(train) < train_size:
		index = randrange(len(dataset_copy))
		train.append(dataset_copy.pop(index))
	return train, dataset_copy

# 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 train/test split
def evaluate_algorithm(dataset, algorithm, split, *args):
	train, test = train_test_split(dataset, split)
	test_set = list()
	for row in test:
		row_copy = list(row)
		row_copy[-1] = None
		test_set.append(row_copy)
	predicted = algorithm(train, test_set, *args)
	actual = [row[-1] for row in test]
	rmse = rmse_metric(actual, predicted)
	return rmse

# Calculate the mean value of a list of numbers
def mean(values):
	return sum(values) / float(len(values))

# Calculate covariance between x and y
def covariance(x, mean_x, y, mean_y):
	covar = 0.0
	for i in range(len(x)):
		covar += (x[i] - mean_x) * (y[i] - mean_y)
	return covar

# Calculate the variance of a list of numbers
def variance(values, mean):
	return sum([(x-mean)**2 for x in values])

# Calculate coefficients
def coefficients(dataset):
	x = [row[0] for row in dataset]
	y = [row[1] for row in dataset]
	x_mean, y_mean = mean(x), mean(y)
	b1 = covariance(x, x_mean, y, y_mean) / variance(x, x_mean)
	b0 = y_mean - b1 * x_mean
	return [b0, b1]

# Simple linear regression algorithm
def simple_linear_regression(train, test):
	predictions = list()
	b0, b1 = coefficients(train)
	for row in test:
		yhat = b0 + b1 * row[0]
		predictions.append(yhat)
	return predictions

# Simple linear regression on insurance dataset
seed(1)
# load and prepare data
filename = 'insurance.csv'
dataset = load_csv(filename)
for i in range(len(dataset[0])):
	str_column_to_float(dataset, i)
# evaluate algorithm
split = 0.6
rmse = evaluate_algorithm(dataset, simple_linear_regression, split)
print('RMSE: %.3f' % (rmse))

运行算法会在训练数据集上打印训练模型的 RMSE。

获得了大约 33(千克朗)的分数,这比在相同问题上实现大约 81(数千克朗)的零规则算法好得多。

RMSE: 33.630

扩展

本教程的最佳扩展是在更多问题上尝试该算法。

只有输入(x)和输出(y)列的小数据集很受欢迎,可用于统计书籍和课程的演示。其中许多数据集都可在线获取。

寻找更多小型数据集并使用简单的线性回归做出预测。

您是否将简单线性回归应用于其他数据集? 在下面的评论中分享您的经验。

评论

在本教程中,您了解了如何在 Python 中从零开始实现简单的线性回归算法。

具体来说,你学到了:

  • 如何从训练数据集中估计统计量,如均值,方差和协方差。
  • 如何估计模型系数并使用它们做出预测。
  • 如何使用简单线性回归对真实数据集做出预测。

你有什么问题吗? 在下面的评论中提出您的问题,我会尽力回答。