管道中 LogisticRegression 的 _coef 值过多

Posted

技术标签:

【中文标题】管道中 LogisticRegression 的 _coef 值过多【英文标题】:Too many _coef values for LogisticRegression in Pipeline 【发布时间】:2019-06-20 15:38:15 【问题描述】:

我正在 sklearn 管道中使用 sklearn-pandas DataFrameMapper。为了评估特征联合管道中的特征贡献,我喜欢测量估计器的系数(逻辑回归)。对于以下代码示例,三个文本内容列 a, bc 被矢量化并为 X_train 选择:

import pandas as pd
import numpy as np
import pickle
from sklearn_pandas import DataFrameMapper
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
np.random.seed(1)

data = pd.read_csv('https://pastebin.com/raw/WZHwqLWr')
#data.columns

X = data.copy()
y = data.result
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)

mapper = DataFrameMapper([
        ('a', CountVectorizer()),
        ('b', CountVectorizer()),
        ('c', CountVectorizer())
])

pipeline = Pipeline([
        ('featurize', mapper),
        ('clf', LogisticRegression(random_state=1))
        ])

pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)

print(abs(pipeline.named_steps['clf'].coef_))
#array([[0.3567311 , 0.3567311 , 0.46215153, 0.10542043, 0.3567311 ,
#        0.46215153, 0.46215153, 0.3567311 , 0.3567311 , 0.3567311 ,
#        0.3567311 , 0.46215153, 0.46215153, 0.3567311 , 0.46215153,
#        0.3567311 , 0.3567311 , 0.3567311 , 0.3567311 , 0.46215153,
#        0.46215153, 0.46215153, 0.3567311 , 0.3567311 ]])

print(len(pipeline.named_steps['clf'].coef_[0]))
#24

与通常返回与特征数量相等长度的系数的多个特征的正常分析不同,DataFrameMapper 返回更大的系数矩阵。

a) 大写的总共 24 个系数是如何解释的? b) 访问每个特征 ("a","b","c") 的 coef_ 值的最佳方法是什么?

期望的输出:

a: coef_score (float)
b: coef_score (float)
c: coef_score (float)

谢谢!

【问题讨论】:

【参考方案1】:

Pipeline 恢复拟合好的DataFrameMapper 后,您可以使用.features 方法访问其内容。这使得迭代您用来将字符串转换为单热编码变量的CountVectorizer 函数成为可能。每个 CountVecotrizer 都有一个 .vocabulary_ 方法,它可以准确地告诉您哪个列代表什么字符串。

因此,您可以按顺序提取DataFrameMapper 中的每个CountVectorizer,然后按顺序提取表示输入矩阵中每一列的字符串。这将允许您拥有一个准确代表系数标签的序列。

根据您的示例,此 sn-p 代码应该可以满足您的需要,并且我在上面已详细描述过(如果您遇到任何错误,请警告我,我将根据您的反馈进行更正):

# recover the fitted mapper
fitted_mapper = pipeline.named_steps['featurize'] 

mapped_labels = list()
# iterate through the CountVectorizers
for label, fun in fitted_mapper.features:
    # Iterate through the sorted vocabulary
    for level, _ in sorted(fun.vocabulary_.items()):
        mapped_labels.append(label+'_'+level)

# the ordered sequence of vectorized strings
print(mapped_labels)

# pick up the coefficients
coefs = pipeline.named_steps['clf'].coef_[0]

# pair mapped labels and coefs and print them
for label, coef in zip(mapped_labels, coefs):
    print("%s:%0.5f" % (label, coef))

【讨论】:

谢谢!当我应用代码时,它似乎给出了单词的分数,而不是特征列(a,b,c)的分数?但是,如果可能的话,这也是一个有趣的附带问题,我如何从上面的示例中选择特定的单词。 通过CountVectorizer将层级编码为单个变量后,特征列不再有系数,但新创建的特征有系数,它们是二进制变量,每个代表一个单词。您仍然可以收集从原始特征之一派生的所有二进制变量,但没有有意义的方法将它们表示为单个系数。即使你可以对与某个特征相关的单词系数进行平均,或者只是将它们相加,甚至计算出最高值和最低值之间的差值,这也没什么用。 还有一种方法可以使用列表而不是字典吗?最后,我需要对所有分数进行归一化,以便它们是相对的。另外,由于列的比例不同,有没有办法纠正,使系数具有可比性? 为了标准化所有的分数,你必须在你的管道中,在'featurize'和'clf'之间放置一个 sklearn.preprocessing.StandardScaler 函数,这样你就可以统计标准化不同的字数变量并且它们的系数具有可比性。另一种方法是让 sklearn.feature_extraction.text.CountVectorizer 与参数 binary=True 一起工作,在这种情况下,您不会对出现次数进行任何计数,而只是每个单词的纯二进制。【参考方案2】:

