机器学习01|一万五字:线性回归【详解及jupyter代码】

文章目录

  • 线性回归
    • 1- 准备
    • 2- 单变量线性回归理论介绍
      • 单变量线性回归模型
      • **注意**:
      • 预测结果
      • 损失函数
      • 梯度下降法
  • 3- 实现单变量线性回归模型
      • **任务1:**
      • **任务2:**
      • **任务3:**
      • **任务4:**
      • **任务5:**
      • **任务6:**
      • **任务7:**
    • 4- 实现多变量线性回归模型
      • **任务8:**
  • 5- 特征归一化
      • **任务9:**
  • 6- 正规方程
      • **任务10:**
  • 7- 预测结果
      • **任务11:**
  • 8- 多项式回归
      • **任务12:**


这是一个有部分代码缺失的notebook,需要完成的就是将 ### START CODE HERE ### ### END CODE HERE ###之间的代码补全。

线性回归

某城市的电网系统需要升级,以应对日益增长的用电需求。电网系统需要考虑最高温度对城市的峰值用电量的影响。项目负责人需要预测明天城市的峰值用电量,他搜集了以往的数据。现在,负责人提供了他搜集到的数据,并请求你帮他训练出一个模型,这个模型能够很好地预测明天城市的峰值用电量。

1- 准备

先导入必要的python包

import numpy as np
import matplotlib.pyplot as plt
import time
%matplotlib inline

导入负责人提供的数据,并可视化数据

data = np.loadtxt('data.txt')
#data 第一列为温度信息 第二列为人口信息
X_raw = data[:,0].reshape(-1,1)
#data 第三列为用电量信息
Y = data[:,2].reshape(-1,1)
plt.xlabel('High temperature')
plt.ylabel('Peak demand ')
plt.scatter(X_raw,Y)
print('X shape:',X_raw.shape)
print('Y shape:',Y.shape)
print('some X[:5]:\n',X_raw[:5])
print('some Y[:5]:\n',Y[:5])

X shape: (80, 1)
Y shape: (80, 1)
some X[:5]:
[[38.24]
[36.53]
[32.92]
[26.59]
[20.05]]
some Y[:5]:
[[4.04]
[2.84]
[3.2 ]
[3.42]
[2.32]]
机器学习01|一万五字:线性回归【详解及jupyter代码】_第1张图片
根据对数据可视化结果的分析,决定使用回归算法训练一个模型,用来预测明天城市的峰值用电量。首先考虑单变量的线性回归模型。

2- 单变量线性回归理论介绍

单变量线性回归模型

单变量线性回归的模型由两个参数 θ 0 \theta_0 θ0, θ 1 \theta_1 θ1来表示一条直线: P e a k   d e m a n d ≈ θ 0 + θ 1 ⋅ ( H i g h   t e m p e r a t u r e ) 。 Peak\ demand \approx \theta_0 + \theta_1 \cdot (High\ temperature) 。 Peak demandθ0+θ1(High temperature)

我们的目标也就是找到一条"最符合"的直线,确定这条直线的参数 θ i \theta_i θi

机器学习01|一万五字:线性回归【详解及jupyter代码】_第2张图片

设输入的特征——最高温度(F)为 x ( i ) ∈ R n + 1 x^{(i)} \in \mathbb{R}^{n+1} x(i)Rn+1 i = 1 , ⋯   , m i=1,\cdots,m i=1,,m m m m为样本总数,在该例子中m=80 n n n为特征的个数,这里为 1 1 1。则: x ( i ) ∈ R 2 = [ 1 high temperature for day i ] 。 x^{(i)} \in \mathbb{R}^2 = \begin{bmatrix} 1 \\ \text{high temperature for day i} \end{bmatrix}。 x(i)R2=[1high temperature for day i]

设输出为 y ( i ) ∈ R y^{(i)} \in \mathbb{R} y(i)R,表示第 i i i天的峰值用电量。

参数为 θ ∈ R n + 1 = [ θ 0 θ 1 ⋮ θ n ] \theta \in \mathbb{R}^{n+1} = \begin{bmatrix} \theta_0 \\ \theta_1 \\ \vdots \\ \theta_n \end{bmatrix} θRn+1= θ0θ1θn 。这里 n = 1 n=1 n=1

在该例子中,模型为一条直线,模型可表示为:
h θ ( x ) = θ T x = θ 0 + θ 1 x 。 h_{\theta}(x) = \theta^T x = \theta_0 + \theta_1 x 。 hθ(x)=θTx=θ0+θ1x

注意

这里的 θ T \theta^T θT是一个向量, θ 0 , θ 1 \theta_0,\theta_1 θ0,θ1是标量。使用向量化表示的原因为:(1)简化数学公式的书写(2)与程序代码中的表示保持一致,且使用向量化的代码实现可以加速运算,因此一般能不用for循环的地方都不用for循环

