我在Python中尝试从头开始实现一个二分类问题的逻辑回归。我的结果与sklearn的实现提供的结果不匹配,如这个例子所示。请注意,这些线条看起来“相似”,但显然不是相同的。
我已经考虑了这个答案中提到的内容:sklearn和我都(i)拟合了截距项,并且;(ii)没有应用正则化(penalty=’none’)。此外,虽然sklearn默认使用100次迭代来训练算法,我使用了10000次迭代,并设置了一个较小的学习率0.01。我尝试了不同的数值组合,但问题似乎并不依赖于此。
与此同时,我确实注意到,即使在与sklearn的结果进行比较之前,我自己的实现得到的结果似乎也是错误的:在某些情况下,决策区域明显偏离。你可以在这个图像中看到一个例子。
最后一点似乎表明问题完全是我的错。这是我的代码(它实际上会在每次运行时生成新的数据集并绘制结果):
%matplotlib inlineimport numpy as npimport matplotlib.pyplot as pltfrom matplotlib.colors import ListedColormapfrom sklearn.datasets import make_blobsfrom sklearn.linear_model import LogisticRegressiondef create_training_set(): X0, y = make_blobs(n_samples=[100, 100], centers=None, n_features=2, cluster_std=1) y = y.reshape(-1, 1) # make y a column vector return np.hstack([np.ones((X0.shape[0], 1)), X0]), X0, ydef create_test_set(X0): xx, yy = np.meshgrid(np.arange(X0[:, 0].min() - 1, X0[:, 0].max() + 1, 0.1), np.arange(X0[:, 1].min() - 1, X0[:, 1].max() + 1, 0.1)) X_test = np.c_[xx.ravel(), yy.ravel()] X_test = np.hstack([np.ones((X_test.shape[0], 1)), X_test]) return xx, yy, X_testdef sigmoid(z): return 1 / (1 + np.exp(-z))def apply_gradient_descent(theta, X, y, max_iter=1000, alpha=0.1): m = X.shape[0] cost_iter = [] for _ in range(max_iter): p_hat = sigmoid(np.dot(X, theta)) cost_J = -1/float(m) * (np.dot(y.T, np.log(p_hat)) + np.dot((1 - y).T, np.log(1 - p_hat))) grad_J = 1/float(m) * np.dot(X.T, p_hat - y) theta -= alpha * grad_J cost_iter.append(float(cost_J)) return theta, cost_iterfig, ax = plt.subplots(10, 2, figsize = (10, 30))cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA', '#AAAAFF'])cmap_bold = ListedColormap(['#FF0000', '#00FF00', '#0000FF'])max_iter = 10000alpha = 0.1all_cost_history = []for n_fil in range(10): X_train, X0, y = create_training_set() xx, yy, X_test = create_test_set(X0) theta, cost_evolution = apply_gradient_descent(np.zeros((X_train.shape[1], 1)), X_train, y, max_iter, alpha) all_cost_history.append(cost_evolution) y_pred = np.where(sigmoid(np.dot(X_test, theta)) > 0.5, 1, 0) y_pred = y_pred.reshape(xx.shape) ax[n_fil, 0].pcolormesh(xx, yy, y_pred, cmap = cmap_light) ax[n_fil, 0].scatter(X0[:, 0], X0[:, 1], c=y.ravel(), cmap=cmap_bold, alpha = 1, edgecolor="black") y = y.reshape(X_train.shape[0], ) clf = LogisticRegression().fit(X0, y) Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape) ax[n_fil, 1].pcolormesh(xx, yy, Z, cmap = cmap_light) ax[n_fil, 1].scatter(X0[:, 0], X0[:, 1], c=y, cmap=cmap_bold, alpha = 1, edgecolor="black")plt.show()
回答:
实际上,你的实现与sklearn的实现之间存在差异:你使用的优化算法(在sklearn中也称为求解器)与sklearn使用的不同,我认为你观察到的差异就源于此。你使用的是梯度下降法,而sklearn的实现默认使用“liblinear”求解器,这是不同的。
确实,不同的优化算法可能会基于以下因素产生不同的结果,例如:
- 收敛速度:由于我们限制了迭代次数,收敛速度较慢的算法会在不同的最小值处停止,从而产生不同的决策区域。
- 算法是否确定性:非确定性算法(如随机梯度下降)在给定相同数据集时可能会收敛到不同的局部最小值。使用非确定性算法,即使数据集和算法完全相同,你也可能观察到不同的结果。
- 超参数:更改超参数(例如梯度下降算法的学习率)也会改变优化算法的行为,从而导致不同的结果。
在你的情况下,有充分的理由解释为什么结果不总是相同的:你使用的梯度下降算法可能会陷入局部最小值(因为迭代次数不足、学习率不佳等),而这与liblinear求解器达到的局部最小值不同。
如果你比较sklearn的不同求解器的实现(重用你的代码),你可以观察到类似的差异:
fig, ax = plt.subplots(10, 2, figsize=(10, 30))cmap_light = ListedColormap(['#FFAAAA', '#AAFFAA', '#AAAAFF'])cmap_bold = ListedColormap(['#FF0000', '#00FF00', '#0000FF'])max_iter = 10000alpha = 0.1solver_algo_1 = 'liblinear'solver_algo_2 = 'sag'for n_fil in range(10): X_train, X0, y = create_training_set() xx, yy, X_test = create_test_set(X0) y = y.reshape(X_train.shape[0], ) clf = LogisticRegression(solver=solver_algo_1, max_iter=max_iter).fit(X0, y) Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape) ax[n_fil, 0].pcolormesh(xx, yy, Z, cmap=cmap_light) ax[n_fil, 0].scatter(X0[:, 0], X0[:, 1], c=y, cmap=cmap_bold, alpha=1, edgecolor="black") clf = LogisticRegression(solver=solver_algo_2, max_iter=max_iter).fit(X0, y) Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape) ax[n_fil, 1].pcolormesh(xx, yy, Z, cmap=cmap_light) ax[n_fil, 1].scatter(X0[:, 0], X0[:, 1], c=y, cmap=cmap_bold, alpha=1, edgecolor="black")plt.show()
例如,使用“liblinear”(左图)和“newton-cg”(右图),你可以得到这样的结果:
尽管逻辑回归的实现是相同的,但优化算法的差异导致了不同的结果。简而言之,你的实现与Scikit-learn的实现之间的差异在于优化算法。
现在,如果你对得到的决策边界的质量不满意,你可以尝试调整梯度下降算法的超参数,或者尝试更改优化算法!