[机器学习技法一]线性回归 Linear regression

359 阅读5分钟

按照之前的约定每周更新一节机器学习技法,今天是8月3日(发布的时候已经5号了),开始更新机器学习基本技法中的线性模型,也是所有模型中最容易实现的模型。

虽然线性模型原理简单,但是经过变换可以成为功能强大的非线性模型,并且其中包含很多重要的思想,话不多说首先介绍线性模型的基本原理。为了节省时间文中的公式直接使用了周志华老师的机器学习这本书中的介绍。

  • 原理部分

    首先是都熟知的机器学习一般函数形式:

经过学习得到w和b之后就能对未知的x进行预测,那么如何确定公式中的w和b呢?常用的方法就是使用测试集的均方误差来进行评估,如果均方误差越小的话就意味着w和b越可能接近真实值。

那么怎么使得均方误差最小呢?很常见的思路就是分别对公式中的w和b求偏导,同时令偏导等于零这样我们就能得到最优的闭式解,于是就有了下面的这种形式:

经过这样的学习我们就能学得当前条件下最好的w和b。但是前提是我们的目标对象是凸函数。

得到这样的结果之后我们希望能够将这个公式扩展到向量形式,因为这样我们的计算往往会更加清晰。

首先将给x添加一个bias,目的是将b融合到w当中去,形式如下:

这时候我们可以把均方误差的表达式写成这个样子:

其实这里就可以看作是最小二乘法啦。

之后同样的对均方误差求偏导,得到这个结果:

之后同样的令偏导等于零得到 ,但是前提条件是XTX需要是满秩矩阵或者正定矩阵才能得到下面这个封闭解的表达式。

  • 代码实例:

    这里使用梯度下降的方法来求解线性模型的最优解,基本思路如下:

    1. 建立模型函数

    2. 初始化参数

    3. 设置阈值更新变量

那么首先是第一步,建立模型函数:

# 使用函数计算当前的均方误差以及当前的导数,希望最后能够返回一个loss的list以及
# 当前的预测值y_hat和导数
def linera_function(x, y, w, b):
    n,m=x.shape
    # 计算预测值y
    y_hat = np.dot(x, w)+b
    # 计算均方误差
    mes = np.sum((y-y_hat)**2)/n
    # 计算dw
    dw = (np.dot(x.T,y_hat-y))/n
    db = np.sum(y_hat-y)/n
    return y_hat, mes, dw, db

之后是初始化w和b的函数

# 初始化w和b
def initial_para(m):
    b= 0
    w = np.zeros((m,1))
    return w, b

之后设置阈值同时更新参数

# 设置阈值同时更新参数
def updata_para(x, y, iteration=10, learning_rate=0.1):
    n,m= x.shape
    w, b = initial_para(m)
    list_loss = []
    for i in range(1, iteration):
        y_hat, mes, dw, db = linera_function(x, y, w, b)
        list_loss.append(mes)
        w -= dw
        b -= db
        if i %100 == 0:
            print('epoch %d equal %f' % (i, mes))
        
        para = {'w':w, 'b':b}
        grad = {'dw':dw, 'db':db}
    return y_hat, para, grad, list_loss

这样线性回归的基本形式就写出来了,之后利用sklearn中的diabetes数据来做一个验证

from sklearn.datasets import load_diabetes
from sklearn.utils import shuffle
from sklearn import model_selection
import matplotlib.pyplot as plt

diabetes = load_diabetes()
data = diabetes.data
target = diabetes.target
# 划分数据集
x_train, x_test, y_train, y_test = model_selection.train_test_split(data, target,test_size = 0.2)
y_train = y_train.reshape((-1, 1))
y_test = y_test.reshape((-1, 1))
y_hat, para, grad, list_loss = updata_para(x_train, y_train, iteration=1000)
# 绘制图像
f = x_test.dot(para['w'])+para['b']
fig, ax = plt.subplots(1, figsize=(8,6))
ax.scatter(range(x_test.shape[0]), y_test)
ax.plot(range(x_test.shape[0]), f, color = 'red')

