预测算法-线性回归(鲍鱼年龄预测)

预测算法-线性回归

面对可逆矩阵

线性回归(模型,策略,算法)
模型: 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
转化为: (yixTiw)T(yixTiw) ( 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(xix)22k2 w ( i ) = exp ( x i − x ) 2 − 2 k 2

构建了一个只含有对角元素的权重矩阵 W W , 并且点 xx(i),w(i),k,,使LWLR x 与 x ( i ) 越 近 , w ( i ) 将 会 越 大 , 上 述 公 式 中 包 含 一 个 需 要 用 户 指 定 的 参 数 k , 决 定 了 对 附 近 的 点 赋 予 多 大 的 权 重 , 这 也 是 使 用 L W L R 时 唯 一 需 要 考 虑 的 参 数

基本工作原理:

  • 读入数据,将数据特征x、特征标签y存储在矩阵x、y中
  • 利用高斯核构造一个权重矩阵 W,对预测点附近的点施加权重
  • 验证 X^TWX 矩阵是否可逆
  • 使用最小二乘法求得 回归系数 w 的最佳估计

使用回归,可以在给定输入的时候预测出一个数值,这是对分类方法的提升,因为这样可以预测连续型数据而不仅仅是离散的类别标签

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 求 逆
Inn()线1,0 其 中 矩 阵 I 是 一 个 n ∗ n ( 等 于 列 数 ) 单 位 矩 阵 对 角 线 上 元 素 全 为 1 , 其 他 元 素 全 为 0
此时回归系数的计算公式变为: w^=(XTX+λI)1XTy w ^ = ( X T X + λ I ) − 1 X T y

岭回归最先用来处理特征数多于样本数的情况, 现在也用于在估计中加入偏差, 从而得到更好的估计.
这里通过引入 λw λ 来 限 制 所 有 w 之 和 ,通过引入该惩罚项 能够减少不重要的参数 这种技术在统计学中叫做缩减

原理:

  • (1)对于有些矩阵, 矩阵中某个元素的一个很小的变动,会引起最后计算结果误差很大,这种矩阵称为”病态矩阵”
    有些时候不正确的计算方法也会使得一个正常的矩阵在运算中表现出病态, 对于高斯消去法来说,如果对角线上的元素很小,
    计算时就会表现出病态的特征
  • (2)回归分析中,最小二乘法是一种无偏估计, 在运算中往往是列满秩的运算
    即: θx=y θ x = y
    此时最小化损失函数为: i(yθx)2 ∑ i ( y − θ x ) 2
  • (3)若x不是列满秩时或者某些列之间的线性相关关系比较大的时候, XTX X T X 接近于奇异 (XTX)1 ( X T X ) − 1 误差变大
    为了解决损失函数误差过大的情况,只要将不适当问题转化为适当问题(给损失函数加上一个正则化项):
    ||Xθy||2+||γθ||2 | | X θ − y | | 2 + | | γ θ | | 2
  • (4)于是得出 θ(α)=(XTX+αI)1XTy θ ( α ) = ( X T X + α I ) − 1 X T y

原理小结:
α,θ(α)θ(α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()

套索方法(lasso)

增加如下约束时, 普通的最小二乘法回归会得到与岭回归一样的公式
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()  # 前向逐步算法 鲍鱼年龄预测项目

参考文献
《机器学习实战》

你可能感兴趣的:(MachineLearning)