本章主要讲解不同的训练模型的方法。

线性回归

No.004 训练模型 - 图1
No.004 训练模型 - 图2是假设函数,使用模型参数No.004 训练模型 - 图3。存在n个特征,m个样本。对于线性模型的训练,设置模型参数,直到最拟合训练集。目标函数是最小化MSE。因此如下成本函数
No.004 训练模型 - 图4
最小化MSE,得到了
No.004 训练模型 - 图5
存在以下方法求解线性回归参数

  1. import numpy as np
  2. X = 2 * np.random.rand(100, 1)
  3. # 这里假设y = 4 + 2x
  4. y = 4 + 2 * X + np.random.rand(100, 1)
  5. # X是全1和X组成。
  6. X_b = np.c_[np.ones((100, 1)), X]
  7. # 方法①:使用公式
  8. theta_best = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y)
  9. # 方法②:调用LinearRegression
  10. from sklearn.linear_model import LinearRegression
  11. lin_reg = LinearRegression()
  12. lin_reg.fit(X, y)
  13. print(lin_reg.intercept_, lin_reg.coef_)
  14. # 方法③:调用最小二乘
  15. theta_best_svd, residuals, rank, s = np.linalg.lstsq(X_b, y, rcond=1e-6)
  16. print(theta_best_svd)
  17. # 方法④:伪逆
  18. print(np.linalg.pinv(X_b).dot(y))
  • 伪逆是使用了奇异值分解(SVD)的标准矩阵分解方法,将训练集矩阵X分解为No.004 训练模型 - 图6。伪逆的计算公式为No.004 训练模型 - 图7

    计算复杂度

  • No.004 训练模型 - 图8是一个(n+1)*(n+1)的矩阵,因此矩阵求逆的复杂度达到了No.004 训练模型 - 图9~No.004 训练模型 - 图10,如果特征数量翻倍,那么复杂度将会乘以No.004 训练模型 - 图11倍。

  • 但是通过SVD的方法,复杂度将为No.004 训练模型 - 图12。当特征加倍,复杂度提升了4倍。

梯度下降

梯度下降的中心思想:迭代调整参数,是成本函数最小化。首先使用随机的No.004 训练模型 - 图13,然后逐步改善,每一步(步长很重要)尝试降低一点成本函数,直到算法收敛得到一个最小值。步长取决于超参数的学习率;如果学习率太低了,需要大量迭代才能够收敛,则会很耗时;但如果学习率太高了,则很容易发散。

批量梯度下降

对参数No.004 训练模型 - 图14计算偏导数
No.004 训练模型 - 图15
对全部的θ计算偏导数,得到了梯度向量No.004 训练模型 - 图16包含了全部的成本函数的偏导数
No.004 训练模型 - 图17
在计算梯度下降的每一步,都是基于完整的训练集X。因此速度会比较慢。但是梯度下降算法将会随着特征数量的上升而提升。
梯度向量乘以学习率No.004 训练模型 - 图18,得到了下坡的步长
No.004 训练模型 - 图19
代码如下

  1. eta = 0.1
  2. n_iterations = 1000
  3. m = 100
  4. theta = np.random.rand(2, 1)
  5. for i in range(n_iterations):
  6. gradients = 2 / m * X_b.T.dot(X_b.dot(theta) - y)
  7. theta -= eta * gradients

结果依然是array([[4.45329892], [1.96504494]])。

随机梯度下降

