机器学习逻辑回归sklearn实现

https://blog.csdn.net/qq_2467...

July 6, 2019 · 1 min · jiezi

机器学习之分类结果的评价

以逻辑回归为例,介绍分类结果的评价方式。精准率和召回率对于极度偏斜的数据,使用分类准确度来评判模型的好坏是不恰当的,精确度和召回率是两个更好的指标来帮助我们判定模型的好快。二分类的混淆矩阵精准率和召回率是存在于混淆矩阵之上的,以二分类为例,分类0是偏斜数据中占优势的一方,将关注的重点放在分类为1上,其混淆矩阵如下:真实值预测值0109978(TN)12(FP)12(FN)8(TP)TN 的含义是预测 negative 正确的数量,即真实分类为0预测的分类结果也为0的共有9978个;FN 的含义是预测 negative 错误的数量,即真实分类为1预测的分类结果为0的共有2个;FP 的含义是预测 positive 错误的数量,即真实分类为0预测的分类结果为1的共有12个;TP 的含义是预测 positive 正确的数量,即真实分类为1预测的分类结果也为1的共有8个。TN、FN、FP、TP 的实现如下(y_true 表示真实分类, y_predict 表示预测结果):import numpy as npdef TN(y_true, y_predict): return np.sum((y_true == 0) & (y_predict == 0))def FP(y_true, y_predict): return np.sum((y_true == 0) & (y_predict == 1))def FN(y_true, y_predict): return np.sum((y_true == 1) & (y_predict == 0))def TP(y_true, y_predict): return np.sum((y_true == 1) & (y_predict == 1))混淆矩阵的结果为:def confusion_matrix(y_test, y_predict): return np.array([ [TN(y_test, y_log_predict), FP(y_test, y_log_predict)], [FN(y_test, y_log_predict), TP(y_test, y_log_predict)] ])Scikit Learn 中封装了混淆矩阵方法 confusion_matrix():from sklearn.metrics import confusion_matrixconfusion_matrix(y_true, y_predict)精准率和召回率及实现有了混淆矩阵,精准率和召回率久很好表示了。精准率表示预测分类结果中预测正确的数量的占比,即:$$precision=\frac{TP}{TP+FP}$$将其用代码表示为:def precision_score(y_true, y_predict): tp = TP(y_true, y_predict) fp = FP(y_true, y_predict) try: return tp / (tp + fp) except: return 0.0召回率表示真实分类中被预测正确的数量的占比,即:$$recall=\frac{TP}{TP+FN}$$将其用代码表示为:def recall_score(y_true, y_predict): tp = TP(y_true, y_predict) fn = FN(y_true, y_predict) try: return tp / (tp + fn) except: return 0.0Scikit Learn 中也封装了计算精准率的方法 precision_score() 和计算召回率的方法 recall_score():from sklearn.metrics import precision_scoreprecision_score(y_true, y_predict)from sklearn.metrics import recall_scorerecall_score(y_true, y_predict)F1 Score精准率和召回率这两个指标的侧重点不同,有的时候我们注重精准率(如股票预测),有的时候我们注重召回率(病人诊断)。但有时候又需要把两者都考虑进行,此后就可以使用 F1 Score 指标。F1 Score 是精准率和召回率的调和平均值,公式为:$$\frac{1}{F1}=\frac{1}{2}(\frac{1}{precision}+\frac{1}{recall})$$即:$F1=\frac{2·precision·recall}{precesion+recall}$,并且 F1 Score 的取值是在区间 $[0, 1]$ 之中的。代码实现为:def f1_score(y_true, y_predict): precision = precision_score(y_true, y_predict) recall = recall_score(y_true, y_predict) try: return 2 * precision * recall / (precision + recall) except: return 0.0Scikit Learn 中封装了方法 f1_score() 来计算 F1 Score:from sklearn.metrics import f1_scoref1_score(y_true, y_predict)Precision-Recall 曲线Scikit Learn 的逻辑回归中的概率公式为 $\hat p=\sigma(\theta^T·x_b)$ ,其决策边界为 $\theta^T·x_b=0$,但是如果决策边界不为0会如何呢?假定决策边界为 $\theta^T·x_b=threshold$,当 threshold 的取值不同(0、大于0的某个值、小于0的某个值),对应的精确度和召回率也不同。如图:圆形和星形是不同的的分类,并且重点放在星形的分类上,可以看出,threshold 的取值越大时,精确率越高,而召回率越低。如果要更改决策边界,逻辑回归的 decision_function() 返回传入数据的 $\theta^T·x_b$ 计算结果 decision_scores,接着再构建一个新的预测结果;如代码所示,设定 decision_scores >= 5(默认decision_scores >= 0 ) 的预测结果才为1,其余为0:from sklearn.linear_model import LogisticRegressionlog_reg = LogisticRegression()# X_train, y_train 为训练数据集log_reg.fit(X_train, y_train)# X_test,y_test 为测试数据集decision_scores = log_reg.decision_function(X_test)y_log_predict_threshold = np.array(decision_scores >= 5, dtype=‘int’)如此可以得到不同的 y_log_predict_threshold,进而得到不同的精准率和召回率。以手写数字识别数据为例,将标记为9的数据分类为1,其余分类为0:from sklearn import datasetsdigits = datasets.load_digits()X = digits.datay = digits.target.copy()y[digits.target==9] = 1y[digits.target!=9] = 0from sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X, y, random_state=500)接着训练逻辑回归模型:from sklearn.linear_model import LogisticRegressionlog_reg = LogisticRegression()log_reg.fit(X_train, y_train)获取测试数据集 X_test 对应的 $\theta^T·x_b$ 取值:decision_scores = log_reg.decision_function(X_test)在 decision_scores 的取值区间划分为一系列值 thresholds ,并将其中的值依次作为决策边界,进而得到不同的精确率和召回率from sklearn.metrics import precision_scorefrom sklearn.metrics import recall_scoreprecisions = []recalls = []thresholds = np.arange(np.min(decision_scores), np.max(decision_scores), 0.1)for threshold in thresholds: y_predict = np.array(decision_scores >= threshold, dtype=‘int’) precisions.append(precision_score(y_test, y_predict)) recalls.append(recall_score(y_test, y_predict))将精确率、召回率与决策边界的关系绘制如图:精确度与召回率的关系,即 Precision-Recall 曲线则为:Scikit Learn 中提供的 precision_recall_curve() 方法传入真实分类结果和 decision_scores,返回 precisions、recalls 和 thresholds:from sklearn.metrics import precision_recall_curveprecisions, recalls, thresholds = precision_recall_curve(y_test, decision_scores)Precision-Recall 曲线越靠外(即面积越大)则表示模型越好。多分类中的精确率和召回率在过分类中,Sckit Learn 提供的 confusion_matrix() 可以直接返回多分类的混淆矩阵,而对于精确率和召回率,则要在 Sckit Learn 提供的方法中指定 average 参数值为 micro,如:from sklearn.metrics import precision_scoreprecision_score(y_test, y_predict, average=‘micro’)ROC 曲线对于用图形面积判断模型好快,ROC 曲线比 Precision-Recall 曲线要好。ROC 曲线涉及两个指标,TPR 和 FPR。TPR 就是召回率,即:$TPR=\frac{TP}{TP+FN}$;FPR 表示真实分类(偏斜数据中占优势的分类)中被预测错误的数量的占比,即:$FPR=\frac{FP}{TN+FP}$。实现代码为:def TPR(y_true, y_predict): tp = TP(y_true, y_predict) fn = FN(y_true, y_predict) try: return tp / (tp + fn) except: return 0.0def FPR(y_true, y_predict): fp = FP(y_true, y_predict) tn = TN(y_true, y_predict) try: return fp / (fp + tn) except: return 0.0对于决策边界的不同,这两个指标的变化趋势是一致的。还是以上面的手写数字识别数据为例,计算不同决策边界下的两指标的值为:fprs = []tprs = []for threshold in thresholds: y_predict = np.array(decision_scores >= threshold, dtype=‘int’) fprs.append(FPR(y_test, y_predict)) tprs.append(TPR(y_test, y_predict))作出的 TPR 和 FPR 的关系图(即 ROC 曲线)为:Scikit Learn 中提供的 roc_curve() 方法传入真实分类结果和 decision_scores,返回 TPR、FPR 和 thresholds:from sklearn.metrics import roc_curvefprs, tprs, thresholds = roc_curve(y_test, decision_scores)roc_auc_score() 方法传入真实分类结果和 decision_scores,返回 ROC 曲线表示的面积。from sklearn.metrics import roc_auc_scoreroc_auc_score(y_test, decision_scores)面积越大,则模型越好。源码地址Github | ML-Algorithms-Action ...

