在进行训练/测试集拆分之前还是之后进行Scikit-Learn的独热编码?

13

我在使用scikit-learn建模时看到了两种情况,但我无法弄清楚其中一种为什么返回的结果与另一种根本不同。 我知道这两种情况唯一不同的是:在一种情况下,我一次性对所有数据进行独热编码,然后将其拆分为训练和测试;而在第二种情况下,我先将数据集拆分为训练和测试,然后基于训练数据分别对两个集合进行独热编码。

从技术上讲,后一种情况更好地判断了过程的泛化误差,但是相比第一种情况,该情况返回的标准化基尼指数(normalized gini)截然不同且表现很差,基本上没有模型可言。 我知道第一种情况的基尼指数(约为0.33)符合在这些数据上构建的模型。

为什么第二种情况的基尼指数会如此不同呢? FYI 数据集包含混合的数字和分类变量。

方法1(对整个数据进行独热编码,然后拆分) 返回:Validation Sample Score: 0.3454355044 (normalized gini)。

from sklearn.cross_validation import StratifiedKFold, KFold, ShuffleSplit,train_test_split, PredefinedSplit
from sklearn.ensemble import RandomForestRegressor , ExtraTreesRegressor, GradientBoostingRegressor
from sklearn.linear_model import LogisticRegression
import numpy as np
import pandas as pd
from sklearn.feature_extraction import DictVectorizer as DV
from sklearn import metrics
from sklearn.preprocessing import StandardScaler
from sklearn.grid_search import GridSearchCV,RandomizedSearchCV
from sklearn.ensemble import RandomForestRegressor, ExtraTreesRegressor
from scipy.stats import randint, uniform
from sklearn.metrics import mean_squared_error
from sklearn.datasets import load_boston

def gini(solution, submission):
    df = zip(solution, submission, range(len(solution)))
    df = sorted(df, key=lambda x: (x[1],-x[2]), reverse=True)
    rand = [float(i+1)/float(len(df)) for i in range(len(df))]
    totalPos = float(sum([x[0] for x in df]))
    cumPosFound = [df[0][0]]
    for i in range(1,len(df)):
        cumPosFound.append(cumPosFound[len(cumPosFound)-1] + df[i][0])
    Lorentz = [float(x)/totalPos for x in cumPosFound]
    Gini = [Lorentz[i]-rand[i] for i in range(len(df))]
    return sum(Gini)

def normalized_gini(solution, submission):
    normalized_gini = gini(solution, submission)/gini(solution, solution)
    return normalized_gini

# Normalized Gini Scorer
gini_scorer = metrics.make_scorer(normalized_gini, greater_is_better = True)



if __name__ == '__main__':

    dat=pd.read_table('/home/jma/Desktop/Data/Kaggle/liberty/train.csv',sep=",")
    y=dat[['Hazard']].values.ravel()
    dat=dat.drop(['Hazard','Id'],axis=1)


    folds=train_test_split(range(len(y)),test_size=0.30, random_state=15) #30% test

    #First one hot and make a pandas df
    dat_dict=dat.T.to_dict().values()
    vectorizer = DV( sparse = False )
    vectorizer.fit( dat_dict )
    dat= vectorizer.transform( dat_dict )
    dat=pd.DataFrame(dat)


    train_X=dat.iloc[folds[0],:]
    train_y=y[folds[0]]
    test_X=dat.iloc[folds[1],:]
    test_y=y[folds[1]]


    rf=RandomForestRegressor(n_estimators=1000, n_jobs=1, random_state=15)
    rf.fit(train_X,train_y)
    y_submission=rf.predict(test_X)
    print("Validation Sample Score: {:.10f} (normalized gini).".format(normalized_gini(test_y,y_submission)))

方法二(首先分割,然后进行独热编码) 这将返回:验证样本得分:0.0055124452(标准化基尼指数)。

from sklearn.cross_validation import StratifiedKFold, KFold, ShuffleSplit,train_test_split, PredefinedSplit
from sklearn.ensemble import RandomForestRegressor , ExtraTreesRegressor, GradientBoostingRegressor
from sklearn.linear_model import LogisticRegression
import numpy as np
import pandas as pd
from sklearn.feature_extraction import DictVectorizer as DV
from sklearn import metrics
from sklearn.preprocessing import StandardScaler
from sklearn.grid_search import GridSearchCV,RandomizedSearchCV
from sklearn.ensemble import RandomForestRegressor, ExtraTreesRegressor
from scipy.stats import randint, uniform
from sklearn.metrics import mean_squared_error
from sklearn.datasets import load_boston

