Skip to content

特征缩放

课程与数据集

本文对应 Andrew Ng(吴恩达)《Machine Learning》梯度下降法实践 1——特征缩放部分。代码使用的 ex1data2.txt(房屋面积、卧室数、价格)为课程官方编程练习数据,与线性回归同属第二周内容。

数据与参考代码可在黄海广教授整理的中文笔记仓库下载:fengdu78/Coursera-ML-AndrewNg-Notes(见 code/ex1/ 目录)。课程主页:Coursera Machine Learning


一、为什么进行特征缩放

一个由两个变量评估的数据:

y=θ0+θ1x1+θ2x2

如果仅仅只是按照我们之前的代码,直接从 (0,0,0) 开始梯度下降,难免会让我们产生疑问:"这个初始点是否合适?有没有更高效的方式?" 比如此处如果 x1 范围为 300–500,x2 范围为 0–5,并且这两个变量对 y 的影响都是很可观的。那么你就会想 "x1 的范围那么大,可见它乘上系数 θ1 后要产生一个很大的变化才能达到与 x2 变化 1 甚至 0.1 相同的结果。" 这就让我们想到 |θ1| 需要是一个很小的值,来削弱 x1 值的变化。

这个在吴恩达机器学习里有一个很好的例子(不要犹豫,肯定是和房价有关的东西):

price=w1x1+w2x2+b
场景x1x2pricew1w2b结果
已知数据20005500k
拟合 A20005500.150price=100,050k(效果不好)
拟合 B200050.15050price=500k(效果很好)

我们自然而然地想到:如果钱的单位是元,我们就把它变成千元;如果人数的单位是个,就把它变成千个、万个,将所有的数据通过这种简单的单位调配使其达到差不多的值。

但是别忘了我们通常输入的都是零向量,也就是说如果可以把所有数据通过简单的线性或加减变换使其都在 0 的某一个领域内,那就再好不过了。


二、特征缩放的数学原理

2.1 方法一(最大最小值归一化)

假设 x1 已经收集到了 m 组数据,那么可以这样进行特征缩放:

取平均值: u1=1mi=1mx1(i)取最大值和最小值: max{x1}min{x1}令: x1:=x1u1max{x1}min{x1}

2.2 方法二(Z-score 标准化)

取平均值、标准差: u1σ1令: x1:=x1u1σ1

特征缩放之前,舍弃明显不对劲的数据尤为重要,这个在中学生物实验就已经提到过了。


三、将 θ 数组变换回来

我们无非就是运行了以下的式子:

yuyσy=θ0+θ1x1u1σ1+θ2x2u2σ2++θnxnunσn

这里 θi 就是我们运行出来的、经过了特征缩放后算出的数据,而原式为:

y=θ0+θ1x1+θ2x2++θnxn

联立可以得出:

θ0=(θ0i=1nθi×uiσi)×σy+uyθi=θiσiσy(i=1,2,3n)

四、代码实现

4.1 导入库与读取数据

首先导入要用的库和数据:

python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

path = 'ex1data2.txt'
data = pd.read_csv(path, header=None, names=['Size', 'Bedrooms', 'Price'])
data.head()  # 查看数据有多少列,方便定义 theta_begin

4.2 特征缩放

对数据进行特征缩放:

python
# 处理数据
means = np.array(data.mean().values).reshape(1, 3)  # 把平均值保存下来并且化为 array 类型
std = np.array(data.std().values).reshape(1, 3)  # 保存 mean std

temp = data.copy()  # 保存源数据
data = (data - data.mean(axis=0)) / data.std(axis=0)  # data.mean(axis=0) 表示每列替换为平均值
print(data.head())

data.insert(0, 'ones', 1)

cols = data.shape[1]

X = data.iloc[:, 0:cols - 1]  # X 是所有行去掉最后一列
Y = data.iloc[:, cols - 1:cols]

X = np.array(X.values)
Y = np.array(Y.values)

theta_begin = np.array([0, 0, 0]).reshape(1, 3)  # 调整为 (1,3) 形状以匹配原矩阵形状

4.3 代价函数与梯度下降

代价函数和梯度下降函数还是老样子,因为我们之前写的函数对于任意列数都有用:

python
iters = 1000
cost = np.zeros(iters)

def cost_function(X, Y, theta):
    inner = np.power((X @ theta.T) - Y, 2)
    return np.sum(inner) / (2 * len(X))

def gradient_descent(X, y, theta, alpha, iters):
    temp = np.zeros(theta.shape)
    parameters = theta.ravel().shape[0]
    global cost
    for i in range(iters):
        error = (X @ theta.T) - y
        for j in range(parameters):
            term = np.multiply(error, X[:, j].reshape(-1, 1))
            temp[0, j] = theta[0, j] - ((alpha / len(X)) * np.sum(term))
        theta = temp.copy()
        cost[i] = cost_function(X, Y, theta)
    return theta

4.4 训练与可视化

我们查看特征缩放得出来的 θ 数组。由于特征缩放不影响 cost 的评估,我们再可视化一下 cost:

python
g = gradient_descent(X, Y, theta_begin, 0.01, iters)
print(g)
print(cost_function(X, Y, g))

# 迭代 cost 可视化
fig, ax = plt.subplots(figsize=(12, 8))
ax.plot(np.arange(iters), cost, 'r')
ax.set_xlabel('iters')
ax.set_ylabel('cost')
ax.set_title('change of cost')
plt.show()

4.5 参数反变换

我们接下来将参数转换回去,即从 θθ

python
def theta_back(theta, means, std):
    thetaback = theta.copy()

    thetaback[0, 0] = (theta[0, 0] - (np.sum((means[0, :-1] * theta[0, 1:]) / std[0, :-1]))) * std[0, -1] + means[0, -1]  # 目标变量也被标准化

    thetaback[0, 1:] = (theta[0, 1:] / std[0, :-1]) * std[0, -1]
    return thetaback

我们看看最终结果:

python
gf = theta_back(g, means, std)
print(gf)

示例输出

text
[[88307.21151185 138.22534685 -7709.05876589]]

前置知识:《线性回归》笔记(ex1data1.txt)。后续:《逻辑回归》《正则化》笔记(ex2data1.txt / ex2data2.txt)。