机器学习推导+python实现(一):线性回归

写在开头:这个系列的灵感已经整个系列的思路会根据公众号机器学习实验室的节奏进行,相当于做一个自己的理解版本,并且按照以往惯例我们会增加一些问题来对小细节进行讨论。

内容安排

笔者觉得如果单单的去调用sklearn库的机器学习的方法有些不妥,这个系列本应该在去年就开始了,但一直拖着没有更新。所以从今天开始我们一起来探究机器学习的乐趣吧。这个系列开始后,我们还会增加很多细节上的思考问题的讨论系列。
根据公众号机器学习实验室的节奏安排我们预计会涉及以下几个内容的实现:线性回归(一)、逻辑回归(二)、K近邻(三)、决策树值ID3(四)、CART(五)、感知机(六)、神经网络(七)、线性可分支持向量机(八)、线性支持向量机(九)、线性不可分支持向量机(十)、朴素贝叶斯(十一)、Lasso回归(十二)、Ridge岭回归(十三)。
相信通过这样的推导我们应该可以更好的去理解和应用及其学习的算法。

1.线性回归的数学推导

这里笔者为了节约编写的时间对于公式的推导我们会从“西瓜书”、《统计学习方法》或者其他资料中进行复制,并在需要补充的地方进行公式扩充。根据上几个深度学习的系列,我们可以进行概念的相互理解。
回归模型(得分函数)
线性回归的形式如下,
f ( x i ) = w x i + b , 使 f ( x i ) y i f(x_i) = wx_i+b,使得f(x_i)\simeq y_i 也就是企图通过变量的线性关系来对结果进行推测,希望尽可能得到本来的结果,也就是个预测问题。那么我们有了得分函数后应当怎么样去计算误差呢?
均方误差(损失函数)
这里我们就使用均方误差来度量回归模型的性能,我们希望求得w和b使得均方误差最小,视图寻找到一条是直线使得到所有样本的欧氏距离最小
( w , b ) = arg min ( w , b )   i = 1 m ( f ( x i ) y i ) 2 (w^*,b^*)={\underset {(w,b)}{\operatorname {arg\,min} }}\ \sum\limits_{i=1}^{m}(f(x_i)-y_i)^2 ( w , b ) = arg min ( w , b )   i = 1 m ( y i w x i b ) 2 (w^*,b^*)={\underset {(w,b)}{\operatorname {arg\,min} }}\ \sum\limits_{i=1}^{m}(y_i-wx_i-b)^2
最小二乘估计
对于线性回归模型我们不用太着急使用梯度的算法进行更新,我们这里使用函数推导的形式进行计算,计算最优值,我们的首先计算其偏导等于0的点,
E ( w , b ) w = 2 i = 1 m ( y i w x i b ) ( x i ) = 2 ( w i = 1 m x i 2 i = 1 m ( y i b ) x i ) = 0 \frac{\partial E_{(w,b)}}{\partial w}=2\sum\limits_{i=1}^{m}(y_i-wx_i-b)(-x_i)=2(w\sum\limits_{i=1}^{m}x_i^2-\sum\limits_{i=1}^{m}(y_i-b)x_i)=0 E ( w , b ) b = 2 i = 1 m ( y i w x i b ) ( 1 ) = 2 ( m b i = 1 m ( y i w x i ) ) = 0 \frac{\partial E_{(w,b)}}{\partial b}=2\sum\limits_{i=1}^{m}(y_i-wx_i-b)(-1)=2(mb-\sum\limits_{i=1}^{m}(y_i-wx_i))=0
分别令上面两个式子等于0通过变形就可以得到两个式子的最优解,这里注意可以先算第二个式子然后反带回第一个式子,那么对第二个式子移项得到,
b = 1 m i = 1 m ( y i w x i ) = y ˉ w x ˉ b=\frac{1}{m}\sum\limits_{i=1}^{m}(y_i-wx_i)=\bar y-w\bar x 将这个式子代入第一个式子对w
进行求解得到,
( w i = 1 m x i 2 i = 1 m ( y i ( y ˉ + w x ˉ ) x i ) = 0 (w\sum\limits_{i=1}^{m}x_i^2-\sum\limits_{i=1}^{m}(y_i-(\bar y+w\bar x)x_i)=0 打开括号就得到,
w ( i = 1 m x i 2 i = 1 m x ˉ x i ) i = 1 m ( y i y ˉ ) x i = 0 w(\sum\limits_{i=1}^{m}x_i^2-\sum\limits_{i=1}^{m}\bar xx_i)-\sum\limits_{i=1}^{m}(y_i-\bar y)x_i=0 w ( i = 1 m x i 2 1 m i = 1 m i = 1 m x i x i ) i = 1 m ( y i 1 m i = 1 m y i ) x i = 0 w(\sum\limits_{i=1}^{m}x_i^2-\frac{1}{m}\sum\limits_{i=1}^{m}\sum\limits_{i=1}^{m}x_ix_i)-\sum\limits_{i=1}^{m}(y_i-\frac{1}{m}\sum\limits_{i=1}^{m}y_i)x_i=0 w ( i = 1 m x i 2 1 m ( i = 1 m x i ) 2 ) i = 1 m ( x i 1 m i = 1 m x i ) y i = 0 w(\sum\limits_{i=1}^{m}x_i^2-\frac{1}{m}(\sum\limits_{i=1}^{m}x_i)^2)-\sum\limits_{i=1}^{m}(x_i-\frac{1}{m}\sum\limits_{i=1}^{m}x_i)y_i=0
通过移项并且将y的均值和x的均值进行展开再合并的技巧得到w的最优解,
w = i = 1 m y i ( x i x ˉ ) i = 1 m x i 2 1 m ( i = 1 m x i ) 2 w=\frac{\sum\limits_{i=1}^{m}y_i(x_i-\bar x)}{\sum\limits_{i=1}^{m}x_i^2-\frac{1}{m}(\sum\limits_{i=1}^{m}x_i)^2} b = y ˉ w x ˉ b=\bar y-w\bar x 这样我们就得到了w和b的最优解。
多元回归形式
下面让我们将回归从一元往多元进行扩展,
f ( x i ) = w T x i + b , 使 f ( x i ) y i f(x_i) = w^Tx_i+b,使得f(x_i)\simeq y_i 首先X就从一元变为了多元,并且包含常数项,
X = [ x 11 x 12 . . . x 1 d 1 x 21 x 22 . . . x 2 d 1 x m 1 x m 2 . . . x m d 1 ] = [ x 1 T 1 x 2 T 1 x m T 1 ] X=\begin{bmatrix} x_{11} & x_{12} &...&x_{1d}&1 \\ x_{21} & x_{22} &...&x_{2d}&1 \\\vdots &\vdots&\ddots&\vdots&\vdots \\ x_{m1} & x_{m2} &...&x_{md}&1 \end{bmatrix}\quad= \begin{bmatrix} x_{1}^T &1 \\ x_{2}^T&1 \\\vdots&\vdots \\ x_{m}^T&1 \end{bmatrix}\quad 然后w的最优解的最小二乘就可以写成,
w ^ = arg min w ^ ( y X w ^ ) T ( y X w ^ ) \hat w^*={\underset {\hat w}{\operatorname {arg\,min} }}(y-X\hat w)^T(y-X\hat w) E w ^ = ( y X w ^ ) T ( y X w ^ ) E_{\hat w}=(y-X\hat w)^T(y-X\hat w) 然后对 w ^ \hat w 求导,
E w ^ w ^ = ( y X w ^ ) T ( y X w ^ ) w ^ = ( y T y y T X w ^ ( X w ^ ) T y + ( X w ^ ) T X w ^ ) w ^ \frac{\partial E_{\hat w}}{\partial \hat w}=\frac{\partial (y-X\hat w)^T(y-X\hat w)}{\partial \hat w}=\frac{\partial(y^Ty-y^TX\hat w-(X\hat w)^Ty+(X\hat w)^TX\hat w)}{\partial \hat w} 这里的 y T X w ^ ( X w ^ ) T y = 2 ( X w ^ ) T y -y^TX\hat w-(X\hat w)^Ty=-2(X\hat w)^Ty 于是得到, E w ^ w ^ = 2 X T y + X T X w ^ = 2 X T ( X w ^ y ) \frac{\partial E_{\hat w}}{\partial \hat w}=-2X^Ty+X^TX\hat w=2X^T(X\hat w-y) 然后令导数等于0,这时不能直接删去 X T X^T ,通过移项求逆得到,
w ^ = ( X T X ) 1 X T y \hat w^*=(X^TX)^{-1}X^Ty
于是以上就是对于线性回归模型的参数估计过程。

2.线性回归的python实现

在计算回归函数,我们重点是在其参数的更新上,如果数据量过大,那么我们传统的最小二乘法计算矩阵的逆就很复杂,还有可能矩阵不可逆无法计算,因此可以通过梯度下降的方法来寻找最优的参数。
首先我们定义一下回归模型的主体,需要定义得分函数、损失函数以及参数的偏导数,

import numpy as np
def linearRegression(X, y, W, b):
    numTrain = X.shape[0]
    numFeatures = X.shape[1]
    
    #得分函数编写
    yHat = np.dot(X, W) + b
    
    #损失函数编写
    loss = np.sum(np.square(yHat - y)) / numTrain
    
    #梯度的编写
    dW = 2 * np.dot(X.T, (yHat - y))/ numTrain
    db = 2 * np.sum(yHat - y)/ numTrain
    
    return yHat, loss, dW, db

然后定义初始参数函数,对于初始化这一块也需要最近学一下,看看有没有更好的初始化办法

def initialParams(d):
    W = np.zeros((d, 1)) + 0.01
    b = 0.01
    return W, b

接着定义训练函数,并输出累计损失以及参数,

def linearTrain(X, y, learningRate = 0.001, numIters = 1000000):
    W, b = initialParams(X.shape[1])
    lossHistory = []
    
    for it in range(1, numIters):
        yHat, loss, dW, db = linearRegression(X, y, W, b)
        
        lossHistory.append(loss)
        W += -learningRate * dW
        b += -learningRate * db
        
        if it % 100000 == 0:
            print("%d numIters loss is %f"%(it, loss))
            
        params = {
            'W': W,
            'b': b
        }
        
        grads = {
            'dW': dW,
            'db': db
        }
        
    return lossHistory, params, grads

紧接着定义预测函数,

def linearPredict(X, params):
    W, b = params['W'], params['b']
    yPred = np.dot(X, W) + b
    return yPred

然后我们函数就定义完了,下面我们来加载数据,并划分训练集与测试集,这里使用sklearn自带数据

from sklearn.datasets import load_diabetes
from sklearn.utils import shuffle

diabetes = load_diabetes()
X = diabetes.data
y = diabetes.target

offset = int(X.shape[0] * 0.9)
X_train, y_train = X[:offset], y[:offset]
X_test, y_test = X[offset:], y[offset:]
y_train = y_train.reshape((-1, 1))
y_test = y_test.reshape((-1, 1))

print('Train data shape: ', X_train.shape)
print('Train labels shape: ', y_train.shape)
print('Test data shape: ', X_test.shape)
print('Test labels shape: ', y_test.shape)
Train data shape:  (397, 10)
Train labels shape:  (397, 1)
Test data shape:  (45, 10)
Test labels shape:  (45, 1)

下面来训练一下我们的线性回归模型吧,

lossHistory, params, grads = linearTrain(X_train, y_train)
100000 numIters loss is 3502.374208
200000 numIters loss is 3198.313524
300000 numIters loss is 3091.734345
400000 numIters loss is 3047.314501
500000 numIters loss is 3028.072201
600000 numIters loss is 3019.457399
700000 numIters loss is 3015.441710
800000 numIters loss is 3013.466611
900000 numIters loss is 3012.422030

我们来画个图看一下损失loss的变化情况,

import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(lossHistory)
plt.xlabel('numIters')
plt.ylabel('loss')
plt.show()

在这里插入图片描述
从图上可以看到在可能很少的迭代损失函数就急速的降为了比较小的值。下面进行预测,计算其样本平均误差,并绘制图像,

扫描二维码关注公众号,回复: 10203483 查看本文章
yPred = linearPredict(X_test)
print("predict average loss is %f"%(np.sum(yPred - y_test)/X_test.shape[0]))

plt.scatter(range(X_test.shape[0]), y_test, color = "lightblue")
plt.plot(yPred, color = "#ffcfdc")
plt.xlabel('X')
plt.ylabel('y')
plt.show()
predict average loss is 2.185061

在这里插入图片描述
总体来说还是可以效果。
下面我们用class简单的封装一下这个代码,并且编写了一个交叉验证的函数,这个地方是借鉴机器学习实验室中的封装技术,这个封装还需要多学习,

import numpy as np
from sklearn.utils import shuffle
from sklearn.datasets import load_diabetes
class LrModel():
    def __init__(self):
        pass
    
    def prepareData(self):
        X = load_diabetes().data
        y = load_diabetes().target
        y = y.reshape(-1, 1)
        data = np.concatenate((X, y), axis=1)
        return data
    
    def linearPredict(self, X):
        W, b = params['W'], params['b']
        yPred = np.dot(X, W) + b
        return yPred
    def initialParams(self, d):
        W = np.zeros((d, 1)) + 0.01
        b = 0.01
        return W, b
    def linearRegression(self, X, y, W, b):
        numTrain = X.shape[0]
        numFeatures = X.shape[1]
        yHat = np.dot(X, W) + b
        loss = np.sum(np.square(yHat - y)) / numTrain
        dW = 2 * np.dot(X.T, (yHat - y))/ numTrain
        db = 2 * np.sum(yHat - y)/ numTrain
        return yHat, loss, dW, db
    
    def linearTrain(self, X, y, learningRate = 0.001, numIters = 1000000):
        W, b = self.initialParams(X.shape[1])
        for it in range(1, numIters):
            yHat, loss, dW, db = self.linearRegression(X, y, W, b)
            W += -learningRate * dW
            b += -learningRate * db
            if it % 100000 == 0:
                print("%d numIters loss is %f"%(it, loss))
            params = {
                'W': W,
                'b': b
            }  
            grads = {
                'dW': dW,
                'db': db
            }  
        return loss, params, grads
    
    def linearPredict(self, X, params):
        W, b = params['W'], params['b']
        yPred = np.dot(X, W) + b
        return yPred
    
    def linearCrossValidation(self, data, k, randomize=True):
        if randomize:
            data = list(data)
            shuffle(data)
        
        slices = [data[i::k] for i in range(k)] 
        for i in range(k):
            validation = slices[i]
            train = [data
                    for s in slices if s is not validation for data in s]
            train = np.array(train)
            validation = np.array(validation)
            yield train, validation

if __name__=='__main__':
    lr = LrModel()
    data = lr.prepareData()
    
    for train, validation in lr.linearCrossValidation(data, 5):
        X_train = train[:, :10]
        y_train = train[:, -1].reshape((-1, 1))
        X_valid = validation[:, :10]
        y_valid = validation[:, -1].reshape((-1, 1))
        loss5 =[]
        loss, params, grads = lr.linearTrain(X_train, y_train)
        loss5.append(loss)
        score = np.mean(loss5)
        print('five kold cross validation score is', score)
        y_pred = lr.linearPredict(X_valid, params)
        valid_score = np.sum(((y_pred - y_valid) ** 2)) / len(X_valid)
        print('valid score is', valid_score)

这个封装函数需要注意的是在交叉验证中使用的yeild函数,能够保住多次循环储存的时候在上一次基础上进行储存。


结语
好了,今天的关于手推线性回归+python实现的部分就到这里了,后面会不断更新之后的内容。
谢谢阅读。
参考
公众号:机器学习实验室

发布了30 篇原创文章 · 获赞 150 · 访问量 6150

猜你喜欢

转载自blog.csdn.net/qq_35149632/article/details/105040640