批量梯度下降的主要问题是:需要使用整个训练集计算每一步梯度,因此训练集很大,算法会特别慢。与之相反的是随机梯度下降,每一步在训练集中随机选择一个实例,仅仅基于该单个实例计算梯度。这种方法更快,并且每次迭代只需要操作少量的数据。
由于算法的随机性质,比批量梯度下降更加不规则。成本函数是不断上上下下,但从整体来看是缓缓下降。但即便达到了最小是,依然会反弹,最终会非常接近最小值。
当成本函数非常不规则,随机梯度下降有助于帮助算法挑出局部最小值,从而对于寻找全局最小值更加具有优势。
因此随机性的好处在于可以套利局部最优解,缺点是永远定位不到最小值——解决之道,逐步降低学习率。开始的步长比较大(有助于快速开展,并且逃离局部最小值),然后越来越小,让算法尽量靠近全局最小值,该算法称为模拟退火(simulated annealing)。

  1. n_epochs = 50
  2. t0, t1 = 5, 50
  3. m = 100
  4. def learning_schedule(t):
  5. return t0 / (t + t1)
  6. theta = np.random.randn(2, 1)
  7. for epoch in range(n_epochs):
  8. for i in range(m):
  9. random_index = np.random.randint(m)
  10. xi = X_b[random_index : random_index + 1]
  11. yi = y[random_index : random_index + 1]
  12. gradients = 2 * xi.T.dot(xi.dot(theta) - yi)
  13. eta = learning_schedule(epoch * m + i)
  14. theta = theta - eta * gradients

使用随机梯度下降,训练实例需要确保IID分布,从而确保平均而言将参数蜡像全局最优解。如果使用Scikit-Learn中的随机梯度下降,进行线性回归,可以使用SGDRegresor。该类默认优化平方误差成本函数。以下代码最多可以运行1000次轮次,或者直到下一轮次期间损失下降低于0.001为止(max_iter=1000,tol=1e-3)。使用默认的学习调度以0.1开始。不使用任何正则化。

  1. from sklearn.linear_model import SGDRegressor
  2. sgd_reg = SGDRegressor(max_iter=1000, tol=1e-3, penalty=None, eta0=0.1)
  3. sgd_reg.fit(X, y.ravel())
  4. print(sgd_reg.intercept_, sgd_reg.coef_)

小批量梯度下降

在每一步中,不是根据完整的训练集,也不是仅仅基于某一个实例计算梯度,小批量梯度下降称为小型批量的随机实例集上计算梯度。

多项式回归

例如,一个简单的二次方程生成非线性数据No.004 训练模型 - 图20没有办法通过直线拟合。因此需要通过Poly-nominal-Features转换训练数据,让训练集中的每个特征的平方(二次多项式)添加为新特征

  1. m = 100
  2. X = 6 * np.random.rand(m, 1) - 3
  3. X = np.array(sorted(list(X)))
  4. y = 0.5 * X ** 2 + X + 2 * np.random.randn(m, 1)
  5. # 添加新特征X**2
  6. from sklearn.preprocessing import PolynomialFeatures
  7. poly_features = PolynomialFeatures(degree=2, include_bias=False)
  8. X_poly = poly_features.fit_transform(X)
  9. print(X[0], X_poly[0])
  10. # 分别为[-1.0904787] 和 [-1.0904787 1.18914379]
  11. # X_poly现在包含了X原始特征+平方项特征。从而能够将LinearRegression模型拟合到扩展之后的训练输数据中。
  12. # 拟合与预测
  13. from sklearn.linear_model import LinearRegression
  14. lin_reg = LinearRegression()
  15. lin_reg.fit(X_poly, y)
  16. print(lin_reg.intercept_, lin_reg.coef_)
  17. # 结果为截距项=0.10470686,系数=0.94094301 0.32822587
  18. y_pred = lin_reg.predict(X_poly)
  19. # 画图
  20. plt.plot(X, y_pred, 'r-', label='Pred')
  21. plt.plot(X, y, 'b.')
  22. plt.xlabel('X')
  23. plt.ylabel('y')
  24. plt.legend()
  25. plt.show()

image.png
Poly-Nomial-Features能够将特征的所有组合添加到给定的多项式阶数中。例如degree=3、存在两个特征a和b的时候,存在No.004 训练模型 - 图22。因此要小心参数爆炸。

学习曲线

