赞
踩
本章从线性回模型开始介绍两种不同的训练模型的方法:
然后讨论多项式回归,参数比线性模式更多,更容易造成对训练数据过拟合,将通过学习曲线分辨这种情况的发生。
最后学习两种经常用于分类任务的模型:Logistic回归和Softmax回归。
线性模式就是对输入特征加权求和,再加上一个偏置项的常数,以此进行预测。
在此等式中:
也可以用向量形式更简洁的表示:
在此等式中:
这就是线性回归模型,训练模型就是设置模型参数直到模型最拟合训练集的过程。回归模型最常见的性能指标是均方根误差RMSE,因此在训练线性回归模型时,需要找到最小化RMSE的θ。通常在实际中是使MSE最小,自然RMSE也最小。线性模型的MSE成本函数如下:
为了得到使成本函数最小的θ值,有一个闭式解方法——也就是一个直接得出结果的数学方程,即标准方程:
在这个方程中:
我们可以通过以下的代码测试这个方程:
import numpy as np import matplotlib.pyplot as plt # 随机生成线性数据集 X = 2*np.random.rand(100,1) y = 4+3*X +np.random.rand(100,1) # 绘制随机数据集 plt.plot(X, y, "b.") plt.xlabel("$x_1$") plt.ylabel("$y$", rotation=0) plt.axis([0, 2, 0, 15]) plt.show() # 使用标准方程计算截距和系数值向量 X_b = np.c_[np.ones((100,1)), X] theta_best = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y) print(theta_best) # 使用上述值进行预测 X_new = np.array([[0],[2]]) X_new_b = np.c_[np.ones((2,1)), X_new] y_predict = X_new_b.dot(theta_best) print(y_predict) # 绘制预测结果 plt.plot(X_new, y_predict, "r-") plt.plot(X, y, "b.") plt.axis([0, 2, 0, 15]) plt.show()
如果使用Sklearn执行线性回归则很简单:
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X, y)
print(lin_reg.intercept_, lin_reg.coef_)
梯度下降是一种非常通用的优化算法,能够为大范围的问题找到最优解。梯度下降的中心思想就是迭代调整参数从而使成本函数最小化。
梯度下降的做法是:通过测量参数向量θ相关的误差函数的局部梯度,并不断沿着降低梯度的方向调整,直到梯度降为0,到达最小值。首先使用随机的一个θ值,然后逐步改进,每次踏出一步,每次都尝试降低一点成本函数,直到算法收敛出一个最小值。
梯度下降中一个重要参数是每一步的步长,这取决与超参数学习率,如果学习率太低,算法需要经过大量迭代才能收敛,将耗费很长时间。如果学习率太高,可能会直接越过山谷到达另一边,甚至比之前的七点还高,导致算法发散,值越来越大。
最后并不是所有的成本函数都只有一个最小值,不规则的函数会导致很难收敛到最小值。如果随机初始化,算法从左边起步,那么会收敛到一个局部最小值,而不是全局最小值。如果算法从右边起步,那么需要经过很长时间才能越过整片高原,如果停下太早,将永远达不到全局最小值。
幸运的是线性回归模型的MSE成本函数就是一个普通的凹函数,即使是乱走也能趋近全局最小值。
应用梯度下降时,需要保证所有特征值的大小比例都差不多,否则收敛的实践会长很多。
要实现梯度下降,需要计算每个参数对于成本函数的梯度影响。换言之计算的是每一种参数的偏导数。
计算梯度下降的每一步时,都是基于完整的训练集X的。这就是为什么该算法会被称为批量梯度下降:每一步都是用整批训练数据。因此面对非常庞大的数据集时,算法会变得极慢。但是梯度下降算法随特征数量拓展的表现较好,如果训练的模型拥有几十万个特征,使用梯度下降比使用标准方程或者SVD要快得多。
一旦有了梯度向量,哪个点向上,就朝反方向下坡,也就是从θ中减去▽θMSE(θ)。这时候学习率η乘以梯度向量确定下坡步长的大小。该算法的公式和快速实现如下:
eta = 0.1
n_iterations = 1000
m = 100
theta = np.random.rand(2,1)
for iteration in range(n_iterations):
gradients = 2/m*X_b.T.dot(X_b.dot(theta)-y)
theta = theta-eta*gradients
print(theta)
下面是学习率态度和学习率合适以及学习率太高的三种情况:
要找到合适的学习率可以使用网格搜索。但是可能需要限制迭代次数,这样网格搜索可以淘汰掉那些收敛耗时太长的模型。
批量梯度下降的主要问题是它要用整个训练集来计算每一步的梯度,所以训练集很大时,算法会特别慢。与之相反的极端是随机梯度下降,每一步在训练集中随机选择一个实例,并且仅基于该单个实例来计算梯度。每次迭代的数据少,算法自然也快得多。
另外由于算法的随即特性,成本函数一直在上上下下,从整体看最终会接近最小值,但是到了最小值,依旧会持续反弹,停下来的参数是足够好的,但不是最好的。当成本函数变得非常不规则时,随机梯度下降还会帮助算法跳出局部最小值,所以相比批量梯度下降,它对找到全局最小值更有优势。
随机梯度下降的好处在于可以逃离局部最优,但缺点是永远定位不出最小值。要解决这个困境,有一个办法是逐步降低学习率。开始的步长比较大,然后越来越小,让算法尽量靠近全局最小值。确定每个迭代学习率的函数叫做学习率调度。下面实现了一个简单的学习率调度实现随机梯度下降:
n_epochs = 50 t0, t1 = 5,50 def learning_schedule(t): return t0/(t+t1) theta = np.random.rand(2,1) for epoch in range(n_epochs): for i in range(m): random_index = np.random.randint(m) xi = X_b[random_index:random_index+1] yi = y[random_index:random_index+1] gradients = 2*xi.T.dot(xi.dot(theta)-yi) eta = learning_schedule(epoch*m+i) theta = theta-eta*gradients print(theta)
使用随机梯度下降时,需要保证训练实例独立且均匀分布,以确保平均而言的将参数蜡像全局最优值。确保这一点的一种简单方法是在训练过程中对实例进行随机混洗。要使用带有sklearn的随机梯度下降执行线性回归,可以使用SGDRegressor类,该类默认优化评分误差成本函数。
from sklearn.linear_model import SGDRegressor
sgd_reg = SGDRegressor(max_iter=1000, tol=1e-3, penalty=None, eta0=0.1) # 最多运行1000轮次或损失下降小于0.001为止,以0.1的学习率开始
sgd_reg.fit(X, y.ravel())
print(sgd_reg.intercept_, sgd_reg.coef_)
在每一步中,不是根据完整的或者仅一个实例的训练集来计算梯度,小批量梯度下降优于随机梯度下降的主要优点是,可以通过矩阵操作的硬件优化来提供性能,特别是在使用GPU时。
与随机梯度下降相比,这种算法在参数空间上的进展更稳定,尤其是在相当大的小批次中。结果,小批次梯度下降最终将比随机下降走得更接近最小值,但仍然很难拜托局部最小值。
下面是我们学习到的线性回归算法的比较:
如果数据比直线更复杂,可以使用线性模型来拟合非线性数据。一个简单的方法是将每个特征的幂次方添加为一个新特征,然后在此跨站特征及上训练一个线性模型。这种技术被称为多项式回归。例如下面基于一个简单的二次方程式生成一些非线性数据:
m = 100
X = 6*np.random.rand(m,1)-3
y = 0.5*X**2+X+2+np.random.rand(m,1)
plt.plot(X, y, "b.")
plt.xlabel("$x_1$", fontsize=18)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.axis([-3, 3, 0, 10])
plt.show()
显然一条直线永远也无法正确拟合此数据。因此,我们可以使用Sklearn的PolynomiaFeatures类来转换训练数据,将训练集中的每个特征的平方添加为新特征。
from sklearn.preprocessing import PolynomialFeatures
poly_features = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly_features.fit_transform(X)
print(X[0], "\n", X_poly[0])
现在X_poly包含了原始的X特征和该特征的平方,可以将LinearRegression模型拟合到此扩展训练数据中:
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X_poly, y)
print(lin_reg.intercept_, lin_reg.coef_)
当存在多个特征时,多项式回归能找到特征之间的关系(这是普通线性回归模型无法做到的),PolynomiaFeatures还可以将特征的所有组合添加到给定的多项式阶数。
高阶多项式回归与普通线性回归相比,拟合数据可能会更好,但也同样更容易造成过拟合。判断模型是否过拟合和欠拟合在交叉验证中提到过:如果模型在训练数据上表现良好,但根据交叉验证的指标泛化较差,说明过拟合;如果两者表现均不理想,则说明欠拟合。
其实还有一种方式是观察学习曲线:这个曲线绘制的是模型在训练集和验证集上关于训练集大小的性能函数。要生成这个曲线,只需要在不同大小的训练子集上多次训练模型即可。下面这段代码给训练集下定义了一个函数,绘制模型的学习曲线:
from sklearn.metrics import mean_squared_error from sklearn.model_selection import train_test_split def plot_learning_curves(model, X, y): X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2) train_errors, val_errors = [],[] for m in range(1, len(X_train)): model.fit(X_train[:m], y_train[:m]) y_train_predict = model.predict(X_train[:m]) y_val_predict = model.predict(X_val) train_errors.append(mean_squared_error(y_train[:m], y_train_predict)) val_errors.append(mean_squared_error(y_val, y_val_predict)) plt.plot(np.sqrt(train_errors), "r+", linewidth=2, label="train") plt.plot(np.sqrt(val_errors), "b-", linewidth=3, label="val") lin_reg = LinearRegression() plot_learning_curves(lin_reg, X, y) plt.show()
当训练集中只有一个或两个实例时,模型可以很好的拟合,这也就是曲线从0开始的原因。但是,随着将新实例添加到训练集中,模型就不可能完美的拟合训练数据,这既因为数据有噪声,又因为它根本不是线性的。因此误差会一直上升,直至平稳状态。这两条学习曲线是典型的欠拟合模型,两条曲线都达到了平稳状态,它们很接近而且很高。
下面是相同数据上的10阶多项式模型的学习曲线:
from sklearn.pipeline import Pipeline
polynomial_regression = Pipeline([
("polu_features", PolynomialFeatures(degree=10, include_bias=False)),
("lin_reg", LinearRegression()),
])
plot_learning_curves(polynomial_regression, X, y)
plt.axis([0, 80, 0, 3])
plt.show()
这点曲线跟上面的曲线有两个非常重要的区别:
改善过拟合模型的一种方法是向其提供更多的训练数据,直到验证误差达到训练误差为止。
减少过拟合的一个好方法是对模型及逆行正则化(即约束模型):它拥有的自由度越少,则拟合数据的难度越大。正则化多项式模型的一种简单方法是减少多项式的次数。对于线性模型,正则化通常是通过约束模型的权重来实现的。下面有三种方式实现了限制权重的方法。
岭回归是线性回归的正则化版本,这迫使学习算法不仅拟合数据,而且还使模型权重尽可能小。注意仅在训练期间将正则化项添加到成本函数中。训练完模型后,要使用非正则化的性能度量来评估模型的性能。
训练过程中使用的成本函数与用于测试的性能指标不同很常见,其中有一个原因是:好的训练成本函数应该具有对优化友好的导数,而用于测试的性能指标应尽可能接近最终目标。
超参数a控制要对模型进行正则化的程度。如果a=0,则岭回归仅是线性回归;如果a非常大,则所有权重最终都非常接近于零,结果是一条经过数据均值的平线。岭回归成本函数如下:
在执行岭回归之前放缩数据很重要,因为它对输入特征的缩放敏感。
下图展示了不同的a值对某些线性数据进行训练的几种岭模型。左侧使用的是普通岭模型,导致了线性预测。右侧先使用PolynomialFeatures扩展数据,然后使用StandardScaler进行缩放,最后将岭模型应用于结果特征:这是带有岭正则化的多项式回归。请注意,a的增加会导致更平滑的预测,从而减少了模型的方差,但增加了偏差。
与线性回归一样,可以通过计算闭合形式的方程或执行梯度下降来执行岭回归。以下是用Sklearn和闭式解来执行岭回归的方法:
from sklearn.linear_model import Ridge
ridge_reg = Ridge(alpha=1, solver="cholesky")
ridge_reg.fit(X, y)
print(ridge_reg.predict([[1.5]]))
sgd_reg = SGDRegressor(penalty="l2")
sgd_reg.fit(X, y.ravel())
print(sgd_reg.predict([[1.5]]))
超参数penalty设置的是使用正则项的类型。设为“l2”表示希望SGD在成本函数中添加一个正则项,等于权重向量的l2范数的平方的一半,即岭回归。
线性回归的另一种正则化叫做最小绝对收缩和选择算子回归,简称Lasso回归。与岭回归一样,它也是向成本函数添加一个正则项,但是它增加的是权重向量的l1范数,而不是l2范数的平方的一半。Lasso回归成本函数如下:
Lasso回归的一个重要特点是它倾向于完全消除掉最不重要特征的权重(也就是将它们设置为0)。Lasso回归会自动执行特征选择并输出一个稀疏模型。Lasso有两个主要区别:
为了避免Lasso在梯度下降时最终在最优解附近反弹,你需要逐渐降低训练期间的学习率(它仍然会在最优解附近反弹,但是步长会越来越小,因此会收敛)。
Lasso成本函数在theta_i=0处不可微,但是如果使用子梯度向量g代替任何theta_i=0,则梯度下降仍然可以正常工作。下式显示了可用于带有Lasso成本函数的梯度下降的子梯度向量方程。
下面是使用Lasso类的sklearn示例:
from sklearn.linear_model import Lasso
las_reg = Lasso(alpha=0.1)
las_reg.fit(X, y)
print(las_reg.predict([[1.5]]))
弹性网络是介于岭回归和Lasso回归之间的中间地带。正则项是岭和Lasso正则项的简单混合,可以控制混合比例r。当r=0时,弹性网络等于岭回归;当r=1时,弹性网络等效于Lasso回归。弹性网络成本函数如下:
对于这几种线性回归的使用场景,通常来说,有正则化都比没有更可取,所以大多数情况下要避免使用纯线性回归。岭回归是一个不错的选择,但是实际用到的特征较少,那么更倾向于使用Lasso和弹性网络,因为他们将无用特征的权重降为0。一般来说,弹性网络优于Lasso回归,以为特征数量超过训练实例数量,又或者说几个特征强相关时,Lasso回归的表现可能非常不稳定。
下面是基于sklearn弹性网络的示例,l1_ration对应于混合比r:
from sklearn.linear_model import ElasticNet
elastic_reg = ElasticNet(alpha=1, l1_ratio=0.5)
elastic_reg.fit(X, y)
print(elastic_reg.predict([[1.5]]))
对于梯度下降这类迭代学习的算法,还有一个与众不同的正则化方法,就是在验证误差达到最小值时停止训练,该方法叫做提前停止法。
和下图展示了用批量梯度下降的高阶多项式回归模型,经过迭代后,训练集上的RMSE不断下降,验证机集上的RMSE也下降,但是到达最低点后升高,说明进入了过拟合。通过提前停止法,一旦验证误差达到最小值就立即停止训练。
使用随机和小批量梯度下降时,曲线不是那么平滑,可能很难知道是否达到了最小值。一种解决方法啊是仅在验证错误超过最小值一段时间后停止,然后回滚模型参数到验证误差最小的位置。
from sklearn.base import clone from sklearn.preprocessing import StandardScaler poly_scaler = Pipeline([ ("poly_features", PolynomialFeatures(degree=90, include_bias=False)), ("std_scaler", StandardScaler()) ]) X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2) X_train_poly_scaled = poly_scaler.fit_transform(X_train) X_val_poly_scaled = poly_scaler.transform(X_val) sgd_reg = SGDRegressor(max_iter=1, tol=np.infty, warm_start=True, penalty=None, learning_rate="constant", eta0=0.0005) minimum_val_error = float("inf") best_epoch = None best_model = None for epoch in range(1000): sgd_reg.fit(X_train_poly_scaled, y_train) y_val_predit = sgd_reg.predict(X_val_poly_scaled) val_error = mean_squared_error(y_val, y_val_predit) if val_error<minimum_val_error: minimum_val_error = val_error best_epoch = epoch best_model = clone(sgd_reg)
与线性回归模型一样,逻辑回归模型也是计算输入特征的加权和(加上偏置项),但是不同于线性回归的直接输出结果,它输出的是结果的数理逻辑值。逻辑回归模型的估计概率公式如下(向量形式):
逻辑记为σ,输出一个介于0~1的数字,一旦逻辑回归模型估算出实例x属于正类的概率,就可以根据0.5的概率中点做出分类预测。逻辑函数及其图像如下:
注意,当t<0时,σ(t)<0.5;当t≥0时,σ(t)≥0.5。所以如果xTθ是正类,逻辑回归模型预测结果是1,如果是负类,则预测为0。
分数t通常称为logit,定义为logit§=log(p/(1–p))的logit函数与logistic函数相反。确实,如果你计算估计概率p的对数,则会发现结果为t。对数也称为对数奇数,因为它是正类别的估计概率与负类别的估计概率之比的对数。
训练的目的就是i设置参数向量θ,使模型对正类实例做出高概率估算(y=1),对负类实例做出低概率估算(y=0)。单个训练实例的成本函数如下:
这个成本函数是有道理的,因为当t接近于0时,-log(t)会变得非常大,所以如果模型估算一个正类实例的概率接近于0,成本将会变得很高。同理估算出一个负类实例的概率接近1,成本也会变得非常高。那么反过来,当t接近于1的时候,-log(t)接近于0,所以对一个负类实例估算出的概率接近于0,对一个正类实例估算出的概率接近于1,而成本则都接近于0。
整个训练集的成本函数是所有训练实例的平均成本。可以用一个称为对数损失的单一表达式来表示:
坏消息是,这个函数没有已知的闭式方程(不存在一个标准方程的等价方程)来计算出最小化成本函数的θ值。而好消息是这是个凸函数,所以通过梯度下降(或是其他任意优化算法)保证能够找出全局最小值(只要学习率不是太高,你又能长时间等待)。下式给出了成本函数关于第j个模型参数θj的偏导数方程:
对于每个实例,它都会计算预测误差并将其乘以第j个特征值,然后计算所有训练实例的平均值。一旦你有了包含所有偏导数的梯度向量就可以使用梯度下降算法了。对于随机梯度下降,一次使用一个实例;对于小批量梯度下降,一次使用一个小批量。
这里使用著名的鸢尾花数据集说明逻辑回归。数据集共有150朵鸢尾花,分别来自三个不同品种(山鸢尾、变色鸢尾和维吉尼亚鸢尾),数据里包含花的萼片以及花瓣的长度和宽度。接下来基于花瓣宽度这一个特征,创建一个分类器来检测维吉尼亚鸢尾花。
import numpy as np from sklearn import datasets from sklearn.linear_model import LogisticRegression import matplotlib.pyplot as plt # 加载数据集 iris = datasets.load_iris() print(list(iris.keys())) # 准备花瓣宽度和结果 X = iris["data"][:,3:] # 花瓣宽度 y = (iris["target"]==2).astype(np.int) # 模型训练 log_reg = LogisticRegression() log_reg.fit(X, y) # 观察宽度在0~3cm的模型估算概率 X_new = np.linspace(0, 3, 1000).reshape(-1, 1) y_proba = log_reg.predict_proba(X_new) decision_boundary = X_new[y_proba[:, 1] >= 0.5][0] # 绘制维吉尼亚鸢尾花的预测曲线外,加上其余两种类型的分布,标注决策边界 plt.figure(figsize=(8, 3)) plt.plot(X[y==0], y[y==0], "bs") plt.plot(X[y==1], y[y==1], "g^") plt.plot([decision_boundary, decision_boundary], [-1, 2], "k:", linewidth=2) plt.plot(X_new, y_proba[:, 1], "g-", linewidth=2, label="Iris virginica") plt.plot(X_new, y_proba[:, 0], "b--", linewidth=2, label="Not Iris virginica") plt.text(decision_boundary+0.02, 0.15, "Decision boundary", fontsize=14, color="k", ha="center") plt.arrow(decision_boundary, 0.08, -0.3, 0, head_width=0.05, head_length=0.1, fc='b', ec='b') plt.arrow(decision_boundary, 0.92, 0.3, 0, head_width=0.05, head_length=0.1, fc='g', ec='g') plt.xlabel("Petal width (cm)", fontsize=14) plt.ylabel("Probability", fontsize=14) plt.legend(loc="center left", fontsize=14) plt.axis([0, 3, -0.02, 1.02]) plt.show()
维吉尼亚鸢尾(三角形所示)的花瓣宽度范围为1.4~2.5cm,而其他两种鸢尾花(正方形所示)花瓣通常较窄,花瓣宽度范围为0.1~1.8cm。注意,这里有一部分重叠。对花瓣宽度超过2cm的花,分类器可以很有信心地说它是一朵维吉尼亚鸢尾花(对该类别输出一个高概率值),对花瓣宽度低于1cm以下的,也可以胸有成竹地说其不是(对“非维吉尼亚鸢尾”类别输出一个高概率值)。在这两个极端之间,分类器则不太有把握。但是,如果你要求它预测出类别(使用predict()方法而不是predict_proba()方法),它将返回一个可能性最大的类别。也就是说,在大约1.6cm处存在一个决策边界,这里“是”和“不是”的可能性都是50%,如果花瓣宽度大于1.6cm,分类器就预测它是维吉尼亚鸢尾花,否则就预测不是(即使它没什么把握)。
sklearn的逻辑回归默认添加l2函数正则化,且超参数不是alpha,而是反值C。C值越高,正则化越少。
逻辑回归模型经过推广,可以直接支持多个类别,而不需要训练并组合多个二元分类器。这就是Softmax回归,或者叫做多元逻辑回归。
原理如下:给定一个实例x,Softmax回归模型首先计算出每个类k的分数sk(x),然乎对这些分数应用softmax函数,估算出每个类的概率。类k的Softmax分数公式如下:
每个类都有自己特定的参数向量θ(k)。所有这些向量通常都作为行存储在参数矩阵中。一旦为实例计算了每个类的分数,就可以通过softmax函数来估算实例属于该类的概率。该函数计算每个分数的指数,然后对其进行归一化。Softmax函数如下:
在此等式中:
跟逻辑回归分类器一样,Softmax回归分类器预测具有最高估计概率的类,如下:
Softmax回归分类器一次只能预测一个类,因此只能和互斥的类一起使用。无法使用它在一张照片内识别多个人。
现在已经知道了模型如何进行概率估算并作出预测,那么训练目标就是得到一个能对目标类做出高概率估算的模型。通过实现下式的交叉熵的最小化来达到目标,一位内模型对目标类做出较低概率的估算时会受到惩罚。交叉熵经常用于衡量一组估算出的类概率跟目标类的匹配程度。
y(j, k)是属于第i个实例的目标概率,一般等于1或0。当只有两个类时,此成本函数等效于逻辑回归的成本函数。
现在,你可以计算每个类的梯度向量,然后使用梯度下降(或任何其他优化算法)来找到最小化成本函数的参数矩阵,使用Softmax回归将鸢尾花分为三类,当用两个以上的类训练的时候,Sklearn的LogisticRegression默认使用一对多的训练方式,不过将超参数multi_class设置为“multinomial”,可以切换为Softmax回归。还必须指定一个支持Softmax回归的求解器,如“lbfgs”。默认使用l2正则化,通过超参数C控制:
X = iris["data"][:,(2,3)]
y = iris["target"]
softmax_reg = LogisticRegression(multi_class="multinomial", solver="lbfgs", C=10)
softmax_reg.fit(X, y)
print(softmax_reg.predict([[5,2]]))
print(softmax_reg.predict_proba([[5,2]]))
下图展示了不同背景色表示的决策边界,任意两个类之间的边界是线性的。该模型预测出的类,估算概率可能低于50%,在所有决策边界相交的地方,所有类的估算概率为33%。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。