FeatureTools 创建的特征构建不一致的模型

Posted

技术标签:

【中文标题】FeatureTools 创建的特征构建不一致的模型【英文标题】:Features Created by FeatureTools Build Inconsistent Models 【发布时间】:2021-10-03 13:42:26 【问题描述】:

我有一个不平衡的数据集,其中包含来自 0 类的 2 亿个数据和来自 1 类的 8000 个数据。我采用了两种不同的方法来构建模型。

    随机采样一个比例为 1:4 的新数据集。意思是 0 类的 32000 和 1 类的 8000。然后使用特征工具生成特征(在我的例子中生成了 70 个特征)并将数据集拆分为 test_size = 0.2 的训练集和测试集,并对少数类进行分层。使用随机森林算法构建模型并预测测试集。

代码:

import ....
df = pd.read_csv(...)
label = df['target']
es = ft.EntitySet(id='maintable')

es = es.entity_from_dataframe(entity_id='maintable',dataframe=df,make_index=True,
index='index',time_index='date_info',variable_types='personal_id': ft.variable_types.Categorical,
'category_id': ft.variable_types.Categorical, 'name': ft.variable_types.Categorical)

es.normalize_entity(base_entity_id='maintable',new_entity_id='personal_id')
es.normalize_entity(base_entity_id='maintable',new_entity_id='category_id')
es.normalize_entity(base_entity_id='maintable',new_entity_id='name')

fm, features = ft.dfs(entityset=es,target_entity='maintable',max_depth=3)

fm = fm.set_index(label.index)
fm['target'] = label

X = fm[fm.columns.difference(['target'])]
y = fm['target']

X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=42,stratify=y,test_size=0.2)

rf = RandomForestClassifier(random_state=42,n_jobs=-1)
scaler = StandardScaler()

X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

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

#print results
.....
    从第 1 类中拆分所有数据,将 60% 用于训练集,40% 用于测试集。训练集的类比与第一种方法相同(1:4),但测试集的类比为 1:200。使用特征工具(再次创建 70 个特征),使用随机森林算法构建模型并预测测试集。

代码:

import ....
df = pd.read_csv(...)
# I merged randomly generated(with java) train and test sets to create features with featuretools. I created a column 'test_data' which takes two binary values (1 for test set 0 for train set) so I can separate train and test set for fitting model and predicting. 
label = df['target','test_data']
es = ft.EntitySet(id='maintable')

es = es.entity_from_dataframe(entity_id='maintable',dataframe=df,make_index=True,
index='index',time_index='date_info',variable_types='personal_id': ft.variable_types.Categorical,
'category_id': ft.variable_types.Categorical, 'name': ft.variable_types.Categorical)

es.normalize_entity(base_entity_id='maintable',new_entity_id='personal_id')
es.normalize_entity(base_entity_id='maintable',new_entity_id='category_id')
es.normalize_entity(base_entity_id='maintable',new_entity_id='name')

fm, features = ft.dfs(entityset=es,target_entity='maintable',max_depth=3)

fm = fm.set_index(label.index)
fm['target','test_data'] = label

df_train = fm.loc[fm['test_data'] == 0]
df_test = fm.loc[fm['test_data'] == 1]

#Drop 'test_data' column because I dont need it anymore
df_train = df_train.drop(['test_data'],axis=1)
df_test = df_test.drop(['test_data'],axis=1)

X_train = df_train[df_train.columns.difference(['target'])]
y_train = df_train['target']

X_test = df_test[df_test.columns.difference(['target'])]
y_test = df_test['target']

rf = RandomForestClassifier(random_state=42,n_jobs=-1)
scaler = StandardScaler()

X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

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

#print results

现在对我来说有趣的部分开始了。以下是两种方法的结果。

1.方法: (0类为负,1类为正)

TN:6306

FP:94

TP:1385

FN:215

2。方法:

TN:576743

FP:63257

TP:361

FN:2839

第一个结果对我来说非常好,但第二个结果很糟糕。这怎么可能?我知道我使用较少的 1 类数据来训练第二种方法的模型,但它不应该有太大差异。我的意思是它比硬币翻转更糟糕。子集是在这两种方法上随机生成的,我尝试了许多不同的子集,但结果与上面几乎相同。任何形式的帮助表示赞赏。

编辑:我可能有一个想法,但不确定...我在第一种方法中使用 train_test_split。因此,训练集和测试集共享一些personal_id,但在第二种方法中,训练集和测试集具有完全不同的personal_id。当模型遇到在它无法正确预测之前没有看到的personal_id 并决定将其标记为多数类时。如果是这种情况,那么正在为给定的分类变量(过度拟合)精确地创建特征。同样,当它遇到任何分类列的不同值时,它只会感到困惑。我该如何克服这样的问题?

Edit2:我测试了上面提到的想法并得到了奇怪的结果。首先,我从数据集中删除了 personal_id 列,但最终得到了更好的模型。然后我测试了我的第二种方法,即personal_id出现在训练集中也应该出现在测试集中。我以为我会得到更好的模型,但它比以前更糟。我真的很困惑……

【问题讨论】:

你能发布一个示例代码 sn-p 来显示问题吗? 我用代码编辑了我的问题。 【参考方案1】:

