线性回归-最小二乘方法代码实现

线性回归-最小二乘方法

使用最小二乘的方法进行原始的计算方式编写

先把该导入的包全部导入了

# 首先需要导入对应的包
import pandas as pd  # 数据处理
import numpy as np  # 数据计算
import matplotlib.pyplot as plt  # 画图
from sklearn.model_selection import train_test_split  # 专门用于数据分割的函数,分割训练集和测试集

# 因为画图,所以先设置字符集防止中文无法显示的问题
plt.rcParams['font.sans-serif'] = [u'simHei']
plt.rcParams['axes.unicode_minus'] = False

1 读取数据

数据存储在文件中需要读取,现在使用的数据是用户的不同时间段家用电量的表格

dir = './data/household_power_consumption_1000.txt'
# 使用pandas进行数据导入
# 使用的是;进行分隔,数据中可能存在大量对的混合类型,所以需要进行low_menorey设置
data_source = pd.read_csv(dir, sep=';', low_memory=False)

数据导入完成后查看下数据,大致分清需要哪些数据参数计算,由于是估计功率和电流之间的关系,所以取出数据的时候就需要取出对应的数据即可

2 取出数据

# 根据数据的内容而言,数据结构如下
# 日期、时间、有功功率、无功功率、电压、电流、厨房用电功率、洗衣服用电功率、热水器用电功率
# 寻找功率和电流之间的关系
# 这里就需要对数据(可以看做是一个二维矩阵)进行切片选出需要的数据
X = data_source.iloc[:, 2:4]  # 切片不包括最后一项
Y = data_source.iloc[:, 5]

pandas的数据结构中可以直接使用iloc进行切片,其切片的规则和numpy中的规则非常相似,因为数据本身是一个表格,这个表格有行有列,只有两个数据维度,那么每个样本存在多个数据,每个属性就是一列

3 分割数据

这里采用的是随机划分,将数据划分成为训练集:测试集=4:1的形式,使用随机抽样的方法,当然分割数据肯定不止是这一种方式,这里先使用这种随机划分的方式

# 划分数据,test_size是按照比例,random_state是表示随机种子
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=0)

4 数据变化

直接的pandas数据不好计算,转成numpy数据进行处理,之后使用最小二乘的时候对矩阵的求逆矩阵,求转置等等,方便计算

# 建立两个训练数据的关系
x_train = np.mat(X_train)
y_train = np.mat(Y_train).reshape(-1,1) # 因为y实际上搞出来不是一个列向量,这里进行转一把

这里有个坑,如果本身的pandas就是一列的话这里直接转回造成数据的矩阵型状无法参与后面的计算,所以这里需要转变一下

5 计算出系数矩阵

theta的求解利用最小二乘的方式,根据理论,中间的计算推导步骤很麻烦,要用到矩阵求导,大量的矩阵变化,好在我们直接有最后的公式
Θ = ( X T X ) 1 X T Y \Theta = (X^T X)^{-1} X^T Y
这里已经有X和Y了,直接带进去计算

theta = (x_train.T * x_train).I * x_train.T * y_train

参数矩阵就已经得到

6 利用参数矩阵计算预测结果

参数矩阵已经得到后,带入原来的y=kx+b这种线性方程直接可以计算出预测值,而测试集数据需要用x_test去和参数矩阵计算,计算的结果与y_test进行比较

# 再带入theta进行验证 使用测试集合获得预测的数值
the_predict = np.mat(X_test) * theta

7 画出对比图

为了直观的看到整个图,这里直接画出图,但是画图需要注意的是x轴的取值,因为数据本身是对比测试值和预测值的关系,那么这两个的样本都是一一对应的,将样本取值当做x轴显然不现实,每个样本本身就具有多个属性,因此,这里使用个数来当做x轴取值

# 接下来画图
# 创建图布
fig = plt.figure()
# 设置标题
plt.title('功率和电流的预测与实际对比图')
# 设置x轴和y轴
# 这里因为数据样品是离散的随机取样,所以x轴定为点的序号,比如第一个点第二个点
x = np.arange(len(X_test)) # 总共的样本个数
plt.plot(x, Y_test, 'r-',label='真实值',) # 红色的为真实值
plt.plot(x, the_predict, 'g-',label='预测值',) # 绿色的为预测值
plt.legend(loc = 'lower right') # 设置图例显示的位置
plt.show()

以上是最原始的最小二乘法,中间的模型是通过自己计算的到的结论,但是我们的前辈们早就已经计算好了对应的theta公式,直接用就可以了,但是这里还存在特例,就是X转置乘以X如果不是正定矩阵,则还不能这么计算