下面用一个简单的例子说明向量化的代码运算更快。

# 随机初始化两个向量,计算它们的点积
x = np.random.rand(10000000,1)
y = np.random.rand(10000000,1)
ans = 0
start = time.time()
for i in range(10000000):
    ans += x[i,0]*y[i,0]
end = time.time()
print('for循环的计算时间: %.2fs'%(end - start))
print('计算结果:%.2f'%(ans))
start = time.time()
ans = np.dot(x.T,y)
end = time.time()
print('向量化的计算时间: %.2fs'%(end - start))
print('计算结果:%.2f'%(ans))

for循环的计算时间: 4.06s
计算结果:2499880.72
向量化的计算时间: 0.00s
计算结果:2499880.72

因为 θ 0 + θ 1 x = [ 1 x ] [ θ 0 θ 1 ] 。 \theta_0 + \theta_1 x=\begin{bmatrix} 1 \quad x \end{bmatrix} \begin{bmatrix} \theta_0 \\ \theta_1 \end{bmatrix} 。 θ0+θ1x=[1x][θ0θ1]
因此,为了方便编程,我们需要给每一个 x ( i ) x^{(i)} x(i)的前面再加一列1。使得每一个 x ( i ) x^{(i)} x(i)成为一个2维向量。

预测结果

模型需要根据输入自变量 x ( i ) x^{(i)} x(i) 和参数 θ \theta θ 来输出预测结果 p r e d i c t _ y ( i ) predict\_y^{(i)} predict_y(i)

将自变量 x ( i ) x^{(i)} x(i) 作为模型的输入,模型根据输入和当前参数 θ \theta θ 输出预测结果:

p r e d i c t _ y ( i ) = h θ ( x ( i ) ) 。 predict\_y^{(i)} = h_\theta(x^{(i)})。 predict_y(i)=hθ(x(i))

其中 h θ ( ) h_\theta() hθ() 为模型在参数为 θ \theta θ 情况下,对于输入的预测函数。

在预测阶段, x x x作为自变量。

损失函数

模型的预测结果和实际结果有差距,为了衡量它们之间的差距,或者说量化使用这个模型产生的损失,我们定义损失函数 l ( p r e d i c t _ y ( i ) , y ( i ) ) l(predict\_y^{(i)}, y^{(i)}) l(predict_y(i),y(i))。这里我们使用平方损失:
l ( p r e d i c t _ y , y ) = ( p r e d i c t _ y ( i ) − y ( i ) ) 2 。 l(predict\_y, y) = \left ( predict\_y^{(i)} - y^{(i)} \right )^2。 l(predict_y,y)=(predict_y(i)y(i))2

上述损失函数表示一个样本的损失,整个训练集的损失使用 J ( θ ) J(\theta) J(θ)表示:
J ( θ ) = 1 2 m ∑ i = 1 m l ( p r e d i c t _ y ( i ) , y ( i ) ) = 1 2 m ∑ i = 1 m ( h θ ( x ( i ) ) − y ( i ) ) 2 = 1 2 m ∑ i = 1 m ( θ T x ( i ) − y ( i ) ) 2 。 \begin{aligned} J(\theta) & = \frac{1}{2m} \sum_{i=1}^{m}l(predict\_y^{(i)}, y^{(i)}) \\ & = \frac{1}{2m} \sum_{i=1}^{m} \left ( h_\theta(x^{(i)}) - y^{(i)} \right )^2 \\ & = \frac{1}{2m} \sum_{i=1}^{m} \left ( \theta^T x^{(i)} - y^{(i)} \right )^2。 \end{aligned} J(θ)=2m1i=1ml(predict_y(i),y(i))=2m1i=1m(hθ(x(i))y(i))2=2m1i=1m(θTx(i)y(i))2
(其中数字2的作用是方便求导时的运算)

为了使模型取得较好的预测效果,需要最小化训练集上的损失,即 minimize θ J ( θ ) \underset{\theta}{\text{minimize}} J(\theta) θminimizeJ(θ)

在损失阶段, θ \theta θ 作为自变量。

梯度下降法

为了得到使损失函数 J ( θ ) J(\theta) J(θ)最小化的 θ \theta θ,可以使用梯度下降法。

损失函数 J ( θ ) J(\theta) J(θ)的函数图像如下:

机器学习01|一万五字:线性回归【详解及jupyter代码】_第3张图片

损失函数 J ( θ ) J(\theta) J(θ)关于参数向量 θ \theta θ中的一个参数,比如 θ 1 \theta_1 θ1的函数图为:

机器学习01|一万五字:线性回归【详解及jupyter代码】_第4张图片