我同意模型可能过度拟合,并且在给定新的个人 ID 的情况下无法泛化。我建议将标签与截止时间一起传递,以获得更结构化的训练和测试集。我将通过一个使用此数据的快速示例。

    index     name  personal_id category_id   date_info  target
0       0   Samuel            3           C  2021-07-15       0
1       1   Samuel            3           C  2021-07-15       0
2       2   Samuel            3           C  2021-07-15       0
3       3   Samuel            3           C  2021-07-15       0
4       4  Rosanne            2           C  2021-05-11       0
..    ...      ...          ...         ...         ...     ...
95     95    Donia            1           C  2020-09-27       1
96     96    Donia            1           C  2020-09-27       1
97     97  Fleming            1           A  2021-06-15       1
98     98     Fred            1           C  2021-02-28       0
99     99  Giacomo            1           A  2021-06-19       1

[100 rows x 6 columns]

首先,根据还包括目标列的时间索引创建截止时间。确保从原始数据中删除目标列。

target = df[['date_info', 'index', 'target']]
df.drop(columns='target', inplace=True)

然后,您可以像往常一样构造实体集。

import featuretools as ft

es = ft.EntitySet(id='maintable')
es = es.entity_from_dataframe(
    entity_id='maintable',
    dataframe=df,
    index='index',
    time_index='date_info',
    variable_types=
        'personal_id': ft.variable_types.Categorical,
        'category_id': ft.variable_types.Categorical,
        'name': ft.variable_types.Categorical
    ,
)
es.normalize_entity(base_entity_id='maintable', new_entity_id='personal_id', index='personal_id',)
es.normalize_entity(base_entity_id='maintable', new_entity_id='category_id', index='category_id')
es.normalize_entity(base_entity_id='maintable', new_entity_id='name', index='name')

现在,在 DFS 调用中,您可以传入目标截止时间。这种方法不会使用目标列来构建特征,并确保目标列与特征矩阵保持对齐。

fm, fd = ft.dfs(entityset=es, target_entity='maintable', max_depth=3, cutoff_time=target)
       personal_id category_id     name  DAY(date_info)  ...  name.NUM_UNIQUE(maintable.MONTH(date_info))  name.NUM_UNIQUE(maintable.WEEKDAY(date_info))  name.NUM_UNIQUE(maintable.YEAR(date_info))  target
index                                                    ...
59               1           C     Fred              28  ...                                            1                                              1                                           1       0
35               1           A  Giacomo              19  ...                                            1                                              1                                           1       1
82               3           B  Laverna              17  ...                                            1                                              1                                           1       0
25               2           C  Rosanne              11  ...                                            1                                              1                                           1       0
23               1           A  Giacomo              19  ...                                            1                                              1                                           1       1

然后,您可以将特征 maxtrix 拆分为训练和测试集。

from sklearn.model_selection import train_test_split

X_train, X_test = train_test_split(fm, test_size=.2, shuffle=False)
y_train, y_test = X_train.pop('target'), X_test.pop('target')

对于 AutoML,您可以使用 EvalML 找到最佳 ML 管道并绘制混淆矩阵。

from evalml import AutoMLSearch
from evalml.model_understanding.graphs import graph_confusion_matrix

automl = AutoMLSearch(
    X_train=X_train,
    y_train=y_train,
    problem_type='binary',
    allowed_model_families=['random_forest'],
)
automl.search()
y_pred = automl.best_pipeline.predict(X_test)
graph_confusion_matrix(y_test, y_pred).show()

您可以在链接页面中找到类似的machine learning examples。如果您觉得这有帮助,请告诉我。

【讨论】:

好的,谢谢,我会试试这个,但我有一个问题。您将 date_info 用于 time_index 和 cut_off 时间。不应该不一样吗?也许 cut_off_time = date_info - 90 天? 我实现了你的示例代码,这就是我得到的:TN: 72476, FP: 0, FN: 86, TP: 1893 Precision: 100%, Recall: 96% 这显然是过度拟合但我可以修复它。现在我应该用我的第二种方法来测试它。我需要在 java 中创建训练集和测试集,合并它们,使用特征工具创建特征,分离它们,拟合模型和预测。我希望我能得到一个好的模型。 您可以使用时间索引date_info作为截止时间。这种方法可确保您仅使用截止时间之前存在的数据构建功能。每个标签都可以有对应的截止时间,这样模型训练就不会出现数据泄露。 如果要在截止时间排除数据,可以在DFS调用中使用include_cuttoff_time=False。您可以在链接页面上找到有关handling time 的更多详细信息。 我想我理解我的问题。这行label = df['target','test_data'] 和这两行fm = fm.set_index(label.index) fm['target','test_data'] = label 导致了问题。合并时,特征矩阵(fm)和label 没有正确对齐。我在一个小数据集上对此进行了测试,我意识到应该标记为 1 类的行在合并过程完成后被标记为 0 类。这个错误导致模型失败。感谢@Jeff 的帮助。我会尝试解决这个问题并更新这篇文章。我仍然不清楚 cutoff_time,所以我可能还有问题。

以上是关于FeatureTools 创建的特征构建不一致的模型的主要内容,如果未能解决你的问题,请参考以下文章

featuretools入门

featuretools入门

FeatureTools

在 featuretools 1.0.0 中将 cutoff_time 传递给 dfs 的正确方法

自动特征工程工具包

使用特征工具创建聚合特征的逻辑