【机器学习快速入门教程4】线性回归

章节4:线性回归

本章节,我们将介绍线性回归问题,机器学习中最基础的问题。

线性回归

线性回归是指在一组数据中拟合一条最合适的直线,使得数据大致落在这条直线上,考虑以下数据集:

import numpy as np
import matplotlib.pyplot as plt

# x, y
data = np.array([
    [2.4, 1.7], 
    [2.8, 1.85], 
    [3.2, 1.79], 
    [3.6, 1.95], 
    [4.0, 2.1], 
    [4.2, 2.0], 
    [5.0, 2.7]
])

有了这些{x,y}集合对,就可以将它们绘制在散点图中,

x, y = data[:,0], data[:,1]

plt.figure(figsize=(4, 3))
plt.scatter(x, y)

线性回归的目标是找到一条直线:y=mx+b,使得它能很好的拟合数据。那么,用什么来判断数据点拟合的好坏程度呢?我们将比较以下三条随机拟合的直线,他们的函数方程如下:

def f1(x):
    return 0.92 * x - 1.0

def f2(x):
    return -0.21 * x + 3.4

def f3(x):
    return 0.52 * x + 0.1

# try some examples
print("f1(-1.0) = %0.2f" % f1(-1))
print("f2( 0.0) = %0.2f" % f2(0))
print("f3( 2.0) = %0.2f" % f3(2))
f1(-1.0) = -1.92
f2( 0.0) = 3.40
f3( 2.0) = 1.14

绘制这三条直线方程,观察每条直线对数据的拟合程度,

min_x, max_x = min(x), max(x)

fig = plt.figure(figsize=(10,3))

fig.add_subplot(131)
plt.scatter(x, y)
plt.plot([min_x, max_x], [f1(min_x), f1(max_x)], 'k-')
plt.title("f1")

fig.add_subplot(132)
plt.scatter(x, y)
plt.plot([min_x, max_x], [f2(min_x), f2(max_x)], 'k-')
plt.title("f2")

fig.add_subplot(133)
plt.scatter(x, y)
plt.plot([min_x, max_x], [f3(min_x), f3(max_x)], 'k-')
plt.title("f3")

直观来看,f1和f3拟合的较好,且f3比f1好像表现更好,而f2是拟合最差的。如何形式上定义拟合的程度呢?我们定义了一个代价(误差)函数,它可以计算直线拟合的点与实际数据点的总的误差。计算代价函数最常用的手段是误差平分和,用J表示,计算的是直线拟合的点与实际数据点差的平方和(平方是为了惩罚误差大的数据点)。

下面,我们绘制误差线,用红色的虚线表示,

min_x, max_x = min(x), max(x)
fig = plt.figure(figsize=(4,3))
plt.scatter(x, y) # original data points
plt.plot([min_x, max_x], [f1(min_x), f1(max_x)], 'k-') # line of f1
plt.scatter(x, f1(x), color='black') # points predicted by f1
for x_, y_ in zip(x, y):
    plt.plot([x_, x_], [y_, f1(x_)], '--', c='red') # error bars
plt.title("error bars: $y_i-f_1(x_i)$")

误差平方和函数又叫做二次代价函数,对误差平方和函数开方以后是距离公式,也叫做欧几里得距离。误差平方和除以数据个数n以后,我们得到均方误差,这几种代价函数经常可交换使用。为了方便起见,均方误差函数的n一般取为2,所以得到以下均方差代价函数:

将f(x)展开就得到:
# sum squared error
def cost(y_pred, y_actual):
    return 0.5 * np.sum((y_actual-y_pred)**2)

x, y = data[:,0], data[:,1]

J1 = cost(f1(x), y)
J2 = cost(f2(x), y)
J3 = cost(f3(x), y)

print("J1=%0.2f, J2=%0.2f, J3=%0.2f" % (J1, J2, J3))
J1=1.18, J2=2.16, J3=0.15

正如我们所料,第三个函数在该数据集上的代价最小,第二个函数的代价最大。
有什么好的方法能帮助我们找到最佳的m和b,使得代价函数最小?最简单的方法就是暴力解决,让计算机进行数以万计的猜测,保留拥有最小代价的m和b。但是在实际的问题中,我们经常又数十,上百乃至数百万的参数需要同时优化,没有计算机能做到在合理的时间内找到最优的解决方案。
所以我们需要一个更正规的方案。或许我们能从代价函数的损失平面找到方法,我们将绘制出一定范围内m和b两个参数组合后的MSE(均方误差)函数,

from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure()
ax = fig.gca(projection='3d')

# check all combinations of m between [-2, 4] and b between [-6, 8], to precision of 0.1
M = np.arange(-2, 4, 0.1)
B = np.arange(-6, 8, 0.1)