假设一开始 J ( θ ) J(\theta) J(θ)的值在紫色点上,为了降低 J ( θ ) J(\theta) J(θ)值,需要 θ 1 \theta_1 θ1往右边移动,这个方向是 J ( θ ) J(\theta) J(θ) θ 1 \theta_1 θ1上的负梯度。只要 θ \theta θ不断往负梯度方向移动, J ( θ ) J(\theta) J(θ)一定可以降到最低值。梯度下降法就是使参数 θ \theta θ不断往负梯度移动,经过有限次迭代(更新 θ \theta θ值)之后,损失函数 J ( θ ) J(\theta) J(θ)达到最低值。

梯度下降法的过程:

  1. 初始化参数向量 θ \theta θ

  2. 开始迭代

    A.根据实际输入 x x x和参数 θ \theta θ预测输出,

    B. 根据预测输出值和实际输出值之间的差距,计算损失函数 J ( θ ) J(\theta) J(θ)

    C. 计算损失函数对 θ \theta θ的梯度,

    D. 更新参数 θ \theta θ

3- 实现单变量线性回归模型

现在,我们开始实现 Regression 算法。

任务1:

首先在 X X X前面加上一列1,表示参数 θ 0 \theta_0 θ0的系数,方便运算。 X X X是形状为 ( m , 1 ) (m,1) (m,1)的矩阵,一共 m m m行数据,我们需要为每一行数据的前面加一列1,如下所示:
[ x ( 0 ) x ( 1 ) ⋮ x ( m − 1 ) ] ⟶ [ 1 x ( 0 ) 1 x ( 1 ) ⋮ 1   x ( m − 1 ) ] 。 \begin{bmatrix} x^{(0)} \\ x^{(1)} \\ \vdots \\x^{(m-1)} \end{bmatrix} \longrightarrow \begin{bmatrix} 1\quad x^{(0)} \\ 1\quad x^{(1)} \\ \vdots \\ 1\ x^{(m-1)} \end{bmatrix}。 x(0)x(1)x(m1) 1x(0)1x(1)1 x(m1)
提示:可以使用np.hstack把两个矩阵水平合在一起。用1初始化向量或矩阵的函数是np.ones。(函数详情可使用python的帮助函数help,比如help(np.ones),或者自行用搜索引擎检索。)

def preprocess_data(X):
    """输入预处理 在X前面加一列1
    参数:
        X:原始数据,shape为(m,1)
        
    返回:
        X_train: 在X加一列1的数据,shape为(m,2)
    """

    m = X.shape[0]   # m 是数据X的行数
    ### START CODE HERE ###

    x_one = np.ones((m,1))
    X_train = np.hstack((x_one,X))
    
    ### END CODE HERE ###
    return X_train
X = preprocess_data(X_raw)
print('new X shape:',X.shape)
print('Y shape:',Y.shape)
print('new X[:5,:]=\n',X[:5,:])
print('Y[:5,:]=\n',Y[:5,:])

new X shape: (80, 2)
Y shape: (80, 1)
new X[:5,:]=
[[ 1. 38.24]
[ 1. 36.53]
[ 1. 32.92]
[ 1. 26.59]
[ 1. 20.05]]
Y[:5,:]=
[[4.04]
[2.84]
[3.2 ]
[3.42]
[2.32]]

任务2:

接着,初始化参数向量 θ \theta θ θ \theta θ的shape是 ( 2 , 1 ) (2,1) (2,1),我们随机初始化 θ \theta θ

提示:numpy的随机函数是np.random.rand

def init_parameter(shape):
    """初始化参数
    参数:
        shape: 参数形状
        
    返回:
        theta_init: 初始化后的参数
    """
    
    np.random.seed(0)
    m, n = shape
    ### START CODE HERE ###
    
    theta_init = np.random.rand(m,n)
    
    ### END CODE HERE ###
    
    return theta_init
theta = init_parameter((2,1))
print('theta shape is ',theta.shape)
print('theta = ',theta)

theta shape is (2, 1)
theta = [[0.5488135 ]
[0.71518937]]

任务3:

通过已知 X X X 和参数 θ \theta θ 计算预测的 p r e d i c t _ Y predict\_Y predict_Y 值。

由于使用for循环单独计算每个预测值效率不高,因此我们需要用向量化的方法代替for循环。 X X X 大小为 m × ( n + 1 ) m \times (n+1) m×(n+1)( n n n表示特征数量,这里 n = 1 n=1 n=1),每行是一条样本特征向量, θ \theta θ 大小为 ( n + 1 ) × 1 (n+1) \times 1 (n+1)×1,可以使用 X θ X \theta (矩阵相乘)计算所有样本的预测结果,大小为 m × 1 m\times 1 m×1。于是这里的线性模型就可以表示为:
h θ ( X ) = X θ 。 h_{\theta}(X) = X \theta。 hθ(X)=
这里 h θ ( X ) h_{\theta}(X) hθ(X)的大小为 m × 1 m \times 1 m×1,结果上等于 p r e d i c t _ Y θ predict\_Y_\theta predict_Yθ

