乐趣区

关于人工智能:Python机器学习算法线性回归

作者 |Vagif Aliyev
编译 |VK
起源 |Towards Data Science

线性回归可能是最常见的算法之一,线性回归是机器学习实践者必须晓得的。这通常是初学者第一次接触的机器学习算法,理解它的操作形式对于更好地了解它至关重要。

所以,简略地说,让咱们来合成一下真正的问题:什么是线性回归?

线性回归定义

线性回归是一种有监督的学习算法,旨在采纳线性办法来建模因变量和自变量之间的关系。换句话说,它的指标是拟合一条最好地捕获数据关系的线性趋势线,并且,从这条线,它能够预测目标值可能是什么。

太好了,我晓得它的定义,但它是如何工作的呢?好问题!为了答复这个问题,让咱们逐渐理解一下线性回归是如何运作的:

  1. 拟合数据(如上图所示)。
  2. 计算点之间的间隔(图上的红点是点,绿线是间隔),而后求平方,而后求和(这些值是平方的,以确保负值不会产生谬误的值并妨碍计算)。这是算法的误差,或者更好地称为残差
  3. 存储迭代的残差
  4. 基于一个优化算法,使得该线略微“挪动”,以便该线能够更好地拟合数据。
  5. 反复步骤 2 -5,直到达到现实的后果,或者残余误差减小到零。

这种拟合直线的办法称为最小二乘法。

线性回归背地的数学

如果曾经了解的请随便跳过这一部分

线性回归算法如下:

能够简化为:

以下算法将根本实现以下操作:

  1. 承受一个 Y 向量(你的数据标签,(房价,股票价格,等等…)

这是你的指标向量,稍后将用于评估你的数据(稍后将具体介绍)。

  1. 矩阵 X(数据的特色):

这是数据的特色,即年龄、性别、性别、身高等。这是算法将理论用于预测的数据。留神如何有一个特色 0。这称为截距项,且始终等于 1。

  1. 取一个权重向量,并将其转置:

这是算法的神奇之处。所有的特征向量都会乘以这些权重。这就是所谓的点积。实际上,你将尝试为给定的数据集找到这些值的最佳组合。这就是所谓的优化。

  1. 失去输入向量:

这是从数据中输入的预测向量。而后,你能够应用老本函数来评估模型的性能。

这基本上就是用数学示意的整个算法。当初你应该对线性回归的性能有一个松软的了解。但问题是,什么是优化算法?咱们如何抉择最佳权重?咱们如何评估绩效?

老本函数

老本函数实质上是一个公式,用来掂量模型的损失或“老本”。如果你已经加入过任何 Kaggle 较量,你可能会遇到过一些。一些常见的办法包含:

  • 均方误差
  • 均方根误差
  • 均匀绝对误差

这些函数对于模型训练和开发是必不可少的,因为它们答复了“我的模型预测新实例的能力如何”这一根本问题?”. 请记住这一点,因为这与咱们的下一个主题无关。

优化算法

优化通常被定义为改良某事物,使其施展其全副后劲的过程。这也实用于机器学习。在 ML 的世界里,优化实质上是试图为某个数据集找到最佳的参数组合。这基本上是机器学习的“学习”局部。

我将探讨两种最常见的算法:梯度降落法和规范方程。

梯度降落

梯度降落是一种优化算法,旨在寻找函数的最小值。它通过在梯度的负方向上迭代地采取步骤来实现这个指标。在咱们的例子中,梯度降落将通过挪动函数切线的斜率来不断更新权重。

梯度降落的一个具体例子

为了更好地阐明梯度降落,让咱们看一个简略的例子。设想一个人在山顶上,他 / 她想爬到山底。他们可能会做的是环顾四周,看看应该朝哪个方向迈出一步,以便更快地下来。而后,他们可能会朝这个方向迈出一步,当初他们离指标更近了。然而,它们在下降时必须小心,因为它们可能会在某一点卡住,所以咱们必须确保相应地抉择咱们的步长。


同样,梯度降落的指标是最小化函数。在咱们的例子中,这是为了使咱们的模型的老本最小化。它通过找到函数的切线并朝那个方向挪动来实现这一点。算法“步长”的大小是由已知的学习速率来定义的。这基本上管制着咱们向下挪动的间隔。应用此参数,咱们必须留神两种状况:

  1. 学习速率太大,算法可能无奈收敛(达到最小值)并在最小值左近反弹,但永远不会达到该值
  2. 学习率太小,算法将破费太长时间能力达到最小值,也可能会“卡”在一个次长处上。

咱们还有一个参数,它控制算法迭代数据集的次数。

从视觉上看,该算法将执行以下操作:

因为此算法对机器学习十分重要,让咱们回顾一下它的作用:

  1. 随机初始化权重。这叫做随机初始化
  2. 而后,模型应用这些随机权重进行预测
  3. 模型的预测是通过老本函数来评估的
  4. 而后模型运行梯度降落,找到函数的切线,而后在切线的斜率上迈出一步
  5. 该过程将反复 N 次迭代,或者如果满足某个条件。

梯度降落法的优缺点

长处:

  1. 很可能将老本函数升高到全局最小值(十分靠近或 =0)
  2. 最无效的优化算法之一

毛病:

  1. 在大型数据集上可能比较慢,因为它应用整个数据集来计算函数切线的梯度
  2. 容易陷入次长处(或部分极小值)
  3. 用户必须手动抉择学习速率和迭代次数,这可能很耗时

既然曾经介绍了梯度降落,当初咱们来介绍规范方程。

规范方程(Normal Equation)

如果咱们回到咱们的例子中,而不是一步一步地往下走,咱们将可能立刻达到底部。规范方程就是这样。它利用线性代数来生成权重,能够在很短的工夫内产生和梯度降落一样好的后果。

规范方程的优缺点

长处

  1. 无需抉择学习速率或迭代次数
  2. 十分快

毛病

  1. 不能很好地扩大到大型数据集
  2. 偏向于产生好的权重,但不是最佳权重

特色缩放

这是许多机器学习算法的重要预处理步骤,尤其是那些应用间隔度量和计算(如线性回归和梯度降落)的算法。它实质上是缩放咱们的特色,使它们在类似的范畴内。把它设想成一座房子,一座房子的比例模型。两者的形态是一样的(他们都是房子),但大小不同(5 米!=500 米)。咱们这样做的起因如下:

  1. 它放慢了算法的速度
  2. 有些算法对尺度敏感。换言之,如果特色具备不同的尺度,则有可能将更高的权重赋予具备更高量级的特色。这将影响机器学习算法的性能,显然,咱们不心愿咱们的算法偏差于一个特色。

为了演示这一点,假如咱们有三个特色,别离命名为 A、B 和 C:

  • 缩放前 AB 间隔 =>

  • 缩放前 BC 间隔 =>

  • 缩放后 AB 间隔 =>

  • 缩放后 BC 的间隔 =>

咱们能够分明地看到,这些特色比缩放之前更具可比性和无偏性。

从头开始编写线性回归

好吧,当初你始终在期待的时刻;实现!

留神:所有代码都能够从这个 Github repo 下载。然而,我倡议你在执行此操作之前先遵循教程,因为这样你将更好地了解你理论在编写什么代码:

https://github.com/Vagif12/ML…

首先,让咱们做一些根本的导入:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_boston

是的,这就是所有须要导入的了!咱们应用的是 numpy 作为数学实现,matplotlib 用于绘制图形,以及 scikitlearn 的 boston 数据集。

# 加载和拆分数据
data = load_boston()
X,y = data['data'],data['target']

接下来,让咱们创立一个定制的 train_test_split 函数,将咱们的数据拆分为一个训练和测试集:

# 拆分训练和测试集
def train_test_divide(X,y,test_size=0.3,random_state=42):
    np.random.seed(random_state)
    train_size = 1 - test_size
    arr_rand = np.random.rand(X.shape[0])
    split = arr_rand < np.percentile(arr_rand,(100*train_size))
    
    X_train = X[split]
    y_train = y[split]
    X_test =  X[~split]
    y_test = y[~split]
    
    return X_train, X_test, y_train, y_test
X_train,X_test,y_train,y_test = train_test_divide(X,y,test_size=0.3,random_state=42)

基本上,咱们在进行

  1. 失去测试集大小。
  2. 设置一个随机种子,以确保咱们的后果和可重复性。
  3. 依据测试集大小失去训练集大小
  4. 从咱们的特色中随机抽取样本
  5. 将随机抉择的实例拆分为训练集和测试集

咱们的老本函数

咱们将实现 MSE 或均方误差,一个用于回归工作的常见老本函数:

def mse(preds,y):
        m = len(y)
        return 1/(m) * np.sum(np.square((y - preds)))
  • M 指的是训练实例的数量
  • yi 指的是咱们标签向量中的一个实例
  • preds 指的是咱们的预测

为了编写洁净、可反复和高效的代码,并恪守软件开发实际,咱们将创立一个线性回归类:

class LinReg:
    def __init__(self,X,y):
        self.X = X
        self.y = y
        self.m = len(y)
        self.bgd = False
  • bgd 是一个参数,它定义咱们是否应该应用批量梯度降落。

当初咱们将创立一个办法来增加截距项:

def add_intercept_term(self,X):
        X = np.insert(X,1,np.ones(X.shape[0:1]),axis=1).copy()
        return X

这基本上是在咱们的特色开始处插入一个列。它只是为了矩阵乘法。

如果咱们不加上这一点,那么咱们将迫使超平面通过原点,导致它大幅度歪斜,从而无奈正确拟合数据

缩放咱们的特色:

def feature_scale(self,X):
        X = (X - X.mean()) / (X.std())
        return X

接下来,咱们将随机初始化权重:

def initialise_thetas(self):
        np.random.seed(42)
        self.thetas = np.random.rand(self.X.shape[1])

当初,咱们将应用以下公式从头开始编写规范方程:

def normal_equation(self):
        A = np.linalg.inv(np.dot(self.X.T,self.X))
        B = np.dot(self.X.T,self.y)
        thetas = np.dot(A,B)
        return thetas

基本上,咱们将算法分为三个局部:

  1. 咱们失去了 X 转置后与 X 的点积的逆
  2. 咱们失去分量和标签的点积
  3. 咱们失去两个计算值的点积

这就是规范方程!还不错!当初,咱们将应用以下公式实现批量梯度降落:

def batch_gradient_descent(self,alpha,n_iterations):
        self.cost_history = [0] * (n_iterations)
        self.n_iterations = n_iterations
        
        for i in range(n_iterations):
            h = np.dot(self.X,self.thetas.T)
            gradient = alpha * (1/self.m) * ((h - self.y)).dot(self.X)
            
            self.thetas = self.thetas - gradient
            self.cost_history[i] = mse(np.dot(self.X,self.thetas.T),self.y)
            
        return self.thetas

在这里,咱们执行以下操作:

  1. 咱们设置 alpha,或者学习率,和迭代次数
  2. 咱们创立一个列表来存储咱们的老本函数历史记录,以便当前在折线图中绘制
  3. 循环 n_iterations 次,
  4. 咱们失去预测,并计算梯度(函数切线的斜率)。
  5. 咱们更新权重以沿梯度负方向挪动
  6. 咱们应用咱们的自定义 MSE 函数记录值
  7. 反复,实现后,返回后果

让咱们定义一个拟合函数来拟合咱们的数据:

def fit(self,bgd=False,alpha=0.158,n_iterations=4000):
        self.X = self.add_intercept_term(self.X)
        self.X = self.feature_scale(self.X)
        if bgd == False:
            
            self.thetas = self.normal_equation()
        else:
            self.bgd = True
            self.initialise_thetas()
            self.thetas = self.batch_gradient_descent(alpha,n_iterations)

在这里,咱们只须要检查用户是否须要梯度降落,并相应地执行咱们的步骤。

让咱们构建一个函数来绘制老本函数:

def plot_cost_function(self):
        
        if self.bgd == True:
            plt.plot(range((self.n_iterations)),self.cost_history)
            plt.xlabel('No. of iterations')
            plt.ylabel('Cost Function')
            plt.title('Gradient Descent Cost Function Line Plot')
            plt.show()
        else:
            print('Batch Gradient Descent was not used!')

最初一种预测未标记实例的办法:

def predict(self,X_test):
        self.X_test = X_test.copy()
        self.X_test = self.add_intercept_term(self.X_test)
        self.X_test = self.feature_scale(self.X_test)
        predictions = np.dot(self.X_test,self.thetas.T)
        return predictions

当初,让咱们看看哪个优化产生了更好的后果。首先,让咱们试试梯度降落:

lin_reg_bgd = LinReg(X_train,y_train)
lin_reg_bgd.fit(bgd=True)

mse(y_test,lin_reg_bgd.predict(X_test))

OUT:
28.824024414708344

让咱们画出咱们的函数,看看老本函数是如何缩小的:

所以咱们能够看到,在大概 1000 次迭代时,它开始收敛。

当初的规范方程是:

lin_reg_normal = LinReg(X_train,y_train)
lin_reg_normal.fit()

mse(y_test,lin_reg_normal.predict(X_test))

OUT:
22.151417764247284

所以咱们能够看到,规范方程的性能略优于梯度降落法。这可能是因为数据集很小,而且咱们没有为学习率抉择最佳参数。

将来

  1. 大幅度提高学习率。会产生什么?
  2. 不利用特色缩放。有区别吗?
  3. 尝试钻研一下,看看你能不能实现一个更好的优化算法。在测试集中评估你的模型

写这篇文章真的很乏味,尽管有点长,但我心愿你明天学到了一些货色。

原文链接:https://towardsdatascience.co…

欢送关注磐创 AI 博客站:
http://panchuang.net/

sklearn 机器学习中文官网文档:
http://sklearn123.com/

欢送关注磐创博客资源汇总站:
http://docs.panchuang.net/

退出移动版