使用模型导包的方式进行预测

下面是另外的方式进行计算用电的功率和时间之间的关系

在实际项目中,线性回归模型实际上已经是封装好的模型

依然开始各种导入

# coding=gbk
# 因为里面存在中文,所以这里直接添加后就不报错了
# 导入对应的库
import numpy as np # 数据计算
import matplotlib.pyplot as plt # 画图
import pandas as pd # 数据处理
from sklearn.linear_model import LinearRegression # 线性回归模型
from sklearn.model_selection import train_test_split  # 构建数据分割器
from sklearn.preprocessing import StandardScaler # 归一化数据的模型
# 配置对应的中文显示问题
plt.rcParams['font.sans-serif'] = [u'simHei']
plt.rcParams['axes.unicode_minus'] = False

这次因为里面注释写的多,不知道哪里有问题,反正不加第一个就报错,既然网上查了说加上就不拨错,亲测有效,所以没有为什么,加上就好了

1 导入数据

# 导入文件
data = pd.read_csv('./data/household_power_consumption_1000.txt', sep=';', low_memory=False)

2 截取需要的数据

这里考虑的是时间和功率之间的关系,但是时间本身是一个字符串,在数据中存在两列用来记录时间

这里需要将这两列单独取出来

data.iloc[:, :2]

依然使用的是切片的方式

取出来之后并不能直接参与计算,因为字符串是需要进行转换才能进行计算,时间有年月日时分秒六个单位组成,数据中确实也是记录下了这六个单位,因此需要进行对应的处理把时间分解出来,使用6个属性进行描述时间

将字符串转变为时间的函数

time.striptime(str, format)

这里需要得到str,也就是时间字符串,原来的时间字符串是存储在表中的两列,虽然能够切片进行切出来,但是实际上还是一个具有很多行的列表,这里使用

pandas_data.apply(function, axis=1, args, kwargs)

这个函数的意义在于进行转换,把pandas数据传递到function函数中进行处理,axis=1表示已一行一行的传进去,很显然这里的function就是一个函数,但是这个函数不能直接在后面加上()传参调用,这里只能写函数名

所以不管这个函数本身需要几个参数,这里默认的第一个参数就是pandas数据的一行,以列表的方式传进去,那么如果这个函数本身还需要其他参数的话就通过args和kwargs进行传入,这两个参数是留给function的,因为只写函数名没法指明需要的参数,所以这里留下了两个接口

既然apply把时间对应的两列拆成一行一行的传进去,那么就应该由function这个函数来接收,传进去的是时间字符串列表,所以需要自定义函数处理

def date_reduce(dt):
    import time
    dt = time.striptime(' '.join(dt), '%d/%m/%Y %H:%M:%S')
    return(t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)

这里得到传进来的列表后进行拆分,把列表转成可以用的时间字符串,之后使用格式化字符串对原时间字符串转义,返回的是一个时间对象

前面说过需要将时间数据拆分成年月日时分秒六个属性,所以这里直接返回这六个属性对应的值

apply函数非常漂亮的将返回的接收了这六个属性值,但是它依然不是正确的能够使用的数据各式,因此这里使用

pd.Serires(data)

使用pd的数据类型Serires直接强行将返回的数据转成Serires类,这就很像str()这种方式强行转成字符串一样

因此整个时间的处理如果下

X = data.iloc[:, :2].apply(lambda x: pd.Series(date_reduce(x)), axis=1)

得到了可以用于计算的X样本数据

下面再把需要的Y值取出来

Y = data.iloc[:, 2]  # 就当做是numpy一样直接取

3 使用拆分函数直接将数据集拆分称为训练集和测试集

# 分成训练集和测试集
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=0)

但是这里只要查看一下就会发现这个数据根本不能使用,X中存在六个属性,而且使用方法进行查看

print(X_train.describe())

你会发现这六个属性中有三个属性的std(标准差)为0,还有两个标准差很大,标准差为0说明数据本身没有变动,那就意味着整个一列都是完全相同的,这样的数据在前面增加任何系数都没有意义,标准差很大的数据表明波动性太大,因此前面的系数确定就很不均衡,容易给一些属性过高的权重而使得某些属性根本就没有权重

为了解决这些问题,必须引入归一化操作

4 归一化数据

归一化是利用模型将数据进行压缩到标准差为1的区间,这样使得每个有用的属性都把标准差压缩到1,大家权重相同,这样训练出来的模型可靠度要高一些

ss = StandardScaler()