提示:矩阵相乘 np.dot(矩阵1,矩阵2)

def compute_predict_Y(X,theta):
    """计算预测结果
    参数:
        X: 训练集数据特征,shape: (m, 2)
        theta: 参数,shape: (2, 1)

    返回:
        predict_Y: 预测结果,shape: (m,1)
    """
    
    ### START CODE HERE ###
    
    predict_Y = np.dot(X,theta)
    
    ### END CODE HERE ###
    return predict_Y
predict_Y = compute_predict_Y(X,theta)
print(predict_Y[:5])

[[27.89765487]
[26.67468106]
[24.09284744]
[19.56569876]
[14.8883603 ]]

任务4:

实现计算损失函数 J ( θ ) J(\theta) J(θ)的函数。
从公式
J ( θ ) = 1 2 m ∑ i = 1 m ( p r e d i c t _ y θ ( i ) − y θ ( i ) ) 2 \begin{aligned} J(\theta) = \frac{1}{2m} \sum_{i=1}^{m} \left ( predict\_y_\theta^{(i)} - y_\theta^{(i)} \right )^2 \end{aligned} J(θ)=2m1i=1m(predict_yθ(i)yθ(i))2
可以看到有个求和,由于使用for循环效率不高,因此需要用向量化的方法代替for循环。 ( p r e d i c t _ Y − Y ) 2 (predict\_Y - Y)^2 (predict_YY)2计算所有样本的损失值,最后求和并除以 2 m 2m 2m得到 J ( θ ) J(\theta) J(θ)的值,得到的 J ( θ ) J(\theta) J(θ)是一个标量。
提示:矩阵乘法运算可使用np.dot函数,平方运算可使用np.power(data, 2)函数,求和运算可使用np.sum

def compute_J(predict_Y, Y):
    """计算损失的函数J
    参数:
        predict_Y: 预测结果,shape: (m, 1)
        Y: 训练集数据标签,shape: (m, 1)
        
    返回:
        loss: 损失值
    """
    
    m = Y.shape[0]
    
    ### START CODE HERE ###

    loss = np.sum(np.power((predict_Y - Y),2))/(2*m)
    
    ### END CODE HERE ###
    
    return loss
first_loss = compute_J(predict_Y, Y)
print("first_loss = ", first_loss)

first_loss = 144.05159786255672

任务5:

计算参数 θ \theta θ的梯度。梯度计算的公式为:
∂ J ( θ ) ∂ θ j = 1 m ∑ i = 1 m ( θ T x ( i ) − y ) x j ( i ) 。 \frac{\partial J(\theta)}{\partial \theta_j} = \frac{1}{m} \sum_{i=1}^{m} \left ( \theta^T x^{(i)} - y \right ) x_j^{(i)}。 θjJ(θ)=m1i=1m(θTx(i)y)xj(i)
向量化公式为:
gradients = 1 m X T ( X θ − Y ) 。 \text{gradients} =\frac{1}{m} X^T (X \theta - Y) 。 gradients=m1XT(Y)
提示:矩阵A的转置表示为A.T X θ X\theta 就是计算出的predict_Y。

def compute_gradient(predict_Y, Y, X):
    """计算对参数theta的梯度值
    参数:
        predict_Y: 当前预测结果,shape: (m,1)
        Y: 训练集数据标签,shape: (m, 1)
        X: 训练集数据特征,shape: (m, 2)
        
    返回:
        gradients: 对theta的梯度,shape:(2,1)
    """
    
    m = X.shape[0]
    
    ### START CODE HERE ###
    
    gradients = np.dot(X.T,(predict_Y-Y))/m

    ### END CODE HERE ###
    
    return gradients
gradients_first = compute_gradient(predict_Y, Y, X)
print("gradients_first shape : ", gradients_first.shape)
print("gradients_first = ", gradients_first)

gradients_first shape : (2, 1)
gradients_first = [[ 16.0079445 ]
[459.96770081]]

任务6:

用梯度下降法更新参数 θ \theta θ,实现update_parameters函数。

提示:parameters = θ \theta θ - l e a r n i n g _ r a t e ⋅ g r a d i e n t s learning\_rate·gradients learning_rategradients

