一: 线性回归方程
线性回归(英语:linear regression)是利用称为线性回归方程的最小二乘函数对一个或多个自变量和因变量
之间关系进行建模的一种回归分析。这种函数是一个或多个称为回归系数的模型参数的线性组合。只有一个自变量
的情况称为简单回归,大于一个自变量情况的叫做多元回归
在线性回归中,数据使用线性预测函数来建模,并且未知的模型参数也是通过数据来估计。这些模型被叫做
线性模型。最常用的线性回归建模是给定X值的y的条件均值是X的仿射函数。不太一般的情况,线性回归模型可以
是一个中位数或一些其他的给定X的条件下y的条件分布的分位数作为X的线性函数表示。像所有形式的回归分析一
样,线性回归也把焦点放在给定X值的y的条件概率分布,而不是X和y的联合概率分布(多元分析领域)。
线性回归有很多实际用途。分为以下两大类:
-
如果目标是预测或者映射,线性回归可以用来对观测数据集的和X的值拟合出一个预测模型。当完成这样一个
模型以后,对于一个新增的X值,在没有给定与它相配对的y的情况下,可以用这个拟合过的模型预测出一个y
值。
-
给定一个变量y和一些变量,这些变量有可能与y相关,线性回归分析可以用来量化y与Xj之
间相关性的强度,评估出与y不相关的,并识别出哪些的子集包含了关于y的冗余信息。
使用sklearn线性回归模型(jupyter)
这里我们以波士顿的房价数据来进行使用分析
(一): 导入sklearn
import numpy as np
# 线性回归,拟合方程,求解系数, 一次幂
# 线性方程:直来直去,不拐弯
from sklearn.linear_model import LinearRegression
# 导入数据集
from sklearn import datasets
# 导入数据分离的方法(获取数据后,一部分数据用来让回归模型学习,另一部分用来预测)
from sklearn.model_selection import train_test_split
(二): 获取波士顿房价数据
# 获取的数据是numpy,ndarray类型
data = datasets.load_boston()
# 该数据内有完整的影响房价的因素和完整的房价信息,本次实验就是将数据分为两部分, 一部分用来训练模型,另一部分用来预测,最后将预测出来的数据和已有的完整信息进行对比,判断该模型是否适用于这组房价数据
data # 查看data的数据结构
data.feature_names # 查看影响房价的属性名
# x是属性,特征,未知数
X = data['data']
X.shape # 运行结果是(506, 13), 506表示样本是506个, 每个样本采集了13个属性特征;13个属性,需要构建构建了13元一次方程
# y是房价的估值
y = data['target']
# !!!!!!!!!!
# X, y = datasets.load_boston(True) 获取到X, y的值和以上的一样
(三): 使用模型进行预测
X_train, X_test, y_train, y_test = train_test_split(X, y) # 将数据进行分离(默认是3:1); train_test_split(X, y)函数会随机打乱顺序
display(X_train.shape, X_test.shape) # (379, 13) ; (127, 13)
# 声明算法
linear = LinearRegression()
# 训练模型
linear.fit(X_train, y_train) # X_train, y_train是之前分离出来用来训练模型的数据
# 预测
y_ = linear.predict(X_test).round(1) # X_test是影响房价的因素,该预测模型能根据影响房价的因素预测剩余部分的房价
# 预估数据和实际数据比较
print(y_)
print(y_test)
经过估计数据和实际数据对比,说明算法模型适用于数据
(四): 自建方程预测数据 与 使用线性模型得到的数据对比
假设波士顿的房价数据符合线性回归的特性,则我们可以通过构建线性方程来预测波士顿剩余部分的房价信息
根据一次线性回归方程: 可推导得出: (有13个影响房
价的因素)
代码如下:
# 通过训练模型,可从模型中得出系数w
w_ = linear.coef_
# 通过训练模型,可从模型中得出截距b
b_ = linear.intercept_
# 自建方程
def fun(w_, b_, X):
return np.dot(X, w_)+b_
# 调用方程得到预估的房价信息
fun(w_, b_, X_test).round(1) # round(1)保留一位小数
'''
array([31.3, 13.4, 28.6, 20.5, 20.4, 19.4, 32.2, 24. , 25.8, 29.5, 24.5,
25.2, 31.9, 8.2, 20.9, 29.3, 22.3, 35.2, 16.4, 18.5, 30.8, 41.1,
16.2, 13.7, 17.7, 23.8, 7.8, 12. , 20.5, 15.3, 29.3, 26.8, 31.8,
26. , 30.4, 39.2, 25.3, 40.7, 11.6, 27.3, 16.7, 18.8, 19.5, 19.9,
20.7, 22.8, 17.4, 21.6, 23.3, 30. , 25.2, 23.7, 34.2, 18.2, 33.5,
16. , 28.3, 14.1, 24.2, 16.2, 16.7, 23.5, 16. , 21.4, 21.8, 28.2,
25.7, 31.2, 18.8, 26.4, 28.3, 21.9, 27.5, 27.1, 27.1, 15. , 26. ,
26.3, 13.2, 13.3, 26.1, 20.5, 16.8, 24.3, 36.6, 21.4, 8.3, 27.8,
3.6, 19.2, 27.5, 33.6, 28.4, 34.3, 28.2, 13.3, 18. , 23.5, 30.4,
32.9, 23.7, 30.5, 19.8, 19.5, 18.7, 30.9, 36.3, 8. , 18.2, 13.9,
15. , 26.4, 24. , 30.2, 20. , 5.6, 21.4, 22.9, 17.6, 32.8, 22.1,
32.6, 20.9, 19.3, 23.1, 21. , 21.5])
'''
# 使用sklesrn中的线性模型得到的预估房价信息
linear.predict(X_test).round(1)
'''
array([31.3, 13.4, 28.6, 20.5, 20.4, 19.4, 32.2, 24. , 25.8, 29.5, 24.5,
25.2, 31.9, 8.2, 20.9, 29.3, 22.3, 35.2, 16.4, 18.5, 30.8, 41.1,
16.2, 13.7, 17.7, 23.8, 7.8, 12. , 20.5, 15.3, 29.3, 26.8, 31.8,
26. , 30.4, 39.2, 25.3, 40.7, 11.6, 27.3, 16.7, 18.8, 19.5, 19.9,
20.7, 22.8, 17.4, 21.6, 23.3, 30. , 25.2, 23.7, 34.2, 18.2, 33.5,
16. , 28.3, 14.1, 24.2, 16.2, 16.7, 23.5, 16. , 21.4, 21.8, 28.2,
25.7, 31.2, 18.8, 26.4, 28.3, 21.9, 27.5, 27.1, 27.1, 15. , 26. ,
26.3, 13.2, 13.3, 26.1, 20.5, 16.8, 24.3, 36.6, 21.4, 8.3, 27.8,
3.6, 19.2, 27.5, 33.6, 28.4, 34.3, 28.2, 13.3, 18. , 23.5, 30.4,
32.9, 23.7, 30.5, 19.8, 19.5, 18.7, 30.9, 36.3, 8. , 18.2, 13.9,
15. , 26.4, 24. , 30.2, 20. , 5.6, 21.4, 22.9, 17.6, 32.8, 22.1,
32.6, 20.9, 19.3, 23.1, 21. , 21.5])
'''
通过自建模型获取预估数据与使用模型获取预估数据进行比较,两组数据完全一致;
(五): 使用线性回归,求解斜率和截距
- 根据最小二乘法: 推到得出公式:
以上公式只能求出w,我们可以先求出w再计算出b;
-
但此处我们有更简单的方法:
根据线性回归方程 我们可以将方程中的b看成是,
所以可得:
代码如下:
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn import datasets
X, y = datasets.load_boston(True)
linear = LinearRegression()
linear.fit(X,y)
w_ = linear.coef_
b_ = linear.intercept_
# 向X中插入一列全是1的数据(任何数的0次方都是1)
X = np.concatenate([X, np.ones(shape = (506, 1))], axis=1)
# 根据最小二乘法的推导公式:w和b的值为(最后一个值是b)
w = ((np.linalg.inv(X.T.dot(X))).dot(X.T)).dot(y)
# 以上w的写法过于装逼,所以分解为:
# A = X.T.dot(X) 求X和转置后的X的内积(公式中的XTX)
# B = np.linalg.inv(A) 求A的逆矩阵(公式中的-1次方)
# C = B.dot(X.T) 求以上矩阵和X的转置矩阵的内积(公式中的XT)
# w = C.dot(y) 与y求内积,得出w和b
'''
运行结果:
array([-1.08011358e-01, 4.64204584e-02, 2.05586264e-02, 2.68673382e+00,
-1.77666112e+01, 3.80986521e+00, 6.92224640e-04, -1.47556685e+00,
3.06049479e-01, -1.23345939e-02, -9.52747232e-01, 9.31168327e-03,
-5.24758378e-01, 3.64594884e+01])
'''
print(b_)
'''
运行结果:
36.45948838509001
'''
扩展一: 最小二乘法和向量范数
- 右上角的2是平方
- 右下角的2是向量2范数
- 竖线内的表达式是向量
根据最小二乘法的公式, 推导得出
向量的1-范数(表示各个元素的绝对值的和)
向量的2-范数(表示每个元素的平方和再开平方)
向量的无穷范数(所有向量元素绝对值中的最大值)
扩展二: 导数, 偏导数
导数:
对函数 求导得:
求导规则:
- 参数求导为0
- 参数乘变量求导为常数
- 变量的次方求导: 求导为
- 复合函数求导:
$$(x^2-x)^2$$求导: 先将括号看成一个整体求导, 结果再乘以括号内的求导结果
$$2(x^2-x)(2x-1)$$
偏导数:
有多个变量得函数求导:
对函数: 求导:
求导规则: 多变量函数只能针对某一个变量求导,此时将其他变量看成常数
将x看成常数a:
求导得:
故求导得:
实现线性回归的两种方式:
正规方程
梯度下降
二: 正规方程
(一): 损失函数
最小二乘法:
当X和y都是常数时,按照向量2范数将上面的最小二乘法解开:
将X,y替换成常数a,b
由于最小二乘法方程的函数值都是大雨或等于0的,所以此时得到一个开口向
上的抛物线(一元二次方程)
此时的就是损失函数,在此时求该函数的导数(抛物线函数顶点的导数为0)
就能得到该函数的最小值,也就是最小损失
此时即可算出最小的,即最小损失
(二): 矩阵常用求导公式
X的转置矩阵对X矩阵求导, 求解出来是单位矩阵
X的转置矩阵和一个常数矩阵相乘再对X矩阵求导, 求解出来就是改常数矩阵
(三): 正规方程矩阵推导过程
此时X,w,y都是矩阵
1: 公式化简
1: 最小二乘法:
2: 向量2范数:
3: 将向量2范数的公式带入到最小二乘法中得:
4. 化简:
由于X, w, y都是矩阵, 运算后还是矩阵; 矩阵得乘法是一个矩阵得行和另一个矩阵得列相乘; 所以矩阵的平方就是该矩阵乘以他本身的转置矩阵
5. 所以:
6. 展开:
注意: 整体转置变成每个元素都转置时,若是有乘法, 则相乘的两个矩阵要交换位置; 如下所示!!!
注意: 若想交换两个相乘的矩阵在算式中的位置,则交换之后双方都需要转置一次; 如下所示!!!
2: 求导
这里 是常数求导后为0
求导:
求导:
所以:
令,则:
矩阵运算没有除法,可以用逆矩阵实现除法的效果
等式两边同时乘以的逆矩阵
I是单位矩阵
得到正规方程:
(四): 数据挖掘实例(预测2020年淘宝双十一交易额)
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
X = np.arange(2009, 2020) # 年份
X = X -2008 # 年份数值太大,差别不明显
y = np.array([0.5, 9.36, 52, 191, 350, 571, 912, 1207, 1682, 2135, 2684]) # 09年到19年的交易额
假设X和y之间是一元三次的关系(按照前几年的数据走势提出的假设)
# X_oo = np.concatenate([a,a]) # 横着级联
X_train = np.c_[X**0, X**1, X**2, X**3] # 竖着级联
'''
array([[ 1, 1, 1, 1],
[ 1, 2, 4, 8],
[ 1, 3, 9, 27],
[ 1, 4, 16, 64],
[ 1, 5, 25, 125],
[ 1, 6, 36, 216],
[ 1, 7, 49, 343],
[ 1, 8, 64, 512],
[ 1, 9, 81, 729],
[ 1, 10, 100, 1000],
[ 1, 11, 121, 1331]], dtype=int32)
'''
linear = LinearRegression(fit_intercept=False) # 声明算法; fit_intercept=False将截距设置为0, w0就是截距
linear.fit(X_train, y) # 训练
w_ = linear.coef_
print(linear.coef_.round(2)) # 获取系数
print(linear.intercept_) # 获取截距
'''
[ 58.77 -84.06 27.95 0.13]
0.0
'''
可以得到方程:
X_test = np.linspace(0,12,126) # 线性分割(将0,12之间分成126分)等差数列包含1和12
X_test = np.c_[X_test**0, X_test**1, X_test**2, X_test**3] # 和训练数据保持一致
y_ = linear.predict(X_test) # 使用模型预测
plt.plot(np.linspace(0,12,126), y_, color='g') # 绘制预测方程曲线
plt.scatter(np.arange(1,12), y, color='red') # 绘制每年的真实销量
# 定义函数
fun = lambda x : w_[0] + w_[1]*x + w_[2]*x**2 + w_[-1]*x**3
fun(12)
'''3294.2775757576132'''
三: 梯度下降
梯度下降法的基本思想可以类比为一个下山的过程。假设这样一个场景:一个人被困在山上,需要从山上下
来(i.e. 找到山的最低点,也就是山谷)。但此时山上的浓雾很大,导致可视度很低。因此,下山的路径就无法确
定,他必须利用自己周围的信息去找到下山的路径。这个时候,他就可以利用梯度下降算法来帮助自己下山。具体
来说就是,以他当前的所处的位置为基准,寻找这个位置最陡峭的地方,然后朝着山的高度下降的地方走,同理,
如果我们的目标是上山,也就是爬到山顶,那么此时应该是朝着最陡峭的方向往上走。然后每走一段距离,都反复
采用同一个方法,最后就能成功的抵达山谷。
(一): 梯度下降
import numpy as np
import matplotlib.pyplot as plt
f = lambda w : (w -3.5)**2 -4.5*w +10
d = lambda w : 2*(w-3.5)-4.5 # 梯度 == 导数
step = 0.1 # 梯度下降的步幅,比率,学习率(默认是1)
# 求当w(x值)为多少时,得到的函数值最小
w = np.random.randint(0,11,size=1)[0] # 随机生成一个初始值数
last_w = w + 0.1 # 梯度下降,每走一步,目标值,都会更新
precision = 1e-4 # 精确率(误差率,越小越精确,并不是越小越好)
print('==============更新前的w:', w)
w_ = [w]
while True:
if np.abs(w - last_w) < precision: # 退出条件
break
last_w = w # 更新
w -= step*d(w)
# 随机初始值在目标值的左边时, d(w)为负, w = w-step*d(w) 会使得w慢慢变大趋近目标值
# 随机初始值在目标值的右边时, d(w)为正, w = w-step*d(w) 会使得w慢慢变小趋近目标值
w_.append(w)
print('==============更新后的w:', w)
(二): 梯度下降实现线性回归
1. 构造数据
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
X = np.linspace(-2, 12, 40).reshape(40,1)
# reshape(40,1)改变形状; 40这个位置可以写成-1, 此时-1代表40; 当后面的1变为2时,此时-1代表20; 也就是说当后面的数值变化时,系统自动用前面规定的总数计算出相应的结果,而这些结果都可以用-1代替;
w = np.random.randint(1,9,size = 1) # 系数
b = np.random.randint(-5,5,size = 1) # 截距
y = w*X + b + np.random.randn(40,1) * 2 # 增加噪声
plt.scatter(X, y) # 绘点
2. 使用sklearn中的回归 - 梯度下降方法计算斜率和截距
linear = LinearRegression() # 定义算法
linear.fit(X, y) # 训练
X_test = np.linspace(-2,12,256).reshape(-1, 1) # 预测数据
y_ = linear.predict(X_test) # 预测结果
print('真实斜率和截距', w, b)
print('算法计算的斜率和截距', linear.coef_, linear.intercept_) # 截距和斜率
'''
真实斜率和截距 [5] [3]
算法计算的斜率和截距 [[4.91521288]] [3.06426015]
'''
plt.plot(X_test, y_, color='g') # 绘制线性回归方程
plt.scatter(X, y, color='r') # 绘制实际的点
3.自定义梯度下降的类实现sklearn中的回归 - 梯度功能
class LinearModel(object):
def __init__(self): # 初始化, 随机给定截距和斜率
self.w = np.random.randn(1)[0]
self.b = np.random.randn(1)[0]
def model(self, x):
return self.w*x + self.b # 一元一次线性方程; 模型
def loss(self,x,y):#损失,最小二乘法
cost = (self.model(x) - y)**2 # 损失函数越小越好
# 求解梯度,两个未知数,所以,偏导
d_w = 2*(self.model(x) - y)*x # 斜率w的偏导
d_b = 2*(self.model(x) - y)*1 # 截距b的偏导
return cost,d_w,d_b
def gradient_descent(self, step, d_w, d_b): # 梯度下降
self.w -= step*d_w # 更新斜率
self.b -= step*d_b # 更新截距
def fit(self, X, y): # 训练模型, 将数据给模型,寻找规律
precision = 1e-4 # 精确度
last_w = self.w + 0.01
last_b = self.b + 0.01
print('------------------------初始的截距和斜率:', self.w, self.b)
while True:
if (np.abs(self.w-last_w) < precision) & (np.abs(self.b-last_b) < precision):
break
last_w = self.w # 更新之前,先保留记录
last_b = self.b
cost_ = 0
dw_ = 0
db_ = 0
for i in range(40): # 计算40个,返回40个偏导数,求平均值
cost,dw,db = self.loss(X[i,0],y[i,0])
cost_ += cost/40
dw_ += dw/40
db_ += db/40
self.gradient_descent(0.01, dw_, db_)
print('------------------------更新后的截距和斜率:', self.w, self.b)
def predict(self, X):
return self.model(X)
model = LinearModel() # 定义算法
w, b = model.fit(X, y) # 获取斜率和截距
y_ = model.predict(X_test) # 用测试数据获取目标值
plt.plot(X_test, y_, color='g') # 绘制线性方程
plt.scatter(X, y, color='r') # 绘制目标点
(三): 随机梯度下降
1: 公式推导(矩阵)
损失函数:
表示m个样本求平均:
X, y都是矩阵,去掉求和符号计算:
梯度下降的更新规则:
2: 实现梯度下降类(矩阵)
import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(-2, 12, 40).reshape(-1, 1)
w = np.random.randint(2, 12, size=1)
b = np.random.randint(-10, 10, size=1)
y = X*w + b + np.random.randn(40, 1)*2.5
plt.scatter(X, y, color='r')
X = np.concatenate([X, np.ones(shape=(40, 1))], axis=1)
def gradient_descent(X,y):
theta = np.random.randn(2,1) # theta中既有斜率,又有截距
last_theta = theta + 0.1
precision = 1e-4
epsilon = 0.01
while True:
# 当斜率和截距误差小于万分之一时,退出
if (np.abs(theta - last_theta) < precision).all():
break
# 更新
last_theta = theta.copy()
theta = theta - epsilon*2/40*X.T.dot(X.dot(theta) - y)
return theta
w_,b_ = gradient_descent(X,y)
j = lambda x : w_*x + b_
plt.scatter(X[:,0],y,color = 'red')
x_test = np.linspace(-2,12,1024)
y_ = j(x_test)
plt.plot(x_test,y_,color = 'green')
(四): 梯度下降法大家族(BGD,SGD,MBGD)
1: 批量梯度下降法(Batch Gradient Descent)
批量梯度下降法,是梯度下降法最常用的形式,具体做法也就是在更新参数时使用所有的样本来进行更新。
由于我们有m个样本,这里求梯度的时候就用了所有m个样本的梯度数据。
2 随机梯度下降法(Stochastic Gradient Descent)
随机梯度下降法,其实和批量梯度下降法原理类似,区别在与求梯度时没有用所有的m个样本的数据,而是仅仅选取一个样本j来求梯度。对应的更新公式是:
3 小批量梯度下降法(Mini-batch Gradient Descent)
小批量梯度下降法是批量梯度下降法和随机梯度下降法的折衷,也就是对于m个样本,我们采用x个样子来迭代,1