直接导入标准化模型对象,此时整个模型还是空的,还没有给他数据进行训练,将训练数据扔进这个模型中,其训练出来的数据就是经过标准化以后的数据了,并且将针对这些数据的标准化计算方法封装在ss对象上

这样做的好处在于如果后面训练出来的预测模型效果还不错,那么就把这个模型和预测模型都保存起来,以后来的新数据都会先进过ss进行标准化后再扔进预测模型进行计算

归一化训练数据和测试数据

X_train = ss.fit_transform(X_train)
X_test = ss.transform(X_test)

这里上面和下面不一样,这里包含fit的方法存在着训练的含义,单纯的transform表示你转变,上面已经fit一次表示已经训练好了x_train数据然后再_transform,表示再把训练数据转变成训练好的数据返回.

下面的X_test的意思就是直接对x_test进行转变返回,这里不能再训练,因为一旦再训练就会给覆盖原来的训练模型ss,那么X_test与X_train又不是完全相同,这就会回使得获得X_train数据时候的ss模型与获得X_test数据的ss模型不载相同,后面一定会存在误差.所以这里训练过的模型就不再训练了

5 使用最小二乘进行计算theta值

这里当然可以对数据进行numpy转变,使用我们的推导结论公式带入直接计算.这样做没有任何问题

但是这里我们使用别人已经写好的模型

model_train = LinearRegression(fit_intercept=True)  # 创建模型对象

同ss一样,这个只是创建了模型对象,还没有进行训练

model_train.fit(X_train, Y_train)

带入数据进行训练,这里只有fit没有后面的transform,表示这里只是把数据扔进去进行模型训练,不需要返回值,我们需要的是模型,执行完这一步之后模型就已经训练完成了,theta什么的已经集成到model_train这个对象身上了,后面只需要调用这个对象就行了

Y_predict = model_train.predict(X_test)

使用这个模型直接把测试数据扔进去进行计算,并把计算后的值反回出来,因为这里扔进去的是测试数据,所以计算结果就是预测值,看吧这里别人直接就算好了

现在我们手里面有了预测数据和测试数据的真实值,我们也不知道准不准确,不要惊慌,别人封装好的功能在model_train上面也有

# 查看模型的r方 也叫做准确率
print(f'训练样本的R**2(准确率):{model_train.score(X_train,Y_train)}')
print(f'测试样本的R**2(准确率):{model_train.score(X_test,Y_test)}')

print(f'模型参数θ:{model_train.coef_}')  # coef_可以直接获取到训练后的参数
print(f'模型截距:{model_train.intercept_}')  # 线性回归y=kx+b的b就是截距

直接可以查看准确率,当然这个模型训练的不咋样,准确率只有0.22.这不是重点,重点是这里直接可以通过模型.score(输入,输出)进行查询看看模型到底效果如何,同时还可以看到参数和截距

y=kx+b

k找到了,b找到了,还有模型的正确率,基本上这次建模就已经完成了

6 画图

当然如果想更直观的看到模型效果如何,可以直接把他们画图画出来

fig = plt.figure()
# 定义x轴
x = np.arange(len(X_test))
plt.plot(x, Y_test, 'r-', label='真实值')
plt.plot(x, Y_predict, 'g-', label='预测值')
plt.legend(loc='upper left')
plt.title('以时间的自变量预测功率与时间的关系模型')
plt.show()

7 模型保存

模型已经训练完成,如果效果还不错的话则需要把模型保存起来方便以后有了新的数据直接调用这个模型进行预测

这里就需要想清楚需要保存哪些东西,首先ss肯定要存,你来的新数据难道就不归一化么?不归一化的话带进去的值就不满足预测模型需要的值,所以ss模型一定要存

用脚指头想也应该想到需要存预测模型,费了半天劲就是为了搞到预测模型,不存它存谁

from sklearn.externals import joblib

joblib.dump(ss, './standard_to_lr_data_model.model')  # ss = StandardScaler() 标准化类:已经训练过的
joblib.dump(model_train, './lr_predict_model.model')  # 就是导入的训练对象model_train = LinearRegression(fit_intercept=True):已经训练过的

最后给总代码

# coding=gbk
# 因为里面存在中文,所以这里直接添加后就不报错了
# 导入对应的库
import numpy as np # 数据计算
import matplotlib.pyplot as plt # 画图
import pandas as pd # 数据处理
from sklearn.linear_model import LinearRegression # 线性回归模型
from sklearn.model_selection import train_test_split  # 构建数据分割器
from sklearn.preprocessing import StandardScaler # 归一化数据的模型


