Python 使用折叠相关参数在GridSearchCV上自定义评分 问题

Python 使用折叠相关参数在GridSearchCV上自定义评分 问题,python,numpy,scikit-learn,Python,Numpy,Scikit Learn,我正在研究一个学习排序问题,其中标准是对预测进行点评估,但对模型性能进行组评估 更具体地说,估计器输出一个连续变量(很像回归器) 但是评分函数需要按查询聚合,即分组预测,类似于发送到GridSearchCV的groups参数,以进行折叠分区 > ltr_score(y_true, y_pred, groups=g) 0.023 路障 到目前为止还不错。当为GridSearchCV提供自定义评分函数时,我无法根据CV折叠动态更改评分函数中的groups参数: from sklearn.mo

我正在研究一个学习排序问题,其中标准是对预测进行点评估,但对模型性能进行组评估

更具体地说,估计器输出一个连续变量(很像回归器)

但是评分函数需要按查询聚合,即分组预测,类似于发送到
GridSearchCV
groups
参数,以进行折叠分区

> ltr_score(y_true, y_pred, groups=g)
0.023
路障 到目前为止还不错。当为
GridSearchCV
提供自定义评分函数时,我无法根据CV折叠动态更改评分函数中的
groups
参数:

from sklearn.model_selection import GridSearchCV
from sklearn.metrics import make_scorer

ltr_scorer = make_scorer(ltr_score, groups=g)  # Here's the problem, g is fixed
param_grid = {...}

gcv = GridSearchCV(estimator=est, groups=g, param_grid=param_grid, scoring=ltr_scorer)
回避这个问题的最简单的方法是什么

一种(失败的)方法 在一份报告中,一条评论被问及/建议:

为什么您不能将{分组列}存储在本地,并在必要时使用拆分器提供的列车测试索引进行索引

OP的回答是“似乎可行”。我认为这也是可行的,但无法使它起作用。显然,
GridSearchCV
将首先使用所有交叉验证分割索引,然后才执行分割、拟合、pred和scoring。这意味着我无法(似乎)在评分时尝试猜测创建当前拆分子选择的原始索引

为了完整起见,我的代码:

class QuerySplitScorer:
    def __init__(self, X, y, groups):
        self._X = np.array(X)
        self._y = np.array(y)
        self._groups = np.array(groups)
        self._splits = None
        self._current_split = None

    def __iter__(self):
        self._splits = iter(GroupShuffleSplit().split(self._X, self._y, self._groups))
        return self

    def __next__(self):
        self._current_split = next(self._splits)
        return self._current_split

    def get_scorer(self):
        def scorer(y_true, y_pred):
            _, test_idx = self._current_split
            return _score(
                y_true=y_true,
                y_pred=y_pred,
                groups=self._groups[test_idx]
            )
用法:

qss = QuerySplitScorer(X, y_true, g)
gcv = GridSearchCV(estimator=est, cv=qss, scoring=qss.get_scorer(), param_grid=param_grid, verbose=1)
gcv.fit(X, y_true)

它不起作用,
self.\u当前\u分割
在最后生成的分割时固定。

据我所知,评分值是成对的(值,组),但估计器不应与组一起工作。让我们用包装纸把它们切开,但把它们留给记分员

简单估算器包装(可能需要一些抛光以完全符合要求)

从sklearn.base导入BaseEstimator,ClassifierMixin,TransformerMixin,克隆
从sklearn.linear_模型导入逻辑回归
从sklearn.utils.estimator\u checks导入检查\u estimator
#从sklearn.utils.validation导入检查\u X\u y,检查\u数组,检查\u是否已安装
从sklearn.model_选择导入GridSearchCV
从sklearn.metrics导入make_scorer
类割估计量(基估计量):
定义初始(自、基估计):
self.base\u估计器=base\u估计器
def配合(自、X、y):
self.\u base\u估计器=克隆(self.base\u估计器)
self._base_estimator.fit(X,y[:,0].ravel())
回归自我
def预测(自我,X):
回归自估计量预测(X)
#检查估计量(CutEstimator(LogisticRegression()))
然后我们可以使用它

def my_分数(y,y_pred):
返回np.sum(y[:,1])
pagam_网格={'base_估计量_uc':[0.2,0.5]}
X=np.random.randn(30,3)
y=np.random.randint(3,size=(X.shape[0],1))
g=np.类(y)
gs=GridSearchCV(CutEstimator(Logistic回归()),pagam_网格,cv=3,
得分=得分者(我的得分),返回训练得分=真
).fit(X,np.hstack((y,g)))
打印(gs.cv_结果[平均测试分数])#10为30/3
打印(gs.cv_结果[平均训练成绩])#20为30-30/3
输出:

[10.10]
[ 20.  20.]
更新1:way但估算值无变化:

pagam_grid={'C':[0.2,0.5]}
X=np.random.randn(30,3)
y=np.random.randint(3,size=(X.shape[0]))
g=np.random.randint(3,size=(X.shape[0]))
cv=GroupShuffleSplit(3,随机状态=100)
组_info={}
对于等速分割中的a、b(X、y、g):
组信息[hash(y[b].tobytes())]=g[b]
组信息[hash(y[a].tobytes())]=g[a]
def my_分数(y,y_pred):
全球集团信息
g=组信息[散列(y.tobytes())]
返回np.和(g)
gs=GridSearchCV(LogisticRegression(),pagam_网格,cv=cv,
得分=得分者(我的得分),返回训练得分=真,
).拟合(X,y,组=g)
打印(gs.cv_结果_u['mean_test_score']))
打印(gs.cv_结果_u['mean_train_score']))

作为想法,您可以在
GridSearchCV
上将
KFold
对象作为
cv
参数。因此,您可以手动折叠,设置记分器,然后运行搜索。@Ilitimofev这基本上就是我正在做的,问题是记分器没有简单的方法来识别他使用的折叠,以便正确地从
组中进行子选择。你的意思是我应该通过一个单一的折叠吗?另一个疯狂的想法是,
y
可以是一个矩阵“
zip(y,g)
”,所以你需要切割第一列的估计器包装器,但scorer可以两者兼而有之。在我的脑海里有这种可能性,但它确实感觉有点像管道胶带解决方案。我只想用计分技巧来解决计分问题,甚至可能在GridSearch中做一点小小的改变。。。不是对估计员。但是一个解决方案就是一个解决方案,如果我不能很快找到更好的解决方案,我会接受这个解决方案。我已经检查了
GridSearchCV
您的问题所在的位置,它是从
BaseSearchCV.fit
调用的普通函数,没有被覆盖的机会。因此,如果你想将不同的
y
放入
fit
score
中,那么在
GridSearchCV
中重写
fit
也需要大量的血液。哈,使用hash是一种很好的黑客手段——我想即使是相同的记录也会产生(几乎肯定)不同的hash,因为它们是不同的对象。
qss = QuerySplitScorer(X, y_true, g)
gcv = GridSearchCV(estimator=est, cv=qss, scoring=qss.get_scorer(), param_grid=param_grid, verbose=1)
gcv.fit(X, y_true)