def update_parameters(theta, gradients, learning_rate=0.0001):
    """更新参数theta
    参数:
        theta: 参数,shape: (2, 1)
        gradients: 梯度,shape: (2, 1)
        learning_rate: 学习率,默认为0.0001
        
    返回:
        parameters: 更新后的参数,shape: (2, 1)
    """
    ### START CODE HERE ###
    
    parameters = theta - np.dot(learning_rate,gradients)
    
    ### END CODE HERE ###
    
    return parameters
theta_one_iter = update_parameters(theta, gradients_first)

print("theta_one_iter = ", theta_one_iter)

theta_one_iter = [[0.54721271]
[0.6691926 ]]

任务7:

将前面定义的函数整合起来,实现完整的模型训练函数。

θ \theta θ迭代更新 iter_num次。迭代次数iter_num也是一个超参数,如果iter_num太小,损失函数 J ( θ ) J(\theta) J(θ)还没有收敛;如果iter_num太大,损失函数 J ( θ ) J(\theta) J(θ)早就收敛了,过多的迭代浪费时间。

def model(X, Y, theta, iter_num = 100, learning_rate=0.0001):
    """线性回归模型
    参数:
        X: 训练集数据特征,shape: (m, n+1)
        Y: 训练集数据标签,shape: (m, 1)
        iter_num: 梯度下降的迭代次数
        theta: 初始化的参数,shape: (n+1, 1)
        learning_rate: 学习率,默认为0.0001
        
    返回:
        loss_history: 每次迭代的损失值
        theta_history: 每次迭代更新后的参数
        theta: 训练得到的参数
    """
    
    loss_history = []
    theta_history = []
    
    for i in range(iter_num):
        
        ### START CODE HERE ###
        # 预测
        predict_Y = compute_predict_Y(X,theta)
        # 计算损失
        loss = compute_J(predict_Y,Y)
        # 计算梯度
        gradients = compute_gradient(predict_Y, Y, X)
        # 更新参数
        
        theta = update_parameters(theta, gradients, learning_rate=0.0001)
        ### END CODE HERE ###
        
        loss_history.append(loss)
        theta_history.append(theta)
    
    return loss_history, theta_history, theta
# 可以自行尝试不同的学习率和迭代次数,最后提交时以100次迭代和0.0001的学习率重新运行一遍

loss_history, theta_history, theta = model(X, Y, theta, iter_num=100, learning_rate=0.0001)

print("theta = ", theta)

plt.plot(loss_history)
print("loss = ", loss_history[-1])

theta = [[0.52732144]
[0.09027749]]
loss = 0.09087253295782578

机器学习01|一万五字:线性回归【详解及jupyter代码】_第5张图片

下面是学习到的线性模型与原始数据的关系可视化。

plt.scatter(X[:,1],Y)
x = np.arange(10,42)
plt.plot(x,x*theta[1][0]+theta[0][0],'r')

机器学习01|一万五字:线性回归【详解及jupyter代码】_第6张图片

现在直观地了解一下梯度下降的过程。

theta_0 = np.linspace(0, 1, 50)
theta_1 = np.linspace(0, 1, 50)
theta_0, theta_1 = np.meshgrid(theta_0,theta_1)
J = np.zeros_like(theta_0)
predict_Ys = np.zeros_like(predict_Y)
print(theta_0.shape)
print(theta_1.shape)
print(predict_Ys.shape)
print(J.shape)

for i in range(50):
    for j in range(50):
        predict_Y = compute_predict_Y(X, np.array([[theta_0[i,j]],[theta_1[i,j]]]))
        J[i,j] = compute_J(predict_Y, Y)

plt.contourf(theta_0, theta_1, J, 10, alpha = 0.6, cmap = plt.cm.coolwarm)
C = plt.contour(theta_0, theta_1, J, 10, colors = 'black')

# 画出损失函数J的历史位置
history_num = len(theta_history)
theta_0_history = np.zeros(history_num)
theta_1_history = np.zeros(history_num)
for i in range(history_num):
    theta_0_history[i],theta_1_history[i] = theta_history[i][0,0],theta_history[i][1,0]
plt.scatter(theta_0_history, theta_1_history, c="r")

(50, 50)
(50, 50)
(80, 1)
(50, 50)

机器学习01|一万五字:线性回归【详解及jupyter代码】_第7张图片

可以看到, J ( θ ) J(\theta) J(θ)的值不断地往最低点移动。在y轴, J ( θ ) J(\theta) J(θ)下降的比较快,在x轴, J ( θ ) J(\theta) J(θ)下降的比较慢。

4- 实现多变量线性回归模型