# get MSE at every combination
J = np.zeros((len(M), len(B)))
for i, m_ in enumerate(M):
    for j, b_ in enumerate(B):
        J[i][j] = cost(m_*x+b_, y)

# plot loss surface
B, M = np.meshgrid(B, M)
ax.plot_surface(B, M, J, rstride=1, cstride=1, cmap=plt.cm.coolwarm, linewidth=0, antialiased=False)
plt.title("cost for different m, b")
plt.xlabel("b")
plt.ylabel("m")

由此可以看到,我们的损失平面有点像一个加长型的碗,因为我们的目的是找到误差最小的参数m和b,所以,大致可以看到最低点在m=0,b=2处。

梯度下降

在这,我们介绍梯度下降来寻找最合适的参数,注意,有比梯度下降更好的方法来解决线性回归的问题。但我们引入梯度下降,是为了将其运用于后面要学习的神经网络中。所以,先介绍下梯度下降在简单问题中的运用。
梯度下降的基本概念是,先随机猜测一组参数,接着计算它们的损失函数的梯度。注意,损失函数(Loss function)是定义在单个训练样本上的,而代价函数(Cost function)是定义在整个训练集上面的,也就是所有样本的误差的总和的平均,也就是损失函数的总和的平均,所以在这是损失函数。章节2介绍过,一个函数的梯度就是包含每个变量偏导数的一个向量,如下:

我们的损失函数J只有两个参数斜率m和y轴截距b,因此它的梯度是:
梯度的意义就是能计算损失函数在任意参数m和b中任意维度的斜率,梯度下降的基本思想就是计算损失函数在当前参数的斜率,接着往完全相反的方向(因为斜率是上升的),移动一小步(通过更新参数m和b),这样就能使参数m和b移动到误差相比之前更小的地方。重复多次执行该步骤,直到损失停止下降(因为已经到达损失平面的最底部)就完成了。
怎样计算对各个参数的偏导数呢?已知,
先计算J对参数m的偏导数,
把常数1/2提到最外面,然后应用和的导数就是导数的和规则,得到:
使用链式法则,降低指数,指数内部函数求导就是-Xi,
再计算J对参数b的偏导数,
把常数1/2提到最外面,然后应用和的导数就是导数的和规则,得到:
使用链式法则,降低指数,指数内部函数求导就是-1,
定义更新规则,用来调整参数m和b(只列出了b的更新规则,m的类似),
α是学习率,用来控制参数更新的大小。在简单的梯度下降中,学习率是手动设置的。但在更复杂的梯度下降中,学习率是自动选择并在训练中调整。如果α太大,则可能会出现越过最低点,损失函数不会收敛的情况;如果α太小,则会出现收敛太慢,花费太多时间的情况。典型的α取值为0.01,0.001,0.0001等等。代码实现如下,

import random

# get our data
x, y = data[:,0], data[:,1]

# it is a good idea to normalize the data
x = x / np.amax(x, axis=0)
y = y / np.amax(y, axis=0)

# choose a random initial m, b
m, b = random.random(), random.random()#0.8, -0.5

def F(x, m, b):
    return m * x + b

# what is our error?
y_pred = F(x, m, b)
init_cost = cost(y_pred, y)

print("initial parameters: m=%0.3f, b=%0.3f"%(m, b))
print("initial cost = %0.3f" % init_cost)

# implement partial derivatives of our parameters
def dJdm(x, y, m, b):
    return -np.dot(x, y - F(x, m, b))

def dJdb(x, y, m, b):
    return -np.sum(y - F(x, m, b))

# choose the alpha parameter and number of iterations
alpha = 0.01
n_iters = 2000

# keep track of error
errors = []
for i in range(n_iters):
    m = m - alpha * dJdm(x, y, m, b)
    b = b - alpha * dJdb(x, y, m, b)
    y_pred = F(x, m, b)
    j = cost(y_pred, y)
    errors.append(j)
    
# plot it
plt.figure(figsize=(16, 3))
plt.plot(range(n_iters), errors, linewidth=2)
plt.title("Cost by iteration")
plt.ylabel("Cost")
plt.xlabel("iterations")

# what is our final error rate
y_pred = F(x, m, b)
final_cost = cost(y_pred, y)

print("final parameters: m=%0.3f, b=%0.3f"%(m, b))
print("final cost = %0.3f" % final_cost)
initial parameters: m=0.181, b=0.682
initial cost = 0.043
final parameters: m=0.584, b=0.325
final cost = 0.009

画出找到的最合适的直线:

min_x, max_x = min(x), max(x)

fig = plt.figure(figsize=(3,3))
plt.scatter(x, y)
plt.plot([min_x, max_x], [m * min_x + b, m * max_x + b], 'k-')
plt.title("line of best fit")

上面所展示的程序就是梯度下降方法,然而对于线性回归问题可以用分析法更好的解决,我们引入梯度下降,是为了在下一章节将其运用在神经网络中。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容