机器学习笔记-线性回归之梯度下降

简介

为了不被时代淘汰,闲暇时间了解一下机器学习相关的知识。学会Python的语法,对numpy等常用库有了一定的了解,现在记录一下自己的学习进展,以便巩固一下学会的知识,也方便自己查阅吧。
学习进度按照github上比较火的项目 practicalAI 进行,Python的基础语法,numpy和pandas等常用库此处就不记录了,等自己搞明白了再尝试补博客吧。
新入坑学习,有写的不对的地方,欢迎指导,谢谢!

什么是线性回归?

按照百度百科的解释:
线性回归是利用数理统计中回归分析,来确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法,运用十分广泛。其表达形式为y = w’x+e,e为误差服从均值为0的正态分布。
回归分析中,只包括一个自变量和一个因变量,且二者的关系可用一条直线近似表示,这种回归分析称为一元线性回归分析。如果回归分析中包括两个或两个以上的自变量,且因变量和自变量之间是线性关系,则称为多元线性回归分析。

个人理解,简单点说就是 我们可以有多个输入值(x1,x2,x3…xn),然后它们对应了一个输出值y,y对于某一个输入值x,其关系都是可以用一条直线表示(此处假设其它输入值是不变的,就像求偏导数那样),这就是我们要处理的线性关系

什么是梯度下降?

假设有一组数据,它是线性的,生成的公式是y = 3x + random_noise
random_noise是随机生成的误差值,这样我们看的的数据集就不是一条完整的直线了
此时我们的表达式是 y = f(x) = ax+ b
但是我们并不知道真实的参数是多少,现在需要做的是假设一个函数,h(x) = θx + θ0
为了方便处理这个θ0 通常我们设定所有的输入集合中,有一个参数x0 并且x0 永远都是1,这样我们的假设函数就变成了 h(x0,x1) = θ0x0 + θ1x1
因为实际可能有很多组输入x,所以可以继续发散,θ和x的组合就可以有多组
根据输入的x,可以得到函数h(x)的预测值,h(x)和真实的y会存在误差,我们使用方差来表示这个误差,这个方差就是 损失函数 J(θ)

J(θ0,θ1...θn)=12ni=0n(hθ(xi)yi)2\\J(\theta_0,\theta_1...\theta_n)=\frac{1}{2n}\sum_{i=0}^n (h_\theta(x_i) -y_i)^2

前面的12\frac{1}{2}是为了求导时抵消的系数,对实际结果无影响
我们要做的事求出损失函数的极小值,让误差最小,而一个函数的导数是这个函数的变化规律,所以就变成了求损失函数为0的点。导函数就变成了

θJ(θ0,θ1...θn)=1ni=0n(hθ(x0i,x1i...xni)yi)xi)\frac{\partial}{\partial \theta}J(\theta_0,\theta_1...\theta_n)= \frac{1}{n}\sum_{i = 0}^n(h_\theta(x_0^i,x_1^i...x_n^i)-y_i)x_i)

我们对单个θ求偏导,将所有的θ偏导数构成一组向量,这个向量就是给定函数在定点上升最快的方向(此处的数学原理略过,我也没太明白。。。)。我们现在要找最小值,所以取负号,找到下降最快的方向。然后让我们的预设值的θ和对应位置的值相见,就相当于逼近了真实值.
但是相减也要有一定的程度,如果过大就会导致错过最低点,从而无法得到我们需要的值,此时就需要对向量进行缩小,我们称之为 学习效率 用α表示,α值过大会导致得不到我们需要的值,值过小有可能会导致学习时间过长,所以需要我们自己去实际调整,通常设定为0.001,0.01,0.1等。
每次计算出新的θ之后,记得下一组计算要使用新的θ数组!

python代码实现

此处参考使用practicalAI中04_Linear_Regression中的数据源。

import numpy as np
import pandas as pd
from numpy import dot
from numpy.linalg import inv
from argparse import Namespace
args = Namespace(
    seed = 1234,
    data_file = 'sample_data.csv',
    num_samples = 100,
    train_size = 0.75,
    test_size = 0.25,
    num_epochs = 100,
)
#设置随机数开始的整数值,保证我们的实验数据是一致的
np.random.seed(args.seed)

def generate_data(num_samples):
    X = np.array(range(num_samples))
    random_noise = np.random.uniform(-10,10,size = num_samples)
    y = 3.65 * X + 10 + random_noise
    return X,y
#得到我们需要的x,y数据
X,y = generate_data(args.num_samples)
#将x,y合并,并转置,然后存储为DataFrame,方便后续操作
data = np.vstack([X,y]).T
df = pd.DataFrame(data,columns = ['x1','y'])
#设置x0参数,方便我们后续建模,前文已经解释过了
df['x0'] = 1
#取出操作过后的x和y矩阵,并且改变y的shape,是为了后续矩阵计算使用
X = df.iloc[:,[2,0]]
Y = df.iloc[:,1].values.reshape(args.num_samples,1)
#数学计算,最小二乘法得到结果的公式
theta = dot(dot(inv(dot(X.T,X)),X.T),Y)

上面主要是讲我们的输入值和输出值进行数据整合分类,然后方便作为训练数据进行计算。
接下来我们看计算的代码,按照之前的思路,我们要计算出损失函数的向量,然后不停的优化我们的参数θ。

#设定θ初始值为1,1
theta = np.array([1.,1.]).reshape(2,1)
alpha = 0.0001
temp = theta
#初始化x0,x1的参数矩阵
x0 = X.iloc[:,0].values.reshape(args.num_samples,1)
x1 = X.iloc[:,1].values.reshape(args.num_samples,1)

for i in range(10000):
	#分别计算两个参数的向量,并且将源数据进行一定程度的变更
    temp[0] = theta[0] - alpha * np.sum((dot(X,theta) - Y) * x0) / args.num_samples
    temp[1] = theta[1] - alpha * np.sum((dot(X,theta) - Y) * x1) / args.num_samples
    #用新学会的数据替换旧数据
    theta = temp
print(theta)

以上代码就是对应的梯度下降实现,经过实测,跑出来的结果是
[[3.22784771]
[3.75543881]]
而使用最小二乘法进行数学计划的结果是
[[10.79569 ]
[ 3.64134481]]
相比之下,最小二乘法更靠近我们预先设计的公式,详细原因还没有弄清楚,猜测和数据源也有一定的关系吧,将原始数据和最小二乘法,梯度下降的结果生成在坐标系中查看,实际误差很小。
蓝色是我们的学习数据,黄色是最小二乘法得到的函数,红色是梯度下降得到的函数。
机器学习笔记-线性回归之梯度下降
另外,我们设置的初始值也有一定的关系,如果把初始数组设置为,[10,3],计算出的结果将和最小二乘法出现的结果相差极小。不过个人认为,我们设置为1,1或者其他,最终得到的结果终归是有用的,误差是可接受的。

代码已上传github