虽然您的初始数据框确实只包含您的三个特征的列abc,但 Pandas DataFrameMapper() 类将 SKlearn 的 CountVectorizer() 应用于每列 a 的相应单词语料库, b,和 c。这导致总共创建了 24 个特征,然后将它们传递给您的 LogisticRegression() 分类器。这就是为什么当您尝试访问分类器的 .coef_ 属性时会得到一个包含 24 个值的未标记列表的原因。

但是,将这 24 个 coeff_ 分数中的每一个与它们来自的原始列(abc)进行匹配非常简单,然后计算每个分数的平均系数分数柱子。以下是我们的做法:

原始数据框如下所示:

             a                   b                c   result
2   here we go   hello here we are   this is a test        0
73  here we go   hello here we are   this is a test        0
...

如果我们运行以下行,我们可以看到由 DataFrameMapper/CountVectorizer() 创建的所有 24 个特征的列表,这些特征用于您的 mapper 对象中:

pipeline.named_steps['featurize'].transformed_names_

['a_another',
 'a_example',
 'a_go',
 'a_here',
 'a_is',
 'a_we',
 'b_are',
 'b_column',
 'b_content',
 'b_every',
 'b_has',
 'b_hello',
 'b_here',
 'b_text',
 'b_we',
 'c_can',
 'c_deal',
 'c_feature',
 'c_how',
 'c_is',
 'c_test',
 'c_this',
 'c_union',
 'c_with']

len(pipeline.named_steps['featurize'].transformed_names_)

24

现在,我们将计算来自a/b/c 列的三组特征的平均 coef 分数:

col_names = list(data.drop(['result'], axis=1).columns.values)
vect_feats = pipeline.named_steps['featurize'].transformed_names_
clf_coef_scores = abs(pipeline.named_steps['clf'].coef_)

def get_avg_coef_scores(col_names, vect_feats, clf_coef_scores):
    scores = 
    start_pos = 0
    for n in col_names:
        num_vect_feats = len([i for i in vect_feats if i[0] == n])
        end_pos = start_pos + num_vect_feats
        scores[n + '_avg_coef_score'] = np.mean(clf_coef_scores[0][start_pos:end_pos])
        start_pos = end_pos
    return scores

如果我们调用我们刚刚写的函数,我们会得到如下输出:

get_avg_coef_scores(col_names, vect_feats, clf_coef_scores)

'a_avg_coef_score': 0.3499861323284858,
 'b_avg_coef_score': 0.40358462487685853,
 'c_avg_coef_score': 0.3918712435073411

如果我们想验证 24 个 coeff 分数中的哪一个属于每个创建的特征,我们可以使用以下字典推导:

key:clf_coef_scores[0][i] for i, key in enumerate(vect_feats)

'a_another': 0.3567310993987888,
 'a_example': 0.3567310993987888,
 'a_go': 0.4621515317244458,
 'a_here': 0.10542043232565701,
 'a_is': 0.3567310993987888,
 'a_we': 0.4621515317244458,
 'b_are': 0.4621515317244458,
 'b_column': 0.3567310993987888,
 'b_content': 0.3567310993987888,
 'b_every': 0.3567310993987888,
 'b_has': 0.3567310993987888,
 'b_hello': 0.4621515317244458,
 'b_here': 0.4621515317244458,
 'b_text': 0.3567310993987888,
 'b_we': 0.4621515317244458,
 'c_can': 0.3567310993987888,
 'c_deal': 0.3567310993987888,
 'c_feature': 0.3567310993987888,
 'c_how': 0.3567310993987888,
 'c_is': 0.4621515317244458,
 'c_test': 0.4621515317244458,
 'c_this': 0.4621515317244458,
 'c_union': 0.3567310993987888,
 'c_with': 0.3567310993987888

【讨论】:

我刚刚看到你在函数中添加了start_pos = end_pos。这是有道理的,并且确实对循环至关重要。 谢谢,是的,它确保我们使用正确的系数值来计算 a、b 和 c 列的平均系数。 嗨@James Dellinger,最后一个问题:当DataFrameMapper 具有未计数矢量化的功能时,这不起作用,对吧?我的意思是例如:'mapper = DataFrameMapper([ ('a', CountVectorizer()), ('b', CountVectorizer()), ('c', None) ])' 看起来 'clf_coef_scores' 只输出一个每个特征列的值。

以上是关于管道中 LogisticRegression 的 _coef 值过多的主要内容,如果未能解决你的问题,请参考以下文章

sklearn StackingClassifer 与管道

在 sklearn 中计算管道逻辑回归 predict_proba

使用管道将 MinMaxScaler() 应用于 RFECV()

使用sklearn Pipeline中的索引提取子管道时出错。

Sklearn:有没有办法为管道定义特定的分数类型?

无法在 mleap 中序列化逻辑回归