自己动手实现线性回归
我们往往会用类似sklearn的linear_model模块直接调包去使用线性回归处理一些回归问题,但我们绝不能只会调包!
接下来我们就用面向对象的方式,自己实现一个线性回归类,并且在这个过程中体会从数学公式到python代码的美妙。
在这里我将不会讲解线性回归的原理和公式(毕竟这是python为主题的公众号),而是聚焦于”从公式到代码“的过程。所以如果对公式原理以及一些线性回归的相关术语不熟悉,可以去看相关的资料充充电,这并不难,毕竟线性回归是机器学习里最简单直观的算法了
简述线性回归模型
线性回归就是输入一堆数据(假设有m条,每条数据有n个属性,也就是m行n列),结果训练后得到模型参数(也就是线性回归方程中与每个属性相乘的那些值,有n个属性,就有对应的n个参数)的过程。
训练的方法有许多,我大致分两类,一类就是正则方程,也就是直接通过解矩阵方程得到参数。另一类就是梯度下降,它又可以细分为批量梯度下降算法、随机梯度下降算法以及小批量梯度下降算法。梯度下降更加有通用性,因为其它机器学习模型的损失函数都极其复杂,无法获得最值的解析解,所以可以说正则方程法算是线性回归模型的”特色“。
设计自定义的线性回归类
初始化函数:
初始化函数接收参与训练的数据集,并且随机设定模型参数
getNormalEquation函数
正则方程函数
BGD函数
BGD函数是梯度下降算法的函数
SGD函数
SGD函数是随机梯度下降算法函数,接收学习率这个参数
MBGD函数
MBGD函数是小批量梯度下降算法的函数,接收学习率这个参数
train_BGD函数
用BGD算法训练,接收迭代率和学习率两个参数
train_SGD函数
用SGD算法训练,接收迭代率和学习率两个参数
train_MBGD函数
用MBGD算法训练,接收迭代率和学习率两个参数
Shuffle函数
这个函数主要用于生成与训练数据集大小相同的随机自然数序列。主要用于SGD函数和MBGD函数中,通过随机数序列打乱训练集和标签集,消除训练集顺序对SGD算法和MBGD算法的影响
Cost函数
这个函数就是平均训练的损失函数,计算训练数据集在模型参数下的预测结果与实际值之间的均方误差。它主要用于三个迭代训练函数中,记录每次迭代后的误差
predict函数
这个函数是预测函数,接受一组测试数据,然后结合模型参数对测试数据进行预测
test函数
这个函数和预测函数很相似,都是结合模型参数预测结果,不同的是这个函数接受的是测试数据集,它遍历整个测试数据集,每次都调用predict函数进行预测
自定义线性回归类的结构
import numpy as np
"""
这是一个关于线性回归的模块
核心部分就是LinearRegression这个类
这个类有以下几个方法
构造方法、根据参数计算预测值的方法、计算损失值的方法、生成随机数序列的方法、使用批量梯度下降、随机梯度下降、小批量随机梯度下降更新参数的方法
"""
class LinearRegress:
def __init__(self, input_data, real_result, theta):
def Cost(self):
def Shuffle(self):
def BGD(self,alpha):
def SGD(self,alpha):
def MBGD(self,alpha,batch_size):
def train_BGD(self,iter,alpha):
def train_SGD(self,iter,alpha):
def train_MBGD(self,iter,batch_size,alpha):
def predict(self,data):
def test(self,test_data):
初始化函数的代码实现
初始化函数__init__的参数是训练数据集input_data、训练结果集real_result以及参数theta。在初始化函数内,主要完成训练数据集、结果集以及模型参数的初始化,与此同时还要遍历每组数据,并且要对数据进行拓展,也就是增加一维常数属性1(因为线性回归方程都是z=ax+by+c这种类似形式,c前面的系数是1)
当theta的默认值是None,如果是None,那么就用正态分布随机给模型参数赋值,如若不是,就用theta作为模型参数
import numpy as np
class LinearRegress:
def __init__(self, input_data, real_result, theta):
"""
inputData :输入数据 (m条记录,n个属性,m*n)
realResult :真实数据 (m条记录,1个值,m*1)
theta :线性回归的参数,也就是这个算法不断更新迭代优化得到的核心数据 (对应n个属性,n*1)
默认为None 如果是None就用正态分布的数据来初始化参数,如果不是None,就用传进来的theta初始化参数
"""
# 输入数据集的形状
row, col = np.shape(input_data)
# 通过输入数据集,建立这个类里面的成员变量Data,先初始化为m个0组成的列表
self.Data = [0] * row
# 给m条数据中的每一条数据都添加上一个常数项1,这一步是为了与线性回归模型中的偏置b对应起来
for (index, data) in enumerate(input_data):
# 将每一条数据构成的列表后面都追加一个1.0,并且根据序号index将它们存放到成员变量Data相应位置中
self.Data[index] = list(data).append(1.0)
# 转化为numpy的格式更加高效
self.Data = np.array(self.Data)
# 通过输入的结果数据集,建立这个类中的成员变量Result
self.Result = real_result
# 如果参数不为None的时候,利用传入的theta初始化相应参数
if theta is not None:
self.Theta = theta
# 不然就用正态分布的数据来初始化
else:
# 此处选取了(n+1*1)的数据,也是为了和偏置匹配
self.Theta = np.random.normal((col + 1, 1))