线性回归(模型,策略,算法)
模型: h(x)=WTx+b h ( x ) = W T x + b
损失函数: J(θ)=∑i=1N(f(xi)−yi)2 J ( θ ) = ∑ i = 1 N ( f ( x i ) − y i ) 2
目标函数为: minJ(θ)=∑i=1N(f(xi)−yi)2 min J ( θ ) = ∑ i = 1 N ( f ( x i ) − y i ) 2
方法1:梯度下降法
θ=θ−α▽J(θ) θ = θ − α ▽ J ( θ )
α:学习速率 α : 学 习 速 率
▽J(θ):偏导数学习的方向 ▽ J ( θ ) : 偏 导 数 学 习 的 方 向
方法2:标准方程法
目标函数为: minJ(θ)=∑i=1N(f(xi)−yi)2 min J ( θ ) = ∑ i = 1 N ( f ( x i ) − y i ) 2
转化为: (yi−xTiw)T(yi−xTiw) ( y i − x i T w ) T ( y i − x i T w )
对w求导:w^=(xTx)−1xTy 对 w 求 导 : w ^ = ( x T x ) − 1 x T y
xTx x T x 的行列式不为0时,存在逆矩阵 那么条件成立, 才能计算 w^模型参数w w ^ 模 型 参 数 w
xTx x T x 的行列式为0时, 不存在逆矩阵
此时线性回归参数 w^=(xTx)−1xTy w ^ = ( x T x ) − 1 x T y
算法优劣:
import numpy as np
import matplotlib.pyplot as plt
def loadData(filename):
fr = open(filename)
featureNum = len(fr.readline().strip().split('\t')) - 1
dataSet = list()
labelMat = list()
for lines in fr.readlines():
lineArr = list()
lines = lines.split('\t')
for i in range(featureNum):
lineArr.append(float(lines[i]))
dataSet.append(lineArr)
labelMat.append(float(lines[-1]))
# print(dataSet)
# print(labelMat)
return dataSet, labelMat
def lr(dataSet, labelMat):
"""
标准方程法解线性回归
:param dataSet: 样本数据集 特征向量 X
:param labelMat: 样本数据集 输入变量 Y
:return
w 模型系数
"""
xMat = np.mat(dataSet)
# print(xMat)
yMat = np.mat(labelMat).T
# print(yMat)
xMatx = xMat.T * xMat
if np.linalg.det(xMatx) == 0:
print('行列式为0 为不可逆矩阵')
return None
w = xMatx.I * xMat.T*yMat
# print(w)
"""
[[3.00681047]
[1.69667188]]
"""
return w
def lr_stand(dataSet, labelMat):
"""
标准方程法解线性回归
:param dataSet: 样本数据集 特征向量 X
:param labelMat: 样本数据集 输入变量 Y
:return
w 模型系数
"""
dataSet, labelMat = regularize(dataSet, labelMat)
xMat = np.mat(dataSet)
# print(xMat)
yMat = np.mat(labelMat)
# print(yMat)
xMatx = xMat.T * xMat
if np.linalg.det(xMatx) == 0:
print('行列式为0 为不可逆矩阵')
return None
w = xMatx.I * xMat.T*yMat
# print(w)
"""
[[3.00681047]
[1.69667188]]
"""
return w
def lr_plot(dataSet, labelMat, w):
x = list()
for i in dataSet:
x.append(i[-1])
y = labelMat
"""失败尝试"""
# w = np.mat(w).T
# dataSet = np.mat(dataSet).T
# print(np.shape(w))
# print(np.shape(dataSet))
# print(np.array(w * dataSet))
# for i in (w * dataSet):
# print('i:', i)
# fit_y = [float(i) for i in np.array(w * dataSet)]
fit_y = list()
for i in dataSet:
fit_y.append(float(np.array(i*w)))
print(np.shape(fit_y))
print('fit_y', fit_y)
fig = plt.subplot(111)
print(np.shape(x))
print(np.shape(y))
rSqure = calcR(y, fit_y)
fig.scatter(x, y, s=12, c='b', alpha=0.5, edgecolors=None, marker='o')
fig.plot(x, fit_y, c='r')
plt.title(rSqure)
plt.show()
解决问题: 由于是对最小方差的无偏估计 往往得出的结果是欠拟合的
解决方法: 如果模型欠拟合将不能取得最好的预测效果 那么久在估计中引入一些偏差 从而降低预测的均方误差
一个方法就是局部加权线性回归(locally weighted linear regression–LWLR)
算法思想:
给预测点附近的每个点赋予一点的权重 然后与线性回归相似
在这个子集上基于最小均方误差来进行普通的回归
需要最小化的目标函数大致为: ∑iw(y(i)−y^(i))2 ∑ i w ( y ( i ) − y ^ ( i ) ) 2
目标函数中w为权重 而不是回归系数
该算法每次预测均需要事先选取出对应的数据子集
即: w^=(XTWX)−1XTWy w ^ = ( X T W X ) − 1 X T W y
其中W是一个矩阵,用来给每个预测数据点赋予权重,w^为回归系数 其 中 W 是 一 个 矩 阵 , 用 来 给 每 个 预 测 数 据 点 赋 予 权 重 , w ^ 为 回 归 系 数
LWLR使用’核’来对附近的点赋予更高的权重
核的类型可以自由选择, 最常见的核就是高斯核
w(i)=exp(xi−x)2−2k2 w ( i ) = exp ( x i − x ) 2 − 2 k 2
构建了一个只含有对角元素的权重矩阵 W W , 并且点 x与x(i)越近,w(i)将会越大,上述公式中包含一个需要用户指定的参数k,决定了对附近的点赋予多大的权重,这也是使用LWLR时唯一需要考虑的参数 x 与 x ( i ) 越 近 , w ( i ) 将 会 越 大 , 上 述 公 式 中 包 含 一 个 需 要 用 户 指 定 的 参 数 k , 决 定 了 对 附 近 的 点 赋 予 多 大 的 权 重 , 这 也 是 使 用 L W L R 时 唯 一 需 要 考 虑 的 参 数
基本工作原理:
使用回归,可以在给定输入的时候预测出一个数值,这是对分类方法的提升,因为这样可以预测连续型数据而不仅仅是离散的类别标签
def lwlr(weightPoint, dataSet, labelMat, k):
"""
局部加权线性回归 lwlr
:param weightPoint: 预测数据点 给预测数据点赋予权重
:param dataSet: 样本数据集 输入空间 X
:param labelMat: 样本数据集 输出空间 Y
:return
hat_w
"""
m, n = np.shape(dataSet)
w = np.mat(np.eye(m))
# 检查是否可逆
xMat = np.mat(dataSet)
yMat = np.mat(labelMat).T
for i in range(m):
diff = weightPoint - dataSet[i, :]
w[i, i] = np.exp((diff*diff.T)/(-2*k**2))
xTx = xMat.T * (w * xMat)
if np.linalg.det(xTx) == 0:
print('行列式为0 该矩阵不可逆')
return None
# print(np.shape(hat_w))
ws = xTx.I * (xMat.T * (w * yMat))
return weightPoint * ws
def lwlrTest(testSet, dataSet, labelMat, k):
"""
局部加权线性回归 返回fit_y 测试结果
:param testSet: 预测数据集
:param dataSet: 样本数据集 输入空间
:param labelMat: 样本数据集 输出空间
:param k: 权重计算 高斯核系数
:return
fit_y
"""
dataSet = np.mat(dataSet)
m, n = np.shape(dataSet)
fit_y = np.zeros(m)
# fit_y = list()
for i in range(m):
# print(dataSet[i, :])
# fit_y[i] = copy_lwlr(testSet[i], dataSet, labelMat, k)
fit_y[i] = lwlr(testSet[i], dataSet, labelMat, k)
# print('局部线性加权回归-(fit_y):', fit_y)
print('局部线性加权回归-(fit_y):', np.shape(fit_y))
return fit_y
def lwlr_plot(dataSet, labelMat, fit_y):
"""
局部加权线性回归图像
"""
yHat = fit_y
xMat = np.mat(dataSet)
srtInd = xMat[:, 1].argsort(0) # argsort()函数是将x中的元素从小到大排列,提取其对应的index(索引),然后输出
xSort = xMat[srtInd][:, 0, :]
# print('xSort', xSort)
fig = plt.figure()
ax = fig.add_subplot(111)
print(yHat[srtInd]) # 从小到大排序
ax.plot(xSort[:, 1], yHat[srtInd])
ax.scatter(xMat[:, 1].flatten().A[0], np.mat(labelMat).T.flatten().A[0], s=2, c='red')
plt.show()
拓展
def calcR(y, fit_y):
"""
计算R的平方 R^2 = 回归平方和 - 总平方和
总平方和 = \sum (y的实际值 - 平均值) ^ 2
回归平方和 = 总平方和 - 残差平方和
残差平方和 = \sum (y的估计值 - 实际值) ^ 2
:param y: 实际值
:param fit_y: 估计值
:return
R^2 决定系数 表示回归关系可以解释应变量80%的变异
"""
y = np.mat(y)
fit = np.mat(fit_y)
yMean = np.mean(y)
# print(yMean)
# print(y - yMean)
# print(np.sum(np.power((y-yMean), 2)))
sumSqu = np.sum(np.power((y-yMean), 2))
# print('总平方和', sumSqu)
residual_squareSum = np.sum(np.power((fit_y - y), 2))
# print('残差平方和', residual_squareSum)
rSqure = (sumSqu - residual_squareSum) / sumSqu
print(rSqure)
print('R^2 %.2f %%' % (rSqure*100))
return rSqure
def rss_error(labelMat, fit_y):
"""平方误差和: 实际值与预测值之差"""
print('平方误差和:', np.sum(np.power((np.mat(labelMat) - fit_y), 2)))
return np.sum(np.power((np.mat(labelMat) - fit_y), 2))
def regularize(dataSet, labelMat):
"""
按列标准化数据
:param dataSet:
:param labelMat:
:return:
"""
dataSet = np.mat(dataSet)
labelMat = np.mat(labelMat).T
xMean = np.mean(dataSet, 0)
yMean = np.mean(labelMat, 0)
xVar = np.var(dataSet, 0)
dataSet = (dataSet - xMean) / xVar
labelMat = labelMat - yMean
return dataSet, labelMat
问题:使用标准方程法求解线性回归时, 需要满足条件 (XTX)−1,即(XTX)行列式不为0,为0时无法计算 ( X T X ) − 1 , 即 ( X T X ) 行 列 式 不 为 0 , 为 0 时 无 法 计 算
问题进一步推导: 如果当数据集中特征比样本点还多的时候,说明输入数据的矩阵x不是满秩矩阵,非满秩矩阵求逆会出问题 如 果 当 数 据 集 中 特 征 比 样 本 点 还 多 的 时 候 , 说 明 输 入 数 据 的 矩 阵 x 不 是 满 秩 矩 阵 , 非 满 秩 矩 阵 求 逆 会 出 问 题
解决方案: 引入岭回归 lasso法 前向逐步回归
简述:岭回归就是在矩阵 XTX X T X 上加入一个 λI从而使得矩阵非奇异,进而能对XTX+λI求逆 λ I 从 而 使 得 矩 阵 非 奇 异 , 进 而 能 对 X T X + λ I 求 逆
其中矩阵I是一个n∗n(等于列数)单位矩阵对角线上元素全为1,其他元素全为0 其 中 矩 阵 I 是 一 个 n ∗ n ( 等 于 列 数 ) 单 位 矩 阵 对 角 线 上 元 素 全 为 1 , 其 他 元 素 全 为 0
此时回归系数的计算公式变为: w^=(XTX+λI)−1XTy w ^ = ( X T X + λ I ) − 1 X T y
岭回归最先用来处理特征数多于样本数的情况, 现在也用于在估计中加入偏差, 从而得到更好的估计.
这里通过引入 λ来限制所有w之和 λ 来 限 制 所 有 w 之 和 ,通过引入该惩罚项 能够减少不重要的参数 这种技术在统计学中叫做缩减
原理:
原理小结:
随着α的增大,θ(α)各元素θ(αi)的绝对值趋向于不断变小,相对于正确值θi的偏差也越来越大,α趋于无穷大时,θ(α)趋向0,其中,θ(α)随着α的改变而变化的轨迹,称为岭迹 随 着 α 的 增 大 , θ ( α ) 各 元 素 θ ( α i ) 的 绝 对 值 趋 向 于 不 断 变 小 , 相 对 于 正 确 值 θ i 的 偏 差 也 越 来 越 大 , α 趋 于 无 穷 大 时 , θ ( α ) 趋 向 0 , 其 中 , θ ( α ) 随 着 α 的 改 变 而 变 化 的 轨 迹 , 称 为 岭 迹
实质上: 岭回归是对最小二乘回归的一种补充,损失了无偏性,以换取高的数值稳定性,从而得到较高的计算精度
def ridge_regression(dataSet, labelMat, lamb=10):
"""
岭回归 求解模型参数
优缺点: 损失了无偏性,得到较高的计算精度
:param dataSet: 数据集 输入空间 X
:param labelMat:数据集 输出空间 Y
:param lamb: lambda 系数
:return
w 模型参数
"""
xMat = np.mat(dataSet)
yMat = np.mat(labelMat)
yMat = np.mat(labelMat).T
i = np.eye(np.shape(dataSet)[1])
demo = xMat.T * xMat + lamb * i
if np.linalg.det(demo) == 0:
print('行列式为0 无法计算逆矩阵')
ws = demo.I * (xMat.T * yMat)
# print('ws---', np.shape(ws))
return ws
def ridge_test(dataSet, labelMat):
"""
转化数据集为均值为0,方差为1的数据集
:param dataSet: 输入空间
:param labelMat: 输出空间
:param lamb: lambda 系数
:return
w_list 模型参数集合
"""
# dataSet = np.mat(dataSet)
# labelMat = np.mat(labelMat).T
# # 计算均值
# xMean = np.mean(dataSet, 0)
# yMean = np.mean(labelMat, 0)
#
# # print(xMean)
# # print(yMean)
# xVar = np.var(dataSet, 0)
# dataSet = (dataSet - xMean) / xVar
# labelMat = labelMat - yMean
dataSet, labelMat = regularize(dataSet, labelMat)
# print('dataSet', dataSet)
# print('labelMat', labelMat)
lamb = 30
wMat = np.zeros((lamb, np.shape(dataSet)[1]))
# print('wMat', np.shape(wMat))
for i in range(lamb):
ws = ridge_regression(dataSet, labelMat.T, lamb=np.exp(i-10))
wMat[i, :] = ws.T
return wMat
def ridge_regress_plot(wMat):
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(wMat)
plt.show()
增加如下约束时, 普通的最小二乘法回归会得到与岭回归一样的公式
∑k=1nw2k≤λ ∑ k = 1 n w k 2 ≤ λ
上式限定了所有回归系数的平方和不能大于 λ λ , 使用普通的最小二乘法回归当在两个或更多的特征相关时,可能会得到一个很大的正系数和一个很大的负系数,使用岭回归可以避免这个问题
lasso 缩减
∑k=1n|wk|≤λ ∑ k = 1 n | w k | ≤ λ
实质上是与岭回归是差不多的,不同点在于某些系数会变为0
前向逐步回归可以得到与lasso差不多的效果,但是更加简单,属于贪心算法
算法简述: 首先, 所有权重都设置为0,然后每一步所做的决策是对某个权重增加或减少一个很小的值
伪代码如下:
数据标准化,使其分布满足 0 均值 和单位方差
在每轮迭代过程中:
设置当前最小误差 lowestError 为正无穷
对每个特征:
增大或缩小:
改变一个系数得到一个新的 w
计算新 w 下的误差
如果误差 Error 小于当前最小误差 lowestError: 设置 Wbest 等于当前的 W
将 W 设置为新的 Wbest
def stepwise_regression(dataSet, labelMat, alpha, numCycle):
"""
前向逐步算法
伪代码
1.数据标准化 分布满足0均值 方差为1
2.对每次迭代过程
3.设置当前最小误差 lowestError 为无穷大
4.对每个特征
5.增大或者缩小
6. 改变一个系数得到一个新的w
计算新w下的误差
如果误差小于当前最小误差, 设置为wbest
将w设置为新的wbest
:param dataSet: 数据集 输入空间
:param labelMat:数据集 输出空间
:param alpha: 学习速率
:param numCycle: 最大迭代次数
:return
wMat 每次的wbest组成的列表
"""
dataSet = np.mat(dataSet)
labelMat = np.mat(labelMat).T
xMean = np.mean(dataSet, 0)
yMean = np.mean(labelMat, 0)
xVar = np.var(dataSet, 0)
dataSet = (dataSet - xMean) / xVar
labelMat = labelMat - yMean
print(np.shape(dataSet)) # (4176, 8)
print(np.shape(labelMat)) # (4176, 1)
feature_num = np.shape(dataSet)[1]
print(feature_num) # 8
w = np.zeros((1, feature_num))
w_test = w.copy()
wbest = None
wMat = np.zeros((numCycle, feature_num))
print(np.shape(w)) # (1, 8)
for cycle in range(numCycle):
lowestError = np.inf
for i in range(feature_num):
for sign in [-1, 1]:
w_test = w.copy()
w_test[:, i] += alpha * sign
error = np.sum(np.power((dataSet * w_test.T - labelMat), 2))
if error < lowestError:
lowestError = error
wbest = w_test
w = wbest.copy()
wMat[cycle, :] = wbest
# print(wMat)
return wMat
def load_abalone_data(filename):
fr = open(filename)
data_Set = list()
label_mat = list()
featureNum = int(len(fr.readline().strip().split('\t'))-1)
for lines in fr.readlines():
line = lines.strip().split('\t')
# print(line)
lineArr = list()
for index in range(featureNum):
# print('index', index)
lineArr.append(float(line[index]))
data_Set.append(lineArr)
label_mat.append(float(line[-1]))
# print('xxxxx')
# print(data_Set)
return data_Set, label_mat
def abalone_stepwise_regression():
filename = 'abalone.txt'
dataSet, labelMat = load_abalone_data(filename)
result_stepwise = stepwise_regression(dataSet, labelMat, alpha=0.01, numCycle=200)
print('result', result_stepwise[-1, :])
# dataSet, labelMat = regularize(dataSet, labelMat)
result_lr = lr_stand(dataSet, labelMat)
print('result_lr:', result_lr.T)
def abalone_predict_ridge_regress():
filename = 'abalone.txt'
dataSet, labelMat = load_abalone_data(filename)
# w = ridge_regression(dataSet, labelMat)
# print(w)
# lr_plot(dataSet, labelMat, w)
wMat = ridge_test(dataSet, labelMat)
print('wMat,', wMat)
ridge_regress_plot(wMat)
def abalone_predict_project():
filename = 'abalone.txt'
# fr = open(filename)
dataSet, labelMat = load_abalone_data("abalone.txt")
# print('abX,', np.shape(dataSet), type(labelMat[0][0]))
# print('abY,', np.shape(dataSet), type(labelMat[0]))
"""
abX, (4177, 8)
abY, (4177,)
"""
# 使用不同的核进行预测
fit_y01 = lwlrTest(dataSet[0:99], dataSet[0:99], labelMat[0:99], 0.1)
fit_y1 = lwlrTest(dataSet[0:99], dataSet[0:99], labelMat[0:99], 1)
fit_y10 = lwlrTest(dataSet[0:99], dataSet[0:99], labelMat[0:99], 10)
# 打印出不同的核预测值与训练数据集上的真实值之间的误差大小
error01 = rss_error(labelMat[0:99], fit_y01)
error1 = rss_error(labelMat[0:99], fit_y1)
error10 = rss_error(labelMat[0:99], fit_y10)
# 打印出不同的核预测值与训练数据集上的r^2 拟合度
r_square01 = calcR(labelMat[0:99], fit_y01)
r_square1 = calcR(labelMat[0:99], fit_y1)
r_square10 = calcR(labelMat[0:99], fit_y10)
# 打印出 不同的核预测值 与 新数据集(测试数据集)上的真实值之间的误差大小
new_fit_y01 = lwlrTest(dataSet[100:199], dataSet[0:99], labelMat[0:99], 0.1)
new_fit_y1 = lwlrTest(dataSet[100:199], dataSet[0:99], labelMat[0:99], 1)
new_fit_y10 = lwlrTest(dataSet[100:199], dataSet[0:99], labelMat[0:99], 10)
new_error01 = rss_error(labelMat[100:199], new_fit_y01)
new_error1 = rss_error(labelMat[100:199], new_fit_y1)
new_error10 = rss_error(labelMat[100:199], new_fit_y10)
def main():
filename = 'data.txt'
dataSet, labelMat = loadData(filename)
# w = lr(dataSet, labelMat)
# lr_plot(dataSet, labelMat, w)
# fit_y = lwlrTest(dataSet, dataSet, labelMat, k=1)
# fit_y = lwlrTest(dataSet, dataSet, labelMat, k=0.01)
fit_y = lwlrTest(dataSet, dataSet, labelMat, k=0.03)
lwlr_plot(dataSet, labelMat, fit_y)
error = rss_error(labelMat, fit_y)
# 查看平方误差和
print('error', error) # 0.03 0.0678212359601 # 0.01 1.16751327518 # 1 1.3520374286
r_square = calcR(labelMat, fit_y)
# 查看r^2 相关系数
print('r^2: %.2f %%' % (r_square*100)) # 0.03 99.45 % # 0.01 99.70 % # 1 97.30 %
if __name__ == '__main__':
# main() # 线性回归与局部线性回归
# abalone_predict_project() # 局部线性回归 鲍鱼年龄预测项目
# abalone_predict_ridge_regress() # 岭回归 鲍鱼年龄预测项目
abalone_stepwise_regression() # 前向逐步算法 鲍鱼年龄预测项目
参考文献
《机器学习实战》