上述例子是单变量回归的例子,样本的特征只有一个一天的最高温度。负责人经过分析后发现,城市一天的峰值用电量还与城市人口有关系,因此,他在回归模型中添加城市人口变量 x 2 x_2 x2,你的任务是训练这个多变量回归方程:
h ( x ) = θ T x = θ 0 ∗ 1 + θ 1 ∗ x 1 + θ 2 ∗ x 2 。 h(x) = \theta^T x = \theta_0 * 1 + \theta_1 * x_1 + \theta_2 * x_2。 h(x)=θTx=θ01+θ1x1+θ2x2
之前实现的梯度下降法使用的对象是 θ \theta θ X X X向量,实现的梯度下降函数适用于单变量回归和多变量回归。不难发现上面使用的向量化公式在多变量回归里依然不变,因此代码也基本一致,直接调用前面实现的函数即可。

任务8:

现在,训练一个多变量回归模型。

#读取数据,X取data的前两列
X = data[:,0:2].reshape(-1, 2)
Y = data[:,2].reshape(-1, 1)

### START CODE HERE ###

# 直接调用上面实现过的函数
# 同样为X的前面添加一列1,使得X的shape从80x2 -> 80x3

X = preprocess_data(X)
# 初始化参数theta ,theta的shape应为 3x1

theta = init_parameter((3,1))
# 传入模型训练,learning_rate设为0.0001

loss_history, theta_history, theta = model(X, Y, theta, iter_num=100, learning_rate=0.1)
### END CODE HERE ###

print("theta = ", theta)

plt.plot(loss_history)
print("loss = ", loss_history[-1])

theta = [[0.52593585]
[0.06715361]
[0.57583208]]
loss = 0.10300473270580186

机器学习01|一万五字:线性回归【详解及jupyter代码】_第8张图片

5- 特征归一化

特征归一化可以确保特征在相同的尺度,加快梯度下降的收敛过程。

任务9:

对数据进行零均值单位方差归一化处理。零均值单位方差归一化公式:
x i = x i − μ i σ i x_i = \frac{x_i - \mu_i}{\sigma_i} xi=σixiμi
其中 i i i表示第 i i i个特征, μ i \mu_i μi表示第 i i i个特征的均值, σ i \sigma_i σi表示第 i i i个特征的标准差。进行零均值单位方差归一化处理后,数据符合标准正态分布,即均值为0,标准差为1。

注意,使用新样本进行预测时,需要对样本的特征进行相同的缩放处理。

提示:求特征的均值,使用numpy的函数np.mean;求特征的标准差,使用numpy的函数np.std,需要注意对哪个维度求均值和标准差。比如,对矩阵A的每列求均值np.mean(A,axis=0)

X = data[:,0:2].reshape((-1, 2))
Y = data[:,2].reshape((-1, 1))

### START CODE HERE ###

# 计算特征的均值 mu
mu = np.mean(X,axis=0)
# 计算特征的标准差 sigma
sigma = np.std(X,axis=0)
# 零均值单位方差归一化
X_norm = (X - mu)/sigma

# 训练多变量回归模型
# X_norm前面加一列1
X = preprocess_data(X_norm)
# 初始化参数theta
theta = init_parameter((3,1))
# 传入模型训练,learning_rate设为0.1

loss_history, theta_history, theta = model(X, Y, theta, iter_num=100, learning_rate=0.1)
### END CODE HERE ###

print("mu = ", mu)
print("sigma = ", sigma)

print("theta = ", theta)

plt.plot(loss_history)
print("loss = ", loss_history[-1])

mu = [25.617 1.131]
sigma = [8.8648434 0.3606716]
theta = [[2.86181356]
[0.70424286]
[0.04097817]]
loss = 0.08591601385802586

机器学习01|一万五字:线性回归【详解及jupyter代码】_第9张图片

我们来直观地了解特征尺度归一化的梯度下降的过程。这里只展示单变量回归梯度下降过程。

X_show = X[:,0:2]
X_show = preprocess_data(X_show)

theta_0 = np.linspace(-2, 3, 50)
theta_1 = np.linspace(-2, 3, 50)
theta_0, theta_1 = np.meshgrid(theta_0,theta_1)
J = np.zeros_like(theta_0)

for i in range(50):
    for j in range(50):
        predict_Y = compute_predict_Y(X_show, np.array([[2.877],[theta_0[i,j]],[theta_1[i,j]]]))
        J[i,j] = compute_J(predict_Y, Y)

plt.contourf(theta_0, theta_1, J, 10, alpha = 0.6, cmap = plt.cm.coolwarm)
C = plt.contour(theta_0, theta_1, J, 10, colors = 'black')

# 画出损失函数J的历史位置
history_num = len(theta_history)
theta_0_history = np.zeros(history_num)
theta_1_history = np.zeros(history_num)
for i in range(history_num):
    theta_0_history[i],theta_1_history[i] = theta_history[i][2,0],theta_history[i][1,0]
plt.scatter(theta_0_history, theta_1_history, c="r")

机器学习01|一万五字:线性回归【详解及jupyter代码】_第10张图片

