Scikit Learn 回归:设计矩阵 X 太大而无法回归。我该怎么办?

Posted

技术标签:

【中文标题】Scikit Learn 回归:设计矩阵 X 太大而无法回归。我该怎么办?【英文标题】:ScikitLearn regression: Design matrix X too big for regression. What do I do? 【发布时间】:2018-03-06 03:09:04 【问题描述】:

我有一个矩阵 X,它有 7000 列和 38000 行。因此它是一个numpy array,形状为(38000, 7000)

我实例化了模型

model = RidgeCV(alphas = (0.001,0.01, 0.1, 1)

然后装上它

model.fit(X, y)

其中y 是响应向量,它是一个形状为(38000,) 的numpy 数组。

通过运行这个我得到一个Memory Error

我该如何解决这个问题?

我的想法

我的第一个想法是“水平”划分矩阵 X。我的意思是,我将 X 划分为两个具有相同列数(从而保留所有特征)但行数更少的矩阵。然后我每次都为每个子矩阵拟合模型?但是恐怕这真的不等于拟合整个矩阵..

有什么想法吗?

【问题讨论】:

我建议使用具有partial_fit 参数的回归算法,例如SGDRegressor。然后你可以按块喂你的矩阵。不过,您必须手动完成简历。 那么对于带有 CV 的 Ridge 回归,没有实现“部分拟合”吗? 【参考方案1】:

这是一个众所周知的问题,可以使用核外学习来解决。通过谷歌搜索该术语,您会发现几种解决问题的方法。

对于您的具体问题,您必须首先创建一个生成器,该生成器将生成矩阵的一行(或几行),而不是使用算法的 partial_fit 方法。

scikit-learn 的标准算法实际上使用解决方案的精确计算,例如 sklearn.linear_model.LinearRegressionsklearn.linear_model.LinearRegression.RidgeCV。其他方法基于批量学习,并有一个partial_fit 方法,如sklearn.linear_model.SGDRegressor,只允许适合一个小批量。这就是你要找的东西。

过程是:使用生成器生成一个小批量,应用partial_fit方法,从内存中删除小批量并获取一个新的。

但是,由于此方法是随机的,并且取决于数据的顺序和权重的初始化,这与标准回归方法给出的解决方案相反,该方法可以拟合内存中的所有数据。我不会详细介绍,而是看一下梯度下降优化以了解它是如何工作的 (http://ruder.io/optimizing-gradient-descent/)

【讨论】:

【参考方案2】:

在我看来,这并不是非常大规模,你可能不需要在这里使用核外学习(虽然我不知道你有多少内存)。

在不需要时使用核外方法,您将付出代价(如果不进行大量调整,则不健壮)!

知道你的特征是稀疏还是密集也会很有趣,这会产生巨大的差异(因为大多数求解器都可以利用稀疏数据!)。

这里要说几句:

RidgeCV 使用了一些问题调整的交叉验证方法,可能正因为如此,没有用于控制底层求解器的参数 当有效的热启动有助于流程(性能)时,使用问题调整的 CV 方法并不少见 手动做CV的时候,sklearn里面所有工具都可以,可以选择不同的solvers 它们在方法和特征方面非常不同

求解器:‘auto’、‘svd’、‘cholesky’、‘lsqr’、‘sp​​arse_cg’、‘sag’、‘saga’

在计算例程中使用的求解器:

‘auto’根据数据类型自动选择求解器。

‘svd’使用 X 的奇异值分解来计算岭系数。奇异矩阵比“cholesky”更稳定。

‘cholesky’使用标准的 scipy.linalg.solve 函数来获得一个封闭形式的解。

‘sparse_cg’使用 scipy.sparse.linalg.cg 中的共轭梯度求解器。作为一种迭代算法,这个求解器比“cholesky”更适合大规模数据(可以设置 tol 和 max_iter)。

‘lsqr’使用专用的正则化最小二乘例程 scipy.sparse.linalg.lsqr。它是最快的,但在旧的 scipy 版本中可能不可用。它还使用迭代过程。

“sag”使用随机平均梯度下降,“saga”使用其改进的、无偏的版本,名为 SAGA。这两种方法也使用迭代过程,并且当 n_samples 和 n_features 都很大时,通常比其他求解器更快。请注意,“sag”和“saga”的快速收敛仅在具有大致相同规模的特征上得到保证。您可以使用 sklearn.preprocessing 中的缩放器对数据进行预处理。

最后五个求解器都支持密集和稀疏数据。但是,当 fit_intercept 为 True 时,只有 ‘sag’ 和 ‘saga’ 支持稀疏输入。

所以我强烈建议尝试: sparse_cglsqr 连同手动简历。预计,这种方法更加稳定/健壮(与使用 SGD 的核外方法相比),您不需要过多调整它们的参数,这是一个巨大的优势。

当然,总是可以使用sagsgd,但收敛理论是基于一些关于参数调整的强假设。在非常大规模的环境中,这两个是可行的候选者(因为其他人不会工作),但在这里我看不到太多优点(再次:我不确定你有多少内存)。如果上述方法不起作用,请在sgd 之前尝试saga 并遵守规则(标准化+参数调整)。 (edit: sag 对我的测试数据非常不利!)

例子:

from sklearn.datasets import make_regression
from sklearn.linear_model import Ridge
from time import perf_counter

X, y, = make_regression(n_samples=38000, n_features=7000, n_informative=500,
                        bias=-2.0, noise=0.1, random_state=0)

print(type(X))  # dense!!! if your data is sparse; use that fact!

clf = Ridge(alpha=1.0, solver="lsqr")
start = perf_counter()
clf.fit(X, y)
end = perf_counter()
print('LSQR: used secs: ', end-start)

输出:

LSQR: used secs:  8.489622474064486

因此,即使在密集情况下,优化也不难(并且使用约 6-8 GB 的内存)。

虽然我会小心地暗示 Ridge 模型和以下基于 SGD 的 Ridge 模型之间的等价性(注意哪个变量是正则化的一部分;懒得检查),这里只是一个演示它有多难调整 SGD。对此持保留态度(也许不评估绝对分数;但方差取决于参数):

备注:这是一个较小的例子!使用您的原始示例,如果不手动设置学习率eta_0,任何 SGD 方法都不会获得收敛(因为内部启发式无法为您做到这一点!)

部分代码:

X, y, = make_regression(n_samples=3800, n_features=700, n_informative=500,
                        noise=0.1, random_state=0)

print(type(X))  # dense!!! if your data is sparse; use that fact!

clf = Ridge(alpha=1.0, solver="lsqr", fit_intercept=False)
start = perf_counter()
clf.fit(X, y)
end = perf_counter()
print('LSQR: used secs: ', end-start)
print('train-score: ', clf.score(X, y))

clf = Ridge(alpha=1.0, solver="sparse_cg", fit_intercept=False)
start = perf_counter()
clf.fit(X, y)
end = perf_counter()
print('sparse_cg: used secs: ', end-start)
print('train-score: ', clf.score(X, y))

clf = SGDRegressor(loss='squared_loss', penalty='l2', alpha=1., fit_intercept=False,
                   random_state=0)
start = perf_counter()
clf.fit(X, y)
end = perf_counter()
print('SGD: used secs: ', end-start)
print('train-score: ', clf.score(X, y))

clf = SGDRegressor(loss='squared_loss', penalty='l2', alpha=1., fit_intercept=False,
                   random_state=0, average=True)
start = perf_counter()
clf.fit(X, y)
end = perf_counter()
print('SGD: used secs: ', end-start)
print('train-score: ', clf.score(X, y))

clf = SGDRegressor(loss='squared_loss', penalty='l2', alpha=1., fit_intercept=False,
                   random_state=0, learning_rate="constant", eta0=0.001)
start = perf_counter()
clf.fit(X, y)
end = perf_counter()
print('SGD: used secs: ', end-start)
print('train-score: ', clf.score(X, y))

clf = SGDRegressor(loss='squared_loss', penalty='l2', alpha=1., fit_intercept=False,
                   random_state=0, n_iter=50, average=True)
start = perf_counter()
clf.fit(X, y)
end = perf_counter()
print('SGD: used secs: ', end-start)
print('train-score: ', clf.score(X, y))

输出:

LSQR: used secs:  0.08252486090450709
train-score:  0.999999907282
sparse_cg: used secs:  0.13668818702548152
train-score:  0.999999181151
SGD: used secs:  0.04154542095705427
train-score:  0.743448766459
SGD: used secs:  0.05300238587407993
train-score:  0.774611911034
SGD: used secs:  0.038653031605587
train-score:  0.733585661919
SGD: used secs:  0.46313909066321507
train-score:  0.776444474871

另外:当使用partial_fit / out-of-memory 方法时,还需要调整mini_batches 的大小(在上面的演示中忽略了 -> 纯 SGD -> 一次一个样本)!再说一遍:这并不容易!

【讨论】:

IMO 这应该是公认的答案。这些是一些重要的细节,可能会大大改善您的结果。

以上是关于Scikit Learn 回归:设计矩阵 X 太大而无法回归。我该怎么办?的主要内容,如果未能解决你的问题,请参考以下文章

SciPy NumPy 和 SciKit-learn ,创建一个稀疏矩阵

给定参考值的二维矩阵,对两个变量进行 Scikit-learn 回归

逻辑回归-5. scikit-learn中的逻辑回归

scikit-learn 线性回归算法库小结

如何为 scikit-learn 的高斯过程回归指定先验?

scikit-learn 线性回归算法库小结