December 13, 2018 · 2 min · jiezi

机器学习之逻辑回归

逻辑回归将样本特征和样本发生的概率联系起来,用于解决分类问题。Sigmoid 函数在最简单的二分类中,逻辑回归里样本发生的概率的值域为 [0, 1],对于线性回归 $\hat{y} = \theta^T·x_b$,为了将 $\hat y$ 映射到值域 [0, 1] 中,引入了 $\sigma$ 函数得到了概率函数 $\hat p$,即:$$\hat p=\sigma(\theta^T·x_b), \hat p\in[0, 1]$$Sigmoid 函数 $\sigma$ 表示为:$\sigma(t)=\frac{1}{1+e^{-t}}$,图示如下:当 t > 0 时,$\sigma$ > 0.5;当 t < 0 时,$\sigma$ < 0.5。因此可对二分类的分类方式为:$$\hat y=\begin{cases} 1, & \hat p \geq 0.5 \ 0, & \hat p \leq 0.5 \end{cases}; \hat p=\sigma(\theta^T·x_b)=\frac{1}{1+e^{-\theta^T·x_b}}$$损失函数如果实际的分类为1,p 越小时,损失越大;如果实际的分类为0,p 越大时,损失越大。引入 log 函数表示则为:$$-ylog(\hat p)-(1-y)log(1-\hat p)$$当 y=0 时,损失为 $-log(1-\hat p)$;当 y=1 时,损失为 $-log(\hat p)$。对于有 m 样本的数据集 (X, y),损失函数为:$$J(\theta)=-\frac{1}{m}\sum_{i=1}^my^{(i)}log(\sigma(X_b^{(i)}\theta))+(1-y^{(i)})log(1-\sigma(X_b^{(i)}\theta))$$其中:$X_b^{(i)} = (1,x_{1}^{(i)},x_{2}^{(i)},…,x_{n}^{(i)})$;$\theta = (\theta_{0}, \theta_{1}, \theta_{2},…, \theta_{n})^T$。损失函数的梯度为了得到在损失尽可能的小的情况下的 $\theta$,可以对 $J(\theta)$ 使用梯度下降法,结果为:$$\nabla J(\theta) = \frac{1}{m}·\begin{pmatrix} \sum_{i=1}^{m}(\sigma(X_b^{(i)}\theta) - y^{(i)}) \\ \sum_{i=1}^{m}(\sigma(X_b^{(i)}\theta) - y^{(i)})·X_1^{(i)} \\ \sum_{i=1}^{m}(\sigma(X_b^{(i)}\theta) - y^{(i)})·X_2^{(i)} \\ \cdots \\ \sum_{i=1}^{m}(\sigma(X_b^{(i)}\theta) - y^{(i)})·X_n^{(i)} \end{pmatrix}$$略去了公式的推导过程。进行向量化处理后结果为:$$\nabla J(\theta) = \frac{2}{m}·X_b^T·(\sigma(X_b\theta)-y)$$实现二分类逻辑回归算法使用 Scikit Learn 的规范将逻辑回归的过程封装到 LogisticRegression 类中。init() 方法首先初始化逻辑回归模型,theta 表示 $\theta$,interception 表示截距,chef_ 表示回归模型中自变量的系数:class LogisticRegression: def init(self): self.coef_ = None self.interceiption_ = None self._theta = None_sigmoid() 方法实现 Sigmoid 函数:def sigmoid(self, t): return 1 / (1 + np.exp(-t))fit() 方法根据训练数据集训练模型,J() 方法计算损失 $J\theta$,dJ() 方法计算损失函数的梯度 $\nabla J(\theta)$,gradient_descent() 方法就是梯度下降的过程,X_b 表示添加了 $x{0}^{(i)}\equiv1$ 的样本特征数据:def fit(self, X_train, y_train, eta=0.01, n_iters=1e4): def J(theta, X_b, y): y_hat = self._sigmoid(X_b.dot(theta)) try: return - np.sum(y * np.log(y_hat) + (1 - y) * np.log(1- y_hat) ** 2) / len(y) except: return float(‘inf’) def dJ(theta, X_b, y): return X_b.T.dot(self._sigmoid(X_b.dot(theta)) - y) /len(y) def gradient_descent(X_b, y, initial_theta, eta, n_iters=n_iters, epsilon=1e-8): theta = initial_theta i_ters = 0 while i_ters < n_iters: gradient = dJ(theta, X_b, y) last_theta = theta theta = theta - eta * gradient if (abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon): break i_ters += 1 return theta X_b = np.hstack([np.ones((len(X_train), 1)), X_train]) initial_theta = np.zeros(X_b.shape[1]) self.theta = gradient_descent(X_b, y_train, initial_theta, eta) self.interception = self.theta[0] self.coef = self._theta[1:] return selfpredict_proba() 将传入的测试数据与训练好模型后的 $\theta$ 经过计算后返回该测试数据的概率:def predict_proba(self, X_predict): X_b = np.hstack([np.ones((len(X_predict), 1)), X_predict]) return self.sigmoid(X_b.dot(self.theta))predict() 方法将经过 predict_proba() 方法得到的测试数据的概率以 0.5 为界转换成类别(0或1):def predict(self, X_predict): proba = self.predict_proba(X_predict) return np.array(proba >= 0.5, dtype=‘int’)score() 将测试数据集的预测分类与实际分类进行比较计算模型准确度:def score(self, X_test, y_test): y_predict = self.predict(X_test) return sum(y_predict == y_test) / len(y_test)决策边界对于 $\hat p=\sigma(\theta^T·x_b)=\frac{1}{1+e^{-\theta^T·x_b}}$,要使 $\hat p=0.5$ 则 $\theta^T·x_b=0$,这就是决策边界。假设 X 数据集只有两个特征,则由 $\theta_0+\theta_1x_1+\theta_2x_2=0$ 得到 $x_2$ 和 $x_1$ 的关系为:$$x_2=\frac{-\theta_0-\theta_1x_1}{\theta_2}$$如图所示,图中的点为只有两个特征的数据,纵轴为特征 $x_2$,横轴为特征 $x_1$,梯度下降法得到的 $\theta$ 与上面公式计算后的决策边界即为图中斜线:逻辑回归中使用多项式特征对于多项式回归,如对 $y=x_1^2+x_2^2-r$ 进行逻辑回归,可以将 $x_1^2$ 看作一个特征 $z_1$,将 $x_2^2$ 看作一个特征 $z_2$,Scikit Learn 提供了 PolynomialFeatures 可以方便的进行转换。举例如下。首先准备数据:import numpy as npX = np.random.normal(0, 1, size=(200, 2))y = np.array(X[:, 0] ** 2 + X[:, 1] ** 2 < 1.5, dtype=‘int’)数据可视化如图:使用前面的 LogisticRegression 类进行逻辑回归,并且使用 Scikit Learn 的 Pipeline 将多项式特征、数据归一化和逻辑回归组合在一起:from LogisticRegression import LogisticRegressionfrom sklearn.pipeline import Pipelinefrom sklearn.preprocessing import PolynomialFeaturesfrom sklearn.preprocessing import StandardScalerdef PolynomailLogisticRegression(degree): return Pipeline([ (‘poly’, PolynomialFeatures(degree=degree)), (‘std_scaler’, StandardScaler()), (’log_reg’, LogisticRegression()) ])设定 PolynomialFeatures 处理后得到的新的特征数据最高维度为2,然后 fit() 方法训练模型:poly_log_reg = PolynomailLogisticRegression(degree=2)poly_log_reg.fit(X, y)得到模型可视化如图:Scikit Learn 中的逻辑回归Scikit Learn 中的 linear_model 模块中也提供了逻辑回归的算法,同时也封装了模型正则化相关的内容。根据正则化中的正则项的不同,正则化的方式主要有四种:$J(\theta)+\alpha L_1$$J(\theta)+\alpha L_2$$C·J(\theta)+L_1$$C·J(\theta)+L_2$Scikit Learn 中的逻辑回归算法的模型正则化采用后两种的方式。L1 为 L1正则项,即 $\sum{i=1}^n|\theta_i|$,LASSO 回归使用了L1;L2 为 L2正则项,即 $\frac{1}{2}\sum{i=1}^n\theta_i^2$,岭回归使用了L2;Scikit Learn 的逻辑回归算法中的参数 c 设定 C 的大小,参数 penalty 设定使用哪种正则项(l1 或 l2)。使用方式如下:from sklearn.linear_model import LogisticRegressiondef PolynomailLogisticRegression(degree, C, penalty=‘l2’): return Pipeline([ (‘poly’, PolynomialFeatures(degree=degree)), (‘std_scaler’, StandardScaler()), (’log_reg’, LogisticRegression(C=C, penalty=penalty)) ])poly_log_reg = PolynomailLogisticRegression(degree=20, C=0.1, penalty=‘l1’)poly_log_reg.fit(X_train, y_train)OvR 与 OvO前面说的都是二分类的逻辑回归,如果要进行多分类的逻辑回归,有 OvR 和 OvO 两种方式。OvR(One vs Rest)将多类别简化成其中一个类别和其余类别为一个类别这种二分类,因此 n 个类别就进行 n 次分类,对于新的数据,看它在这 n 个分类结果中哪个分类得分最高即为哪个类别。OvO(One vs One)在多类别中选取两个类别作为二分类,因此 n 个类别就进行 $C_n^2$ 次分类,对于新的数据,看它在这 $C_n^2$ 次分类结果中数量最大即为哪个类别。Scikit Learn 的逻辑回归算法中的参数 multi_class 用于设定使用 OvR(参数值为 ovr)还是 OvO(参数值为 multinomial),如:LogisticRegression(multi_class=‘ovr’)LogisticRegression(multi_class=‘multinomial’)同时 Scikit Learn 中的 multiclass 模块中也提供了 OneVsRestClassifier(OvR)类和 OneVsOneClassifier(OvO)类,可以将任意的二分类算法(要求符合 Scikit Learn 规范)应用在这两个类上完成多分类。使用方式如下:# OvRfrom sklearn.multiclass import OneVsRestClassifierovr = OneVsRestClassifier(LogisticRegression())ovr.fit(X, y)# OvOfrom sklearn.multiclass import OneVsOneClassifierovo = OneVsOneClassifier(log_reg)ovo.fit(X, y)Github | ML-Algorithms-Action ...