def gini(solution, submission):
    df = zip(solution, submission, range(len(solution)))
    df = sorted(df, key=lambda x: (x[1],-x[2]), reverse=True)
    rand = [float(i+1)/float(len(df)) for i in range(len(df))]
    totalPos = float(sum([x[0] for x in df]))
    cumPosFound = [df[0][0]]
    for i in range(1,len(df)):
        cumPosFound.append(cumPosFound[len(cumPosFound)-1] + df[i][0])
    Lorentz = [float(x)/totalPos for x in cumPosFound]
    Gini = [Lorentz[i]-rand[i] for i in range(len(df))]
    return sum(Gini)

def normalized_gini(solution, submission):
    normalized_gini = gini(solution, submission)/gini(solution, solution)
    return normalized_gini

# Normalized Gini Scorer
gini_scorer = metrics.make_scorer(normalized_gini, greater_is_better = True)



if __name__ == '__main__':

    dat=pd.read_table('/home/jma/Desktop/Data/Kaggle/liberty/train.csv',sep=",")
    y=dat[['Hazard']].values.ravel()
    dat=dat.drop(['Hazard','Id'],axis=1)


    folds=train_test_split(range(len(y)),test_size=0.3, random_state=15) #30% test

    #first split
    train_X=dat.iloc[folds[0],:]
    train_y=y[folds[0]]
    test_X=dat.iloc[folds[1],:]
    test_y=y[folds[1]]

    #One hot encode the training X and transform the test X
    dat_dict=train_X.T.to_dict().values()
    vectorizer = DV( sparse = False )
    vectorizer.fit( dat_dict )
    train_X= vectorizer.transform( dat_dict )
    train_X=pd.DataFrame(train_X)

    dat_dict=test_X.T.to_dict().values()
    test_X= vectorizer.transform( dat_dict )
    test_X=pd.DataFrame(test_X)


    rf=RandomForestRegressor(n_estimators=1000, n_jobs=1, random_state=15)
    rf.fit(train_X,train_y)
    y_submission=rf.predict(test_X)
    print("Validation Sample Score: {:.10f} (normalized gini).".format(normalized_gini(test_y,y_submission)))
2个回答

14

尽管之前的评论正确地建议最好先对整个特征空间进行映射,但在您的情况下,Train和Test都包含所有列中的所有特征值。

如果您比较两个版本的vectorizer.vocabulary_,它们完全相同,因此在映射方面没有任何区别。因此,这不能导致问题。

Method 2失败的原因是当您执行此命令时,dat_dict会被按原始索引重新排序。

dat_dict=train_X.T.to_dict().values()
换句话说,train_X在进入此行代码时具有一个洗牌的索引。将其转换为dict时,dict的顺序重新按照原始索引的数字顺序进行排序。这会导致您的训练和测试数据与y完全失去相关性。
方法1没有遇到这个问题,因为您在映射后对数据进行了洗牌。
您可以通过在方法2中两次分配dat_dict时都添加.reset_index()来解决此问题,例如:
dat_dict=train_X.reset_index(drop=True).T.to_dict().values()

这可以确保在转换成dict时数据顺序得以保留。

当我添加了那段代码后,我得到了以下结果:
- 方法1:验证样本得分:0.3454355044(标准化基尼指数)
- 方法2:验证样本得分:0.3438430991(标准化基尼指数)


2
有人指出我应该在 reset_index 中包含 drop=True,这样字典就不会包含索引信息。我已经更新了代码以反映这一点,尽管它并不会改变结果。如果没有 drop=Truedat_dict 就会包含索引信息,但它不会被映射到任何东西,因为 train_X 中没有名为 index 的列。 - inversion

3

我无法运行您的代码,但我猜测在测试数据集中要么:

  • 您没有看到某些分类变量的所有级别,因此如果您只是根据这些数据计算您的虚拟变量,您实际上会有不同的列。
  • 否则,也许您具有相同的列,但它们的顺序不同?

这个。在验证或测试集上进行的任何编码必须与训练集上进行的编码相同。 - Josephine Moeller
字典向量化器不仅利用训练集来定义虚拟变量,然后将这些“规则”应用于测试集。 - B_Miner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接