如何判断欠拟合还是过拟合?如何确定模型复杂性。之前通过了交叉检验估计模型的泛化能力。还有一种方法是通过观察学习曲线:绘制的是在训练集和验证机上关于训练集大小的性能函数。

  1. from sklearn.metrics import mean_absolute_error
  2. from sklearn.model_selection import train_test_split
  3. def plot_training_curves(model, X, y):
  4. # 拆分训练集和验证集
  5. X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2)
  6. # 训练误差和验证误差
  7. train_errors, val_errors = [], []
  8. # 随着训练样本量的逐渐增大
  9. for m in range(1, len(X_train)):
  10. model.fit(X_train[:m], y_train[:m])
  11. y_train_pred = model.predict(X_train[:m])
  12. y_val_pred = model.predict(X_val)
  13. train_errors.append(mean_absolute_error(y_train[:m], y_train_pred))
  14. val_errors.append(mean_absolute_error(y_val, y_val_pred))
  15. plt.plot(np.sqrt(train_errors), 'r-+', linewidth=2, label='train')
  16. plt.plot(np.sqrt(val_errors), 'b-+', linewidth=3, label='val')
  17. plt.legend()
  18. plt.xlabel('Training Set Size')
  19. plt.ylabel('RMSE')
  20. plt.show()
  21. # X 和 y 来自上一列的二次方方程
  22. plot_training_curves(LinearRegression(), X, y)

image.png

  • 当训练集只有很少的几个实例的时候,模型能够很好地拟合;但是随着实例的增加,模型不能够完美地拟合训练数据,因为模型并不是线性、而是二次方,因此训练误差会一直上升,直到平稳。
  • 而验证集上的误差一开始会很高,因为训练数据太少了,无法泛化;之后随着训练集的增大,验证误差逐渐下降。但是同样因为线性模型并不能够拟合,因此之后的误差保持平稳。
  • 两条曲线均表现出欠拟合的状态。

如果使用10阶多项式进行拟合

  1. from sklearn.pipeline import Pipeline
  2. polynomial_reg = Pipeline([
  3. ('poly_features', PolynomialFeatures(degree=10, include_bias=False)),
  4. ('lin_reg', LinearRegression())
  5. ])
  6. plot_training_curves(polynomial_reg, X, y)

image.png

偏差/方差权衡

模型的泛化误差能够表示为三个非常不同的误差之和:

  • 偏差:泛化误差的原因在于错误的假设,例如假设数据是线性,但是实际上是二次;高偏差模型最有可能欠拟合训练集。
  • 方差:由于模型对训练数据的细微变化过于敏感;具有很多自由度的模型(例如高阶多项式魔性)可能具有较高的方差,因此可能过拟合训练数据。
  • 不可避免的误差:数据本身的噪声导致,减少这部分误差的唯一方法是清理数据。

正则化(Regularized)线性模型

减少过拟合的方法是对模型进行正则化(约束模型):拥有的自由度越少,那么过拟合数据的难度就越大,正则化模型的简单方法是减少多项式的次数。正则化多项式模型的一种简单方法是减少多项式的次数。
对于线性模型,正则化通常是约束模型的权重实现。

岭回归Ridge

通过将等于No.004 训练模型 - 图25的正则项调价到成本函数中,不仅拟合数据,而且使模型的权重尽可能小。超参数No.004 训练模型 - 图26控制了模型的正则化程度。如果No.004 训练模型 - 图27,那么岭回归=线性回归;如果No.004 训练模型 - 图28很大,那么所有权重最终都会接近0,结果是经过数据均值的平均线。成本函数如下
No.004 训练模型 - 图29
注意:下标是从1开始,因此并没有对No.004 训练模型 - 图30正则化。如果将No.004 训练模型 - 图31定义为特征权重的向量No.004 训练模型 - 图32,那么正则项等于No.004 训练模型 - 图33表示为权重向量的No.004 训练模型 - 图34范数。对于梯度下降,只需将No.004 训练模型 - 图35田间道MSE梯度向量即可。
在执行岭回归之前,需要对数据进行缩放(例如,StandardScaler),因为对于输入特征的缩放很敏感。大部分正则化都需要如此。
我们能够通过计算闭合形式(closed-form)的方程,或者执行梯度下降,执行岭回归。闭合形式的岭回归
No.004 训练模型 - 图36

  1. from sklearn.linear_model import Ridge
  2. m = 100
  3. X = 6 * np.random.rand(m, 1) - 3
  4. X = np.array(sorted(list(X)))
  5. y = 0.5 * X ** 2 + X + 2 * np.random.randn(m, 1)
  6. # 讨论呢各个角落Cholesky求解
  7. ridge_reg = Ridge(alpha=1, solver='cholesky')
  8. ridge_reg.fit(X, y)
  9. print(ridge_reg.predict([[1.5]]))
  10. # 1.55071465
  11. # 使用随机梯度下降法
  12. sgd_reg = SGDRegressor(penalty='l2')
  13. sgd_reg.fit(X, y.ravel())
  14. print(sgd_reg.predict([[1.5]]))