def date_reduce(dt):
    # 传进来的是两列数据,这两列数据是时间,实际是一个列表两个元素,用空格拼接称为字符串
    import time
    t = time.strptime(' '.join(dt), '%d/%m/%Y %H:%M:%S')
    # 解析了数据之后进行返回时间元组
    return (t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)


# 配置对应的中文显示问题
plt.rcParams['font.sans-serif'] = [u'simHei']
plt.rcParams['axes.unicode_minus'] = False

# 导入文件
data = pd.read_csv('./data/household_power_consumption_1000.txt', sep=';', low_memory=False)
# 因为需要处理时间这个变量,其对应的是前两列做对应的变化
X = data.iloc[:, :2].apply(lambda x: pd.Series(date_reduce(x)), axis=1)
# 值得一提的是apply函数的使用,apply函数的参数,第一个参数是一个函数,这个参数是需要传递一个函数,可以是一个隐函数,这里如果不是隐函数则传递的函数自动的会根据后面参数的决定传递的对象,第二个参数是axis,表示轴,如果为1,则表示一行一行的传进去,那么谁调用就传递谁,后面的args和kwargs都是设定传递给第一个参数的,因为第一个参数是写函数名,写函数名没有办法直接执行,所以设计args和kwargs进行传参
# pd.Series()是将里面的内容转变成为Series数据类型

# 处理好时间后继续获取对应的Y
Y = data.iloc[:, 2]  # 就当做是numpy一样直接取
# 分成训练集和测试集
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=0)
# print(X_train.describe())
# 查询的里面存在一个std,这个是标准差,平方后就是方差,查询后发现里面存在std为0的数据,标准差为0,表示数据根本就没有发送变化
# std大了也表示波动很大,对于方差大的,需要规范化到0-1之间,最好将所有的数据都归一化到0-1之间,这样才不会导致造成参数非常大

# 标准化数据
ss = StandardScaler()  # 创建标准化对象,使用标准差为1的
# print(X_train)
X_train = ss.fit_transform(X_train)  # 转变为了可以训练用的数据
# print(X_train)
# 写出对应的测试数据
X_test = ss.transform(X_test)  # 妈蛋,感觉跟fit_transform没有啥子区别
# print(X_test)

# 使用DataFrame将数据处理称为列表形式
# print(X_train.describe()) # 这里会报错,说的是numpy.ndarray没有对应的属性
# 这就是说明X_train通过StandardScaler的fit_transform之后称为了numpy数组对象

# print(pd.DataFrame(X_train).describe())

# print(data.info())

# 直接训练模型
model_train = LinearRegression(fit_intercept=True)  # 创建模型对象
# 训练模型
model_train.fit(X_train, Y_train)
# 获得预测值
Y_predict = model_train.predict(X_test)

# 查看模型的r方 也叫做准确率
print(f'训练样本的R**2(准确率):{model_train.score(X_train,Y_train)}')
print(f'测试样本的R**2(准确率):{model_train.score(X_test,Y_test)}')

# 预测值减去实际值的平方后再求均值 就是均方差
mse = np.average((Y_predict - Y_test) ** 2)
# 如果再开根号 就是标准差
rmse = np.sqrt(mse)

print(f'模型参数θ:{model_train.coef_}')  # coef_可以直接获取到训练后的参数
print(f'模型截距:{model_train.intercept_}')  # 线性回归y=kx+b的b就是截距

# 两个模型实际上已经完成了,现在可以进行画图操作
fig = plt.figure()
# 定义x轴
x = np.arange(len(X_test))
plt.plot(x, Y_test, 'r-', label='真实值')
plt.plot(x, Y_predict, 'g-', label='预测值')
plt.legend(loc='upper left')
plt.title('以时间的自变量预测功率与时间的关系模型')
plt.show()

# 到这里为止,已经训练好了两个模型
# 1 标准化模型,这个模型是针对数据进行归一化的模型,保存它的意义在于如果我们最后的模型成功了,那么新的数据进来后也需要执行归一化,这就要求每次归一化的模型必须使用的一样,否则带入计算模型中则无法得到预期的值
# 2 计算模型,这个模型就是我们的线性回归模型,这个模型里面存放着针对归一化之后的数据计算操作的封装
# 使用固定的库进行保存模型
from sklearn.externals import joblib

joblib.dump(ss, './standard_to_lr_data_model.model')  # ss = StandardScaler() 标准化类:已经训练过的
joblib.dump(model_train, './lr_predict_model.model')  # 就是导入的训练对象model_train = LinearRegression(fit_intercept=True):已经训练过的

猜你喜欢

转载自blog.csdn.net/weixin_43959953/article/details/85071749
今日推荐