最终得到的结果是这样的

预测效果应该说是一般吧但是这是最简单的线性回归模型还是可以看初已经有了基本的样子了。

那么现在我们对上面的这个模型做一个类的封装,其中使用了sklearn中的kFold来实现n折交叉验证:

import numpy as  np
from sklearn.datasets import load_diabetes
from sklearn.utils import shuffle
from sklearn import model_selection
import matplotlib.pyplot as plt

class Linear_regression():
    def __init__(self,test_size = 0.2, iteration = 1000):
        self.test_size = test_size
        self.iteration = iteration

    def linera_function(self, x, y):
        n,m=x.shape
        # 计算预测值y
        y_hat = np.dot(x, self.w)+self.b
        # 计算均方误差
        mes = np.sum((y-y_hat)**2)/n
        # 计算dw
        dw = (np.dot(x.T,y_hat-y))/n
        db = np.sum(y_hat-y)/n
        return y_hat, mes, dw, db

    def initial_para(self, m):
        self.b= 0
        self.w = np.zeros((m,1))

    # 设置阈值同时更新参数
    def update_para(self,x, y, iteration=1000, learning_rate=0.01):
        self.n,self.m= x.shape
        self.initial_para(self.m)
        list_loss = []
        for i in range(1, iteration):
            y_hat, mes, dw, db = self.linera_function(x, y)
            list_loss.append(mes)
            self.w -= dw
            self.b -= db
            if i %100 == 0:
                print('epoch %d equal %f' % (i, mes))
            
            para = {'w':self.w, 'b':self.b}
            grad = {'dw':dw, 'db':db}
        return y_hat, para, grad, list_loss

    def k_fold(self, data, k=3):
        kf = model_selection.KFold(n_splits=k)
        for train, target in kf.split(data):
            yield train, target

    def predict(self, x_test, y_test):
        y_pred = np.dot(x_test, self.w)+self.b
        return y_pred
    


if __name__ == "__main__":
    diabetes = load_diabetes()
    data = diabetes.data
    target = diabetes.target
    testlinear = Linear_regression()
    mes_list = []
    fig = plt.figure(figsize=(10,10))
	count = 1
    for train, validation in testlinear.k_fold(data):
        x_train = data[train]
        y_train = target[train].reshape((-1,1))
        x_target = data[validation]
        y_target = target[validation].reshape((-1, 1))
        # print('x_train.shape=',x_train.shape)
        y_hat, para, grad, list_loss = testlinear.update_para(x_train, y_train)
        y_pred = testlinear.predict(x_target, y_target)
        print('k-fold mes is', list_loss[-1])
        error = np.sum((y_pred-y_target)**2)/(len(x_target))
        print('k-fold predict mes is', error)
        ax = fig.add_subplot(2,2,count)
    	plt.scatter(range(x_target.shape[0]), y_target)
    	plt.plot(np.arange(x_target.shape[0]), y_pred,color='orange')
    	count += 1

最后可以得出这样的模拟结果。

  • 接下来我们尝试调整一下我们的线性模型,让它能够实现一些非线性的拟合,看看能不能得到更好的结果,这里同样是参照西瓜书中的部分。

    • 首先是原理部分:

我们希望最终的模型能够实现对上面公式的拟合,这里其实就是让逼近y。实质上还是线性回归知识我们对训练数据做了一个非线性拟合。原理很简单,我们只要对原来的代码做一点修改就能实现。

  • 这里只用对原来代码中这一段进行修改就行了。
if __name__ == "__main__":
   diabetes = load_diabetes()
   data = diabetes.data
   # target = diabetes.target
   target = np.log(diabetes.target)  # 将原来的y映射到指数函数的空间中
   testlinear = Linear_regression()

最终得到的结果如下

感觉好像结果更差了,不过这里只是对简单的线性模型做一些调试。