Lasso回归

最小绝对收缩和选择算子回归(Least Absolute Shrinkage and Selection Operator Regression,Lasso)。和岭回归一样,通过向成本函数添加了正则项,但是添加的权重向量是No.004 训练模型 - 图37范数。公式如下
No.004 训练模型 - 图38
Lasso回归的一个重要特点是他倾向于消除不重要的特征的权重,将他们设置为0。为了避免使用Lasso 在梯度下降时在最优解附近反弹,需要逐步降低训练期间的学习率。

  1. from sklearn.linear_model import Lasso
  2. lasso_reg = Lasso(alpha=0.1)
  3. lasso_reg.fit(X, y)
  4. print(lasso_reg.predict([[1.5]]))
  5. from sklearn.linear_model import SGDRegressor
  6. sgd_reg = SGDRegressor(penalty='l1')
  7. sgd_reg.fit(X, y.ravel())
  8. print(sgd_reg.predict([[1.5]]))

弹性网络

弹性网络介于Ridge和Lasso之间,通过控制混合比例r。当r=0的时候,弹性网络等于岭回归;当r=1的时候,弹性网络等于Lasso。成本函数如下
No.004 训练模型 - 图39

  1. from sklearn.linear_model import ElasticNet
  2. # l1_ratio即为混合比
  3. elastic_net = ElasticNet(alpha=0.1, l1_ratio=0.5)
  4. elastic_net.fit(X, y)
  5. elastic_net.predict([[1.5]])

提前停止

对于梯度下降的迭代类学习算法,存在一个不同的正则化方法,即当验证误差达到最小值的时候停止训练——提前停止法。
经过一轮轮的训练,训练集上的预测误差(RMSE)会不断下降,验证集上的误差也会下降;但是达到某个点,验证误差会停止下降,开始上升,说明模型开始过拟合。因此,通过早起停止,能够在验证误差得到最小值就立刻停止。

  1. from sklearn.base import clone
  2. from sklearn.linear_model import Ridge
  3. from sklearn.preprocessing import StandardScaler
  4. from sklearn.pipeline import Pipeline
  5. from sklearn.preprocessing import PolynomialFeatures
  6. from sklearn.model_selection import train_test_split
  7. from sklearn.metrics import mean_absolute_error
  8. X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2)
  9. poly_scaler = Pipeline([
  10. ('Poly_features', PolynomialFeatures(degree=90, include_bias=False)),
  11. ('std_scaler', StandardScaler())
  12. ])
  13. X_train_poly_scaled = poly_scaler.fit_transform(X_train)
  14. X_val_poly_scaled = poly_scaler.transform(X_val)
  15. sgd_reg = SGDRegressor(max_iter=1, tol=-np.infty, warm_start=True,
  16. penalty=None, learning_rate='constant', eta0=0.0005)
  17. min_val_error = float('inf')
  18. best_epoch = None
  19. best_model = None
  20. for epoch in range(1000):
  21. sgd_reg.fit(X_train_poly_scaled, y_train)
  22. y_val_predict = sgd_reg.predict(X_val_poly_scaled)
  23. val_error = mean_absolute_error(y_val, y_val_predict)
  24. if val_error < min_val_error:
  25. min_val_error = val_error
  26. best_epoch = epoch
  27. best_model = clone(sgd_reg)

逻辑回归

回归算法也能够用于分类。逻辑回归(Logit)被广泛用于估算一个实例属于某个特定类别的概率。如果与预估概率超过了50%,那么该实例属于正类,否则是负类。从而是一个二元分类器。

估计概率