可以看到, J ( θ ) J(\theta) J(θ)的值不断地往最低点移动。与没有进行特征尺度归一化的图相比,归一化后,每个维度的变化幅度大致相同,这有助于 J ( θ ) J(\theta) J(θ)的值快速下降到最低点。

6- 正规方程

对于求函数极小值问题,可以使用求导数的方法,令函数的导数为0,然后求解方程,得到解析解。正规方程正是使用这种方法来求解损失函数 J ( θ ) J(\theta) J(θ)的极小值,而线性回归的损失函数 J ( θ ) J(\theta) J(θ)是一个凸函数,所以极小值就是最小值。

正规方程的求解过程就不详细推导了,正规方程的公式是:
θ = ( X T X ) − 1 X T Y \theta = (X^T X)^{-1} X^T Y θ=(XTX)1XTY

如果 m ≤ n + 1 m \le n +1 mn+1,那么 X T X X^T X XTX是奇异矩阵,即 X T X X^T X XTX不可逆。
X T X X^T X XTX不可逆的原因可能是:

  • 特征之间冗余,比如特征向量中两个特征是线性相关的。
  • 特征太多,删去一些特征再进行运算。

正规方程的缺点之一就是会出现 X T X X^T X XTX不可逆的情况,可以通过正则化的方式解决。另一个缺点是,如果样本的个数太多,特征数量太多( n > 10000 n \gt 10000 n>10000),正规方程的运算会很慢(求逆矩阵的运算复杂)。

任务10:

下面来实现正规方程。
提示:Numpy 求逆矩阵的函数是np.linalg.inv

def normal_equation(X, Y):
    """正规方程求解线性回归方程的参数
    参数:
        X: 训练集数据特征,shape: (m, n+1)
        Y: 训练集数据标签,shape: (m, 1)
        
    返回:
        theta: 线性回归方程的参数
    """
    
    ### START CODE HERE ###
        
    theta = np.dot(np.dot(np.linalg.inv(np.dot(X.T,X)),X.T),Y)

    ### END CODE HERE ###
    
    return theta
theta = normal_equation(X, Y)

print("theta = ", theta)

theta = [[2.861875 ]
[0.70429906]
[0.04092011]]

可以自行对比一下直接用正规方程求解出的 θ \theta θ与用线性回归模型训练出的 θ \theta θ之间的差异,会“惊奇”地发现两者几乎一模一样。

7- 预测结果

任务11:

假设明天的最高温度是 x 1 = 40 x_1 = 40 x1=40°C,人口 x 2 = 3.3 x_2 = 3.3 x2=3.3百万,使用通过正规方程计算得到的 θ \theta θ预测明天的城市的峰值用电量(单位:GW)吧!
注意 x x x要进行同样的特征尺度归一化处理。

def predict(theta,x):
    """预测峰值用电量
    参数:
        X: 需要预测数据的特征,shape: (m, n+1), 这里只预测一天的结果, m=1
        theta: 最终确定的参数,shape: (n+1, 1)
        
    返回:
        prediction: 预测结果,shape: (m, 1)
    """

    ### START CODE HERE ###

    # 零均值单位方差归一化
    # 计算特征的均值 mu
    mu = np.mean(x,axis=1)
    # 计算特征的标准差 sigma
    sigma = np.std(x,axis=1)
    # 零均值单位方差归一化
    x = (x - mu)/sigma
    # 在x前面加一列
    x = preprocess_data(x)
    #用theta和处理后的x计算预测值
    prediction = compute_predict_Y(x,theta[:,0:1])

    ### END CODE HERE ###

    return prediction

#明天的特征
x = np.array([[40,3.3]])
print('预计明天的峰值用电量为:%.2f GW'%(predict(theta,x))) 

预计明天的峰值用电量为:4.25 GW

8- 多项式回归

以上都是线性模型,当我们数据的特征 X X X与预测结果 Y Y Y之间没有明显的线性关系,而且又找不到合适的映射函数时,可以尝试多项式回归。
下面导入另一组最高气温与用电量数据,我们用线性模型试一试看看效果发现并不太好。

data1 = np.loadtxt('data1.txt')
X = data1[:,0].reshape(-1,1)
Y = data1[:,1].reshape(-1,1)

plt.scatter(X,Y)
X = np.hstack((np.ones((X.shape[0],1)),X))
theta = normal_equation(X,Y)
plt.plot(np.sort(X[:,1]),np.dot(X,theta)[np.argsort(X[:,1])],'r')

机器学习01|一万五字:线性回归【详解及jupyter代码】_第11张图片