December 8, 2018 · 3 min · jiezi

机器学习之多项式回归与模型泛化

多项式回归多项式回归使用线性回归的基本思路非线性曲线如图:假设曲线表达式为:$y=ax^2+bx+c$,如果将 $x^2$ 看作为 $x_1$,即 $y_1=ax_1+bx+c$,此时就有了两个特征,则可以看作是线性曲线表达式。首先生成一组样本数据:import numpy as npimport matplotlib.pyplot as pltx = np.random.uniform(-3, 3, size=100)X = x.reshape(-1, 1)y = 0.5 * x2 + x + 2 + np.random.normal(0, 1, size=100)(x, y) 如图所示(横轴为 x,纵轴为 y):接着在 $X$ 的基础上增加一个新的特征 $x1(x^2)$ 形成一个新的 $X2$:X2 = np.hstack([X, X2])再使用线性回归算法:from sklearn.linear_model import LinearRegressionlin_reg = LinearRegression()lin_reg.fit(X2, y)y_predict = lin_reg.predict(X2)此时对 $X2$ 的预测值反映到图中就是第一张图里的曲线。PolynomialFeatures 和 Pipeline对于增加新的特征(如:$x^2$),Scikit Learn 提供了 PolynomialFeatures,使用方式如下:from sklearn.preprocessing import PolynomialFeaturespoly = PolynomialFeatures(degree=2)poly.fit(X)X2 = poly.transform(X)参数 degree 表示最高次幂;得到的新的 $X2$ 前5行数据如下:# X2[:5,:]array([[ 1. , 1.16207716, 1.35042333], [ 1. , -2.62969804, 6.91531181], [ 1. , 0.99966958, 0.99933928], [ 1. , 0.35525362, 0.12620514], [ 1. , -2.48933626, 6.19679503]])第一列为 $x^0$,第二列为 $x$,第三列为 $x^2$。Scikit Learn 还提供了 Pipeline,将多项式特征、数据归一化和线性回归组合在了一起,大大方便的编程的过程。使用方式如下:from sklearn.pipeline import Pipelinefrom sklearn.preprocessing import StandardScalerfrom sklearn.linear_model import LinearRegressionpoly_regression = Pipeline([ (“poly”, PolynomialFeatures(degree=2)), (“std_scaler”, StandardScaler()), (“lin_reg”, LinearRegression())])Pipeline() 传入的是一个列表,包含了执行每一个步骤的实例,每一个步骤又是一个元组类型,其中第二个表示实例,第一个表示给实例取的名称。接着就可以进行 fit()、predict() 等内容了:poly_regression.fit(X, y)y_predict = poly_regression.predict(X)欠拟合和过拟合欠拟合和过拟合的理解在使用多项式回归的过程中需要考虑一个问题,即欠拟合和过拟合。如果对前面的样本单单使用线性回归,得到的模型如图(这里省略的代码实现):训练出来的模型很简单,但它并不能完整的表述数据之间的关系,这就是欠拟合(underfitting)。如果使用多项式回归,代码如下:def PolynomialRegression(degree): return Pipeline([ (“poly”, PolynomialFeatures(degree=degree)), (“std_scaler”, StandardScaler()), (“lin_reg”, LinearRegression()) ])poly100_reg = PolynomialRegression(degree=100)poly100_reg.fit(X, y)y_predict100 = poly100_reg.predict(X)将 degree 设置为100,即最高次幂为 100,训练出来的模型对训练数据 X 的预测结果如图:可见该模型对训练数据解释的很好,但是如果用测试数据来预测一下:X_plot = np.linspace(-3, 3, 100).reshape(-1, 1)y_plot = poly100_reg.predict(X_plot)可以看出对预测数据预测的非常糟糕,这是因为训练的模型过多的表达了训练数据中的噪音,从而造成了对预测数据的结果也含有了寻多噪音,这就是过拟合(overfitting)。模型复杂度曲线上面的过拟合出来的模型对训练数据解释的很好,但对测试数据(新的数据)解释的非常差,也就是模型的泛化能力差。所以为了防止模型过拟合,通常将数据分为训练数据和测试数据,通过测试数据来检验是否过拟合。一般模型准确率与训练数据和测试数据的关系为:图形中左边属于欠拟合,右边属于过拟合,而中间对于测试数据模型准确率高的地方就是模型泛化能力好的地方。学习曲线除了模型复杂度曲线,还可以使用学习曲线来可视化欠拟合和过拟合。所谓学习曲线,就是随着训练样本的逐渐增多,训练出的模型的能力的变化。首先将数据分为训练数据和测试数据:from sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X, y)learning_curve() 函数中使用均方误差来表示模型的优劣,并且训练数据(75个)从1个慢慢增大为75个,记录对训练数据和测试数据预测值的均方误差,用于画学习曲线。from sklearn.metrics import mean_squared_error # 均方误差def learning_curve(algo, X_train, X_test, y_train, y_test): train_score = [] test_score = [] for i in range(1, 76): algo.fit(X_train[:i], y_train[:i]) y_train_predict = algo.predict(X_train[:i]) train_score.append(mean_squared_error(y_train[:i], y_train_predict)) y_test_predict = algo.predict(X_test) test_score.append(mean_squared_error(y_test, y_test_predict))先来看看使用线性回归的情况(根据前文已经知道是欠拟合):learning_curve(LinearRegression(), X_train, X_test, y_train, y_test)作出的学习曲线如图:可以看出均方误差最后趋于稳定。接着来看看使用多项式回归并且设定最高次幂为2的情况:poly2_reg = PolynomialRegression(degree=2)learning_curve(poly2_reg, X_train, X_test, y_train, y_test)作出的学习曲线如图:可以看出均方误差最后也趋于稳定,但是比过拟合情况下均方误差的值要小,表示模型更好。接着来看看使用多项式回归并且设定最高次幂为20的情况:poly20_reg = PolynomialRegression(degree=20)learning_curve(poly20_reg, X_train, X_test, y_train, y_test)作出的学习曲线如图:可以看出对训练数据的军方误差最后会趋于稳定,但对测试数据则不然,这种情况就是过拟合的表现。验证数据集和交叉验证虽然将数据集划分为训练数据集和测试数据集能够为判断模型是否过拟合提供参考,但这样的划分方式并不严谨,因为模型可能是针对测试数据集过拟合的。更好的方式是将数据集划分为训练数据集、验证数据集和测试数据集。验证数据集用于验证模型的效果,方便调整超参数来改善模型;测试数据集用于衡量最终的模型性能。但是这种方式也有可能对验证数据集过拟合,此时可以使用交叉验证(Cross Validation)。k-folds 交叉验证交叉验证即将数据集划分为训练数据集和测试数据集,并将训练数据集分成 k 份,每次将其中一份作为验证数据集,剩下的(k-1)份作为训练数据集,如此可以得到 k 个模型,再将这 k 个模型的均值作为结果调参。以 kNN 手写数字识别算法举例说明:首先准备数据:from sklearn import datasetsfrom sklearn.model_selection import train_test_splitdigits = datasets.load_digits()X = digits.datay = digits.targetX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4)Scikit Learn 中提供了用于交叉验证的 cross_val_score,模型将数据分成三份用于交叉验证,返回三个模型的估算分数:from sklearn.neighbors import KNeighborsClassifierfrom sklearn.model_selection import cross_val_scoreknn_clf = KNeighborsClassifier()cross_val_score(knn_clf, X_train, y_train)# 返回 array([0.9640884 , 0.97506925, 0.96901408])接着进行调参:best_score, best_p, best_k = 0, 0, 0for k in range(2, 10): for p in range(1, 5): knn_clf = KNeighborsClassifier(weights=“distance”, n_neighbors=k, p=p) scores = cross_val_score(knn_clf, X_train, y_train) score = np.mean(scores) if score > best_score: best_score, best_p, best_k = score, p, k print(“best score is:”, best_score)print(“best p is:”, best_p)print(“best k is:”, best_k)# best score is: 0.9795997710242826# best p is: 3# best k is: 2循环中每次比较 cross_val_score 返回数组的均值,最大的对应的 k 和 p 就是最优的超参数。拿到了想要的超参数就可以进行模型训练了:best_knn_clf = KNeighborsClassifier(weights=“distance”, n_neighbors=2, p=3)best_knn_clf.fit(X_train, y_train)best_knn_clf.score(X_test, y_test)# 结果0.9902642559109874网格搜索Scikit Learn 提供了网格搜索(GridSearchCV)整合了上面交叉验证和调参的全过程:from sklearn.model_selection import GridSearchCVparam_grid = [ { ‘weights’: [‘distance’], ’n_neighbors’: [i for i in range(1, 11)], ‘p’: [i for i in range(1, 6)] }]knn_clf = KNeighborsClassifier()grid_search = GridSearchCV(knn_clf, param_grid=param_grid, verbose=1)grid_search.fit(X_train, y_train)grid_search.best_params_ 返回得到的最优超参数;best_knn_clf = grid_search.best_estimator_ 返回最优的模型。在 cross_val_score 和 GridSearchCV 中可以指定参数 cv 来设置将训练数据集分为几份(默认为3)。方差处理偏差和方差对于一个模型而言,模型误差=偏差(Bias)+方差(Variance)+不可避免的误差。偏差和方差表示如图导致偏差的主要原因是对问题本身的假设不正确,导致方差的主要原因是使用的模型太复杂。对于欠拟合,就属于高偏差;而过拟合,就属于高方差。在机器学习算法中,主要的挑战来自方差,解决的方法主要有:降低模型复杂度;降维;增加样本数;使用验证集;模型正则化。接下来主要看看模型正则化。模型正则化对于高方差的模型可以用模型正则化(Regularization)处理,限制参数的大小。以使用梯度下降法的线性回归为例,其目标函数为:使 $J(\theta) = MSE(y, \hat{y};\theta)$ 尽可能小;如果模型过拟合,得到的 $\theta$ 就可能非常大,因此需要对 $J(\theta)$ 加入限制是的 $\theta$ 尽可能小。有两种主要方式:岭回归和 LASSO 回归。岭回归岭回归(Ridge Regression)就是在目标函数中加入了 $\alpha\frac{1}{2}\sum_{i=1}^n\theta_i^2$,即使 $J(\theta) = MSE(y, \hat{y};\theta)+\alpha\frac{1}{2}\sum_{i=1}^n\theta_i^2$ 尽可能小。Scikit Learn 中提供了 Ridge 类表示岭回归,参数为 $\alpha$。使用过程如下:from sklearn.linear_model import Ridgex = np.random.uniform(-3, 3, size=100)X = x.reshape(-1, 1)y = 0.5 * x + 3 + np.random.normal(0, 1, size=100)def PolynomialRegression(degree, alpha): return Pipeline([ (“poly”, PolynomialFeatures(degree=degree)), (“std_scaler”, StandardScaler()), (“ridge_reg”, Ridge(alpha=alpha)) ])设置 degree 为20,alpha 为0.0001 训练模型:ridge_reg = PolynomialRegression(20, 0.0001)ridge_reg.fit(X_train, y_train)y_predict = ridge_reg.predict(X_test)画出来的图形为:设置 degree 为20,alpha 为1 训练模型:ridge_reg = PolynomialRegression(20, 1)ridge_reg.fit(X_train, y_train)y_predict = ridge_reg.predict(X_test)画出来的图形为:相比之下模型好上了不少。LASSO 回归LASSO 回归(Least Absolute Shrinkage and Selection Operator Regression)与岭回归不同的是使用 $\alpha\sum_{i=1}^n|\theta_i|$ 对 $\theta$ 进行限制,即使 $J(\theta) = MSE(y, \hat{y};\theta)+\alpha\sum_{i=1}^n|\theta_i|$ 尽可能小。Scikit Learn 中提供了 Lasso 类表示岭回归,参数为 $\alpha$。使用过程如下:from sklearn.linear_model import Lassodef LassoRegression(degree, alpha): return Pipeline([ (“poly”, PolynomialFeatures(degree=degree)), (“std_scaler”, StandardScaler()), (“lasso_reg”, Lasso(alpha=alpha)) ])设置 degree 为20,alpha 为0.01 训练模型:lasso_reg = PolynomialRegression(20, 0.01)lasso_reg.fit(X_train, y_train)y_predict = lasso_reg.predict(X_test)画出来的图形为:设置 degree 为20,alpha 为0.1 训练模型:lasso_reg = PolynomialRegression(20, 0.1)lasso_reg.fit(X_train, y_train)y_predict = lasso_reg.predict(X_test)画出来的图形为:相比之下模型也好上了不少。LASSO 回归相比岭回归趋向于使得一部分 $\theta$ 等于0,因此画出来的曲线也相对更直一些。弹性网弹性网(Elastic Net)则同时使用了岭回归和LASSO 回归,即使用了 $\alpha\frac{1-r}{2}\sum_{i=1}^n\theta_i^2 +r \alpha\sum_{i=1}^n|\theta_i|$ 对 $\theta$ 进行限制。使用上面的例子就是使 $J(\theta) = MSE(y, \hat{y};\theta)+\alpha\frac{1-r}{2}\sum_{i=1}^n\theta_i^2 +r \alpha\sum_{i=1}^n|\theta_i|$ 尽可能小。源码地址Github | ML-Algorithms-Action ...

December 1, 2018 · 3 min · jiezi