本文采用批量梯度下降BGD、随机梯度下降SGD、小批量梯度下降MBGD、和正则方程,四种思路来构造不同的线性回归预测器。
我们经常会用到梯度下降法来对机器学习算法进行训练。也就是批量梯度下降法BGD,随机梯度下降法SGD,小批量梯度下降MBGD法。
可以参考此链接资料:http://www.cnblogs.com/maybe2030/p/5089753.html#_label0
import numpy as np class LR(object): def __init__(self, input_data, realresult, theta=None): """ :param input_data: 输入数据 :param realresult: 真实数据 :param theta: 线性回归的参数,默认为None """ # 获得输入数据的形状 row, col = np.shape(input_data) # 构造输入数据数组 self.input_data = [0]*row # 给每组输入数据增添常数项1 for (index, data) in enumerate(input_data): Data = [1.0] # 把input_data拓展到Data内,即把input_data的每一维数据添加到Data Data.extend(list(data)) self.input_data[index] = Data self.input_data = np.array(self.input_data) # 构造输入数据对应的结果 self.result = np.array(realresult) # 参数theta不为None时,利用theta构造模型参数 if theta is not None: self.theta = theta else: # 随机生成服从标准正态分布的参数 self.theta = np.random.normal(0, 1, (col+1, 1)) def Cost(self): """ 这是计算损失函数的函数 """ # 在线性回归中的损失函数定义为真实结果与预测结果之间的均方误差 # 首先计算输入数据的预测结果 predict = self.input_data.dot(self.theta) # 计算真实结果与预测结果之间的均方误差 cost = (predict - self.result).T[0] cost = np.average(cost ** 2) return cost def BGD(self, alpha): """ BGD:batch gradient descent批量梯度下降,能收敛到全局最优解,但计算所需的内存空间巨大 这是利用BGD算法进行一次迭代调整参数的函数 :param alpha: 学习率 """ # 定义梯度增量数组 gradient_increasment = [] # 对输入的训练数据及其真实结果进行遍历 for (input_data, real_result) in zip(self.input_data, self.result): # 计算每组input_data的梯度增量,并放入梯度增量数组 g = (input_data.dot(self.theta) - real_result) * input_data gradient_increasment.append(g) # 按列计算属性的平均梯度增量 ave_g = np.average(gradient_increasment, 0) # 改变平均梯度增量数组的形状 ave_g = ave_g.reshape(len(ave_g), 1) # 将行向量变成列向量 # 更新模型参数self.theta self.theta = self.theta - alpha * ave_g def Shuffle_Sequence(self): """ 这是运行SGD算法或MBGD算法之前,随机打乱原始数据集的函数 """ # 首先获得训练集的规模,然后按照规模生成自然数序列 length = len(self.input_data) random_sequence = list(range(length)) # 利用numpy的随机打乱函数打乱训练数据下标 random_sequence = np.random.permutation(random_sequence) # 返回数据集随机打乱后的数据序列 return random_sequence def SGD(self, alpha): """ SGD:stochastic gradient descent随机梯度下降,属于贪心算法,求得的是次优解;频繁的调节超参数,加快了收敛速度 这是利用SGD算法进行一次迭代调整参数的函数 :param alpha:学习率 """ # 首先将数据集随机打乱,减少数据集顺序对参数调优的影响 shuffle_sequence = self.Shuffle_Sequence() self.input_data = self.input_data[shuffle_sequence] self.result = self.result[shuffle_sequence] # 对训练数据进行遍历,利用每组训练数据对参数进行调整 for (input_data, real_result) in zip(self.input_data, self.result): # 计算每组数据input_data的梯度增量 g = (input_data.dot(self.theta) - real_result) * input_data # 调整每组input_data的梯度增量的形状 g = g.reshape(len(g), 1) # 更新线性回归的模型参数 self.theta = self.theta - alpha * g def MBGD(self, alpha, batch_size): """ MBGD函数,根据小批量样本规模batch_size将训练数据集划分为多个小批量训练样本,遍历这些小批量样本, 与BGD类似,计算每个小批量样本上的平均梯度增量并结合学习率alpha来更新模型参数self.theta :param alpha: 学习率 :param batch_size: 小批量样本规模 """ # 首先利用shuffle_sequence函数将训练数据集随机打乱,减少数据集顺序对参数调优的影响 shuffle_sequence = self.Shuffle_Sequence() self.input_data = self.input_data[shuffle_sequence] self.result = self.result[shuffle_sequence] # 遍历每个小批量样本 for start in np.arange(0, len(shuffle_sequence), batch_size): # 判断start + batch_size是否大于数组长度 # 防止最后一组小批量样本规模可能小于batch_size end = np.min([start + batch_size, len(shuffle_sequence)]) # 获取训练小批量样本集及其标签 mini_batch = shuffle_sequence[start:end] mini_train_input_data = self.input_data[mini_batch] mini_train_result = self.result[mini_batch] # 定义梯度增量数组 gradient_increasment = [] for (data, result) in zip(mini_train_input_data, mini_train_result): # 计算每组data的梯度增量,并放入梯度值增量数组 g = (data.dot(self.theta) - result) * data gradient_increasment.append(g) # 按列计算每组小样本训练集的平均梯度增量 ave_g = np.average(gradient_increasment, 0) # 改变平均梯度增量数组的形状 ave_g = ave_g.reshape(len(ave_g), 1) # 将行向量变成列向量 # 更新模型参数self.theta self.theta = self.theta - alpha * ave_g def train_BGD(self, iter, alpha): """ 这是利用BGD算法迭代优化的函数 :param iter: 迭代次数 :param alpha: 学习率 :return: cost """ # 定义平均训练损失数组,记录每轮迭代的训练数据集的损失 cost = [] # 开始进行迭代训练 for i in range(iter): # 利用学习率alpha,结合BGD算法对模型进行训练 self.BGD(alpha) # 记录每次迭代的平均训练损失 cost.append(self.Cost()) cost = np.array(cost) return cost def train_SGD(self, iter, alpha): """ 这是利用SGD算法迭代优化的函数 :param iter: 迭代次数 :param alpha: 学习率 :return: cost """ # 定义平均训练损失数组,记录每轮迭代的训练数据集的损失 cost = [] # 开始进行迭代训练 for i in range(iter): # 利用学习率alpha,结合SGD算法对模型进行训练 self.SGD(alpha) # 记录每次迭代的平均训练损失 cost.append(self.Cost()) cost = np.array(cost) return cost def train_MBGD(self, iter, batch_size, alpha): """ 这是利用MBGD算法迭代优化的函数 :param iter: 迭代次数 :param alpha: 学习率 :param batch_size: 小批量样本规模 :return: cost """ # 定义平均训练损失数组,记录每轮迭代的训练数据集的损失 cost = [] # 开始进行迭代训练 for i in range(iter): # 利用学习率alpha,结合MBGD算法对模型进行训练 self.MBGD(alpha, batch_size) # 记录每次迭代的平均训练损失 cost.append(self.Cost()) cost = np.array(cost) return cost def predict(self, data): """ 这是对一组测试数据预测的函数 :param data: 测试数据 :return: predict_result """ # 对测试数据加一维,以适应矩阵乘法 temp = [1.0] temp.extend(data) data = np.array(temp) # 计算预测结果,计算结果形状为(1,) predict_result = data.dot(self.theta).T[0] return predict_result def test(self, test_data): """ 这是对测试数据集的线性回归预测函数 :param test_data: 测试数据集 """ # 定义预测结果数组 predict_result = [] # 对测试数据进行遍历 for data in test_data: # 预测每组data的结果 predict_result.append(self.predict(data)) predict_result = np.array(predict_result) return predict_result def getNormalEquation(self): """ x.T * x * theta = x.T * Y "为正则方程,theta = (x.T*x)^(-1)*(x.T*Y) 这是利用正则方程计算模型参数self.theta """ """ 0.001 * np.eye(np.shape(self.input_data.T))是防止出现原始X.T不可逆 """ # 获得输入数据数组形状 col, row = np.shape(self.input_data.T) # 计算输入数据矩阵的转置 XT = self.input_data.T + 0.001 * np.eye(col, row) # 计算矩阵的逆 inv = np.linalg.inv(XT.dot(self.input_data)) # 计算模型参数self.theta self.theta = inv.dot(XT.dot(self.result))
import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib as mpl from sklearn.datasets import load_boston from sklearn.model_selection import train_test_split from LinearRegression import LR def Merge(data, col): """ 这是生成DataFrame数据的函数 :param data: 输入数据,为行向量 :param col: 列名称数组 """ data = np.array(data).T return pd.DataFrame(data, columns=col) #.....导入数据及划分训练数据与测试数据.... boston = load_boston() input_data = boston.data result = boston.target # 为行向量 # 只取第6维特征,平均房间数目 input_data = np.array(input_data)[:, 5] # 为行向量 # 保存原始数据集 data = Merge([input_data, result], ['平均房间数目', '房价']) data.to_excel('./原始数据.xlsx') # 改变数据集的与真实房价数组的形状 input_data = input_data.reshape(len(input_data), 1) # 将行向量变为列向量,为数组类型 result = np.array(result).reshape(len(result), 1) # 将数据集分成训练数据集和测试数据集 train_data, test_data, train_result, test_result = \ train_test_split(input_data, result, test_size=0.3, random_state=50) # 解决matplotlib中的中文乱码问题,以便于后面实验结果的可视化 mpl.rcParams['font.sans-serif'] = [u'simHei'] mpl.rcParams['axes.unicode_minus'] = False # 利用散点图可视化测试数据集,并保存可视化结果 plt.scatter(test_data, test_result, alpha=0.5, c='b', s=10) plt.grid(True) plt.xlabel('房间数') plt.ylabel('真实房价') plt.legend(labels='真实房价', loc='best') plt.savefig('./测试集可视化.jpg', bbox_inches='tight') plt.show() plt.close() # ....开始构建线性回归模型.... # BGD优化的线性回归模型 LinearRegression_BGD = LR(train_data, train_result, theta=None) # SGD优化的线性回归模型 LinearRegression_SGD = LR(train_data, train_result, theta=None) # MBGD优化的线性回归模型 LinearRegression_MBGD = LR(train_data, train_result, theta=None) # 正则方程优化的线性回归模型 LinearRegression_NormalEquation = LR(train_data, train_result, theta=None) # ....训练模型.... iter = 30000 # 迭代次数 alpha = 0.001 # 学习率 batch_size = 64 # 小批量样本规模 # BGD的平均训练损失 BGD_train_cost = LinearRegression_BGD.train_BGD(iter, alpha) # SGD的平均训练损失 SGD_train_cost = LinearRegression_SGD.train_SGD(iter, alpha) # MBGD的平均训练损失 MBGD_train_cost = LinearRegression_MBGD.train_MBGD(iter, batch_size, alpha) # 利用正则方程获取的参数 LinearRegression_NormalEquation.getNormalEquation() # 3种梯度下降算法平均训练损失结果可视化,并保存可视化结果 col = ['BGD', 'SGD', 'MBGD'] iter = np.arange(iter) plt.plot(iter, BGD_train_cost, 'r-.') plt.plot(iter, SGD_train_cost, 'b-') plt.plot(iter, MBGD_train_cost, 'k--') plt.grid(True) plt.xlabel('迭代次数') plt.ylabel('平均损失函数') plt.legend(labels=col, loc='best') plt.savefig('./3种梯度下降算法的平均训练损失.jpg', bbox_inches='tight') plt.show() plt.close() # 整合3种梯度下降算法的平均训练损失到DataFrame train_cost = [BGD_train_cost, SGD_train_cost, MBGD_train_cost] train_cost = Merge(train_cost, col=['BGD', 'SGD', 'MBGD']) # 保存三种梯度下降算法的平均训练损失及其统计信息 train_cost.to_excel('./3种梯度下降算法的平均训练损失.xlsx') # train_cost.describe().to_excel('./3种梯度下降算法的平均训练损失统计') print("3种梯度下降算法的平均训练损失统计\n", train_cost.describe()) # 计算4种调优算法下的拟合曲线 x = np.arange(int(np.min(test_data)), int(np.max(test_data) + 1)) x = x.reshape(len(x), 1) # 将行向量转化为列向量 # BGD算法的拟合曲线 BGD = LinearRegression_BGD.test(x) # SGD算法的拟合曲线 SGD = LinearRegression_SGD.test(x) # MBGD算法的拟合曲线 MBGD = LinearRegression_MBGD.test(x) # 正则方程的拟合曲线 NormalEquation = LinearRegression_NormalEquation.test(x) # 4种模型的拟合直线可视化,并保存可视化结果 col = ['BGD', 'SGD', 'MBGD', '正则方程'] plt.plot(x, BGD, 'r-.') plt.plot(x, SGD, 'b-') plt.plot(x, MBGD, 'k--') plt.plot(x, NormalEquation, 'g:') plt.scatter(test_data, test_result, alpha=0.5, c='b', s=10) plt.grid(True) plt.xlabel('房间数') plt.ylabel('预测值') plt.legend(labels=col, loc='best') plt.savefig('./不同模型或梯度下降算法的预测值比较.jpg', bbox_inches='tight') plt.show() plt.close() # 利用测试集进行线性回归预测 # BGD算法的预测结果 BGD_predict = LinearRegression_BGD.test(test_data) # SGD算法的预测结果 SGD_predict = LinearRegression_SGD.test(test_data) # MBGD算法的预测结果 MBGD_predict = LinearRegression_MBGD.test(test_data) # 正则方程的预测结果 NormalEquation_predict = LinearRegression_NormalEquation.test(test_data) # 保存预测数据 # x.tolist()是将numpy.array转化为python的list类型的函数 data = [test_data.T.tolist()[0], test_result.T.tolist()[0], BGD_predict, SGD_predict, MBGD_predict, NormalEquation_predict] col = ['平均房间数目', '真实房价', 'BGD预测结果', 'SGD预测结果', 'MBGD预测结果', '正则方程预测结果'] data = Merge(data, col) data.to_excel('./测试数据与不同模型和梯度下降算法的预测结果.xlsx') # 计算四种算法的均方误差及其统计信息 # test_result之前的形状为(num,1),首先计算其转置后 test_result = test_result.T[0] # BGD算法的均方误差 BGD_error = ((BGD_predict - test_result) ** 2) # SGD算法的均方误差 SGD_error = ((SGD_predict - test_result) ** 2) # BGD算法的均方误差 MBGD_error = ((MBGD_predict - test_result) ** 2) # BGD算法的均方误差 NormalEquation_error = ((NormalEquation_predict - test_result) ** 2) # 整合四种算法的均方误差到DataFrame error = [BGD_error, SGD_error, MBGD_error, NormalEquation_error] col = ['BGD', 'SGD', 'MBGD', '正则方程'] error = Merge(error, col) # 保存四种均方误差及其统计信息 error.to_excel('./四种算法的预测均方误差原始数据.xlsx') print("四种算法的预测均方误差统计\n", error.describe())
MBGD在训练集上相对于BDG有较快的收敛速度;也避免了SGD算法的由于过早收敛且波动较大的问题。与梯度下降算法相比,正则方程由于时间复杂度大,更适合小数据集;另外,正则表达式的调参效果无法与梯度下降算法相比,且在测试集上的性能比较差,误差相比其他的较大。