多项式回归的最大优点就是可以通过增加 X X X的高次项对实测点进行逼近,直至满意为止。事实上,多项式回归可以处理相当一类非线性问题,它在回归分析中占有重要的地位,因为任一函数都可以分段用多项式来逼近。因此,在通常的实际问题中,不论依变量与其他自变量的关系如何,我们总可以用多项式回归来进行分析。假设数据的特征只有一个 a a a,多项式的最高次数为 K K K,那么多项式回归方程为:
h ( x ) = θ T x = θ 0 ∗ a 0 + θ 1 ∗ a 1 + θ 2 ∗ a 2 + ⋯ + θ K ∗ a K 。 h(x) = \theta^T x = \theta_0 * a^0 + \theta_1 * a^1 + \theta_2 * a^2 + \cdots + \theta_K * a^K。 h(x)=θTx=θ0a0+θ1a1+θ2a2++θKaK
若令 x = [ a 0 , a 1 , a 2 , ⋯   , a K ] T x = \begin{bmatrix} a^0, a^1, a^2, \cdots, a^K \end{bmatrix}^T x=[a0,a1,a2,,aK]T,那么
h ( x ) = θ T x = θ 0 ∗ x 0 + θ 1 ∗ x 1 + θ 2 ∗ x 2 , ⋯   , θ K ∗ x K , h(x) = \theta^T x = \theta_0 * x_0 + \theta_1 * x_1 + \theta_2 * x_2, \cdots, \theta_K * x_K, h(x)=θTx=θ0x0+θ1x1+θ2x2,,θKxK
这就变为多变量线性回归了。

任务12:

现在想要得到一个如下的多项式模型, K = 2 K=2 K=2,直接用上面的正规方程进行求解。
h ( x ) = θ T x = θ 0 ∗ 1 + θ 1 ∗ x + θ 2 ∗ x 2 。 h(x) = \theta^T x = \theta_0 * 1 + \theta_1 * x + \theta_2 * x^2。 h(x)=θTx=θ01+θ1x+θ2x2
输入数据 X X X变为:
[ x ( 0 ) x ( 1 ) ⋮ x ( m − 1 ) ] ⟶ [ 1 x ( 0 ) x ( 0 ) 2 1 x ( 1 ) x ( 1 ) 2 ⋮ 1   x ( m − 1 ) x ( m − 1 ) 2 ] 。 \begin{bmatrix} x^{(0)} \\ x^{(1)} \\ \vdots \\x^{(m-1)} \end{bmatrix} \longrightarrow \begin{bmatrix} 1\quad x^{(0)}\quad {x^{(0)}}^2 \\ 1\quad x^{(1)} \quad {x^{(1)}}^2\\ \vdots \\ 1\ x^{(m-1)}\quad {x^{(m-1)}}^2 \end{bmatrix}。 x(0)x(1)x(m1) 1x(0)x(0)21x(1)x(1)21 x(m1)x(m1)2

data1 = np.loadtxt('data1.txt')
X = data1[:,0].reshape(-1,1)
Y = data1[:,1].reshape(-1,1)

m = X.shape[0]    # m 是数据X的行数
X_square = np.power(X,2)

### START CODE HERE ###

# 对X 前面加1, 后面加平方,变为 m x 3 的矩阵


X = np.hstack((X,X_square))
X = preprocess_data(X)

# 用正规方程求解theta
theta = normal_equation(X,Y)

### END CODE HERE ###

plt.scatter(X[:,1],Y)
plt.plot(np.sort(X[:,1]),np.dot(X,theta)[np.argsort(X[:,1])],'r')

机器学习01|一万五字:线性回归【详解及jupyter代码】_第12张图片

所有任务到这里就结束了,下面是对上面的数据进行任意多项式拟合的结果,你可以通过改变 K K K的值来调整多项式的阶数,看看不同模型的效果(但不设的太大, K ≤ 193 K \le 193 K193)。可以看到,越复杂的模型,虽然拟合数据的效果越好,但是其泛化能力就会很差,所以模型的选择应该要尽量符合实际需求。

from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

def PolynomialRegression(degree):
    return Pipeline([
        ("poly",PolynomialFeatures(degree=degree)),
        ("std_scaler",StandardScaler()),
        ("lin_reg",LinearRegression())    
    ])
X = data1[:,0].reshape(-1,1)
Y = data1[:,1].reshape(-1,1)

K = 193  #可以调整K的值(0<=K<=193)

poly_reg = PolynomialRegression(degree=K)
poly_reg.fit(X,Y.squeeze())
y_predict = poly_reg.predict(X)
plt.scatter(X,Y)
plt.plot(np.sort(X[:,0]),y_predict[np.argsort(X[:,0])],color='r')

机器学习01|一万五字:线性回归【详解及jupyter代码】_第13张图片

你可能感兴趣的:(机器学习基础,机器学习,线性回归,jupyter,人工智能,深度学习)