【机器学习】向量化使得简单线性回归性能提升
向量化使得简单线性回归性能提升
- 一、摘要
- 二、向量化运算概述
- 三、向量化运算在简单线性回归中的应用
- 四、性能测试与结果分析
一、摘要
本文主要讲述了向量化运算在简单线性回归算法中的应用。通过回顾传统for循环方式实现的简单线性回归算法,介绍了如何通过最小二乘法计算a的值。然而,这种方式在计算性能上存在效率较低的问题。为了提高性能,视频引入了向量化运算的概念,即将计算过程从循环方式转变为向量之间的计算。通过向量化运算,可以快速地计算出结果,相比传统的for循环方式,在性能上具有显著的优势。视频还强调了向量化运算需要基于推导的结果进行,对于入门级别的同学来说,可以先实现更清晰的程序版本,再基于这个版本进行向量化,以更好地理解算法的原理。
二、向量化运算概述
向量化概述:
- 向量化运算是一种重要的概念,可以提高计算性能。
- 通过将计算转换为向量之间的运算,可以大大提升性能。
向量化运算性能高的原因:
在计算机学科中,向量化运算通常比for循环等具有更好的计算性能,主要原因如下:
- 硬件层面
- 利用SIMD指令集:现代计算机的CPU通常支持单指令多数据(SIMD)指令集,如Intel的SSE、AVX等。向量化运算能够直接利用这些指令集,在一条指令中同时对多个数据元素进行相同的操作,例如可以同时对4个或8个单精度浮点数进行加法运算。而for循环每次只能处理一个数据元素,需要多条指令来完成相同数量数据的操作,这使得向量化运算在数据处理效率上有天然的优势。
- 提高缓存命中率:向量化运算通常会连续访问内存中的数据,这有利于提高CPU缓存的命中率。因为数据在内存中是连续存储的,当加载一个数据元素时,相邻的数据元素也很可能被加载到缓存中,后续对这些数据的访问就可以直接从缓存中获取,大大加快了访问速度。相比之下,for循环可能会以非连续的方式访问数据,导致缓存失效的情况更频繁,需要从内存中多次读取数据,增加了访问时间。
- 软件层面
- 减少指令开销:for循环需要在每次迭代时执行循环控制指令,如比较、跳转等,这些指令会增加额外的开销。而向量化运算将多个数据的处理合并为一个操作,减少了指令的数量和执行时间,从而提高了整体性能。
- 编译器优化:编译器对向量化运算的优化能力更强。编译器可以识别向量化的代码模式,并对其进行更高级的优化,如自动展开循环、调整指令顺序等,以充分利用硬件的特性。对于for循环,虽然编译器也能进行一些优化,但由于其灵活性较高,编译器很难像对向量化运算那样进行深度优化。
- 并行执行能力:向量化运算更适合并行计算。在多核处理器或GPU等并行计算环境中,向量化运算可以很容易地将数据划分到不同的核心或线程中进行并行处理,从而进一步提高计算速度。for循环虽然也可以通过一些方式实现并行化,但需要更多的代码调整和同步操作,实现起来相对复杂,且可能存在数据竞争等问题,影响性能。
三、向量化运算在简单线性回归中的应用
- 在简单线性回归中,a(斜率)的计算方式可以通过向量化运算来优化。
- 通过将计算转换为向量点乘,可以避免使用for循环,提高效率。
- 向量化运算的实现
- 基于上一篇博文中的SimpleLinearRegressionModel类所在工程中文件改名成为SimplyLearnRegressionDemo.py,然后在其中新建一个SimpleLinearRegressionVectorModel类,使得fit函数通过向量化运算来计算a和b。
- 通过使用numpy的dot方法,实现了向量点乘,提高了计算速度。
- 具体SimpleLinearRegressionVectorModel代码如下:
import numpy as np class SimpleLinearRegressionModel: def __init__(self): # 初始化线性回归模型的参数a和b为None self.a_ = None self.b_ = None def fit(self, x_train, y_train): """ 根据训练数据集x_train,y_train训练Simple Linear Regression模型 """ # 断言x_train的维度为1,确保是单特征训练数据 assert x_train.ndim == 1, \ "Simple Linear Regressor can only solve single feature training data." # 断言x_train和y_train的长度相等,确保数据匹配 assert len(x_train) == len(y_train), \ "the size of x_train must be equal to the size of y_train" # 先分别求出x和y的均值 x_mean = np.mean(x_train) y_mean = np.mean(y_train) # 先求a的分子部分 numerator = 0.0 # 分子 denominator = 0.0 # 分母 # 遍历训练数据,计算分子和分母 for x_i, y_i in zip(x_train, y_train): numerator += (x_i - x_mean) * (y_i - y_mean) denominator += (x_i - x_mean) ** 2 # 然后计算a的值 self.a_ = numerator / denominator # 最后计算b的值 self.b_ = y_mean - self.a_ * x_mean return self def predict(self, x_predict): """ 给定待预测数据集x_predict,返回表示x_predict的结果向量 """ # 断言x_predict的维度为1,确保是单特征数据 assert x_predict.ndim == 1, \ "Simple Linear Regressor can only solve single feature training data." # 断言模型已经训练过(a_和b_不为None) assert self.a_ is not None and self.b_ is not None, \ "must fit before predict!" # 对每个待预测数据调用_predict方法,生成预测结果数组 return np.array([self._predict(x) for x in x_predict]) def _predict(self, x_single): """ 给定单个待预测数据x_single,返回x_single的预测结果值 """ # 根据训练得到的参数a_和b_进行预测计算 return self.a_ * x_single + self.b_ def __repr__(self): # 返回模型的字符串表示形式 return "SimpleLinearRegressionModel()" """向量化的方式实现简单线性回归的算法""" class SimpleLinearRegressionVectorModel: def __init__(self): # 初始化线性回归模型的参数a和b为None self.a_ = None self.b_ = None def fit(self, x_train, y_train): """ 根据训练数据集x_train,y_train训练Simple Linear Regression模型 """ # 断言x_train的维度为1,确保是单特征训练数据 assert x_train.ndim == 1, \ "Simple Linear Regressor can only solve single feature training data." # 断言x_train和y_train的长度相等,确保数据匹配 assert len(x_train) == len(y_train), \ "the size of x_train must be equal to the size of y_train" # 先分别求出x和y的均值 x_mean = np.mean(x_train) y_mean = np.mean(y_train) # 先求a的分子部分 # 向量化的方式实现 numerator = (x_train - x_mean).dot(y_train - y_mean) denominator = (x_train - x_mean).dot(x_train - x_mean) # 然后计算a的值 self.a_ = numerator / denominator # 最后计算b的值 self.b_ = y_mean - self.a_ * x_mean return self def predict(self, x_predict): """ 给定待预测数据集x_predict,返回表示x_predict的结果向量 """ # 断言x_predict的维度为1,确保是单特征数据 assert x_predict.ndim == 1, \ "Simple Linear Regressor can only solve single feature training data." # 断言模型已经训练过(a_和b_不为None) assert self.a_ is not None and self.b_ is not None, \ "must fit before predict!" # 对每个待预测数据调用_predict方法,生成预测结果数组 return np.array([self._predict(x) for x in x_predict]) def _predict(self, x_single): """ 给定单个待预测数据x_single,返回x_single的预测结果值 """ # 根据训练得到的参数a_和b_进行预测计算 return self.a_ * x_single + self.b_ def __repr__(self): # 返回模型的字符串表示形式 return "SimpleLinearRegressionVectorModel()"
四、性能测试与结果分析
-
在jupyter中进行性能测试
# 导入在PyCharm中封装好的工程项目 import sys project_path = 'D:/PycharmProjects/pythonProject/' if project_path not in sys.path: sys.path.append(project_path) from SimpleLinearRegressionDemo import SimpleLinearRegressionModel from SimpleLinearRegressionDemo import SimpleLinearRegressionVectorModel # for循环实现的 sreg = SimpleLinearRegressionModel() # 向量化实现的 vreg = SimpleLinearRegressionVectorModel() # 定义一个随机的测试数据 num = 100000 # 自定义线性方程中的x,随机给定 big_x = np.random.random(size=num) # 定义线性方程中的y,其中np.random.normal(size=num)是给定了一个噪声 big_y = big_x * 2.0 + 3.0 + np.random.normal(size=num) # 使用魔法命令%timeit计算程序执行所花时间 %timeit sreg.fit(big_x,big_y) %timeit vreg.fit(big_x,big_y)
执行结果如下:
51.9 ms ± 546 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) 760 µs ± 11.9 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
下面为你详细解释其含义:
第一行:51.9 ms ± 546 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
51.9 ms
:这是每次循环的平均执行时间,单位是毫秒(ms
),也就是说该代码每次循环平均花费 51.9 毫秒来执行。± 546 µs
:这表示测量结果的标准差,单位是微秒(µs
)。标准差反映了多次测量结果的离散程度,这里说明每次循环执行时间在平均时间 51.9 毫秒左右波动,波动范围大约是正负 546 微秒。mean ± std. dev. of 7 runs, 10 loops each
:这描述了测量的具体方式。7 runs
表示进行了 7 次独立的测试运行;10 loops each
表示每次运行中代码循环执行了 10 次。最终的平均时间和标准差是基于这 7 次运行,每次 10 个循环的所有测量结果计算得出的。
第二行:
760 µs ± 11.9 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
760 µs
:每次循环的平均执行时间,单位是微秒(µs
),即该代码每次循环平均花费 760 微秒来执行。± 11.9 µs
:测量结果的标准差,单位是微秒。说明每次循环执行时间在平均时间 760 微秒左右波动,波动范围大约是正负 11.9 微秒。mean ± std. dev. of 7 runs, 1,000 loops each
:同样描述了测量方式。进行了 7 次独立的测试运行,每次运行中代码循环执行了 1000 次,平均时间和标准差是基于这些测量结果计算的。
对比分析
- 从这两组数据可以看出,第二组代码的执行效率明显高于第一组。第二组每次循环平均耗时 760 微秒,而第一组每次循环平均耗时 51.9 毫秒(即 51900 微秒)。你可以根据这些性能数据来优化代码或者选择更高效的实现方式。
在jupyter中执行过程:
-
结果显示,向量化运算实现的SimpleLinearRegressionVectorModel性能大幅提升,达到了68倍的速度提升。