逻辑回归的估计概率
No.004 训练模型 - 图40
逻辑函数No.004 训练模型 - 图41是一个sigmoid函数,即为S形函数,输出介于0~1之间的数字。
image.png
一旦逻辑回归模型估算出例No.004 训练模型 - 图43属于正类的概率No.004 训练模型 - 图44,就能够预测标签No.004 训练模型 - 图45
No.004 训练模型 - 图46

训练和成本函数

逻辑回归模型的训练目标是为了设置参数向量No.004 训练模型 - 图47,是模型对正类实例做出高概率估计(No.004 训练模型 - 图48),对负类实例做出低概率估计(No.004 训练模型 - 图49),例如单个训练实例No.004 训练模型 - 图50的成本函数
No.004 训练模型 - 图51
当概率No.004 训练模型 - 图52接近0的时候,No.004 训练模型 - 图53将会接近No.004 训练模型 - 图54,因此模型将正类实例的概率估计为0,将会导致成本很高;同样,如果将负例实例的概率估计为1,那么成本No.004 训练模型 - 图55将会接近No.004 训练模型 - 图56
对于整个训练集的成本函数是所有训练实例的平均成本,能够通过损失函数表示
No.004 训练模型 - 图57
偏导数
No.004 训练模型 - 图58

  1. from sklearn import datasets
  2. iris = datasets.load_iris()
  3. print(list(iris.keys()))
  4. X = iris['data'][:, 3:]
  5. y = (iris['target'] == 2).astype(np.int)
  6. from sklearn.linear_model import LogisticRegression
  7. log_reg = LogisticRegression()
  8. log_reg.fit(X, y)
  9. X_new = np.linspace(0, 3, 1000).reshape(-1, 1)
  10. y_proba = log_reg.predict_proba(X_new)
  11. plt.plot(X_new, y_proba[:, 1], "g-", label="Iris virginica")
  12. plt.plot(X_new, y_proba[:, 0], "b--", label="Not Iris virginica")
  13. plt.legend()
  14. plt.show()
  15. # + more Matplotlib code to make the image look pretty

image.png

Softmax回归

多元逻辑回归,给定一个实例No.004 训练模型 - 图60No.004 训练模型 - 图61首先计算出每个类No.004 训练模型 - 图62的分数No.004 训练模型 - 图63,然后对这些分数应用softmax函数,估计每个类的概率。
No.004 训练模型 - 图64类的No.004 训练模型 - 图65分数No.004 训练模型 - 图66——每一类都有自己的指定参数向量No.004 训练模型 - 图67。在得到了实例No.004 训练模型 - 图68在每个类的分数之后,通过No.004 训练模型 - 图69函数计算实例属于类别No.004 训练模型 - 图70的概率No.004 训练模型 - 图71。该函数计算每个分数的指数,然后对其归一化,除以所有指数的总和。分数通常称为对数或者对数奇数。No.004 训练模型 - 图72函数如下
No.004 训练模型 - 图73
其中,一共有No.004 训练模型 - 图74类,No.004 训练模型 - 图75是一个向量、包含了实例No.004 训练模型 - 图76的每个类别的分数,No.004 训练模型 - 图77是实例No.004 训练模型 - 图78属于类别No.004 训练模型 - 图79的估计概率。
No.004 训练模型 - 图80回归分类预测公式如下
No.004 训练模型 - 图81
argmax返回的是势函数最大化的变量值。在该等式中,返回的是是估计概率No.004 训练模型 - 图82最大化的k值。
训练目标是得到一个能够对目标类做出高概率估算的模型(使其他类概率相应降低)。通过成本函数(交叉熵函数)最小化该目标
No.004 训练模型 - 图83
其中No.004 训练模型 - 图84是类别k中第i个实例的目标概率,等于0或者1。
两个概率分布No.004 训练模型 - 图85No.004 训练模型 - 图86之间的交叉熵定义为No.004 训练模型 - 图87,梯度向量如下
No.004 训练模型 - 图88

  1. X = iris["data"][:, (2, 3)] # petal length, petal width
  2. y = iris["target"]
  3. softmax_reg = LogisticRegression(multi_class="multinomial",solver="lbfgs", C=10)
  4. softmax_reg.fit(X, y)
  5. print(softmax_reg.predict([[5, 2]]))
  6. # 结果为[2]