De fin 2019 à début 2020? Je me souviens que l'encodage cible est devenu un sujet brûlant.
Le codage cible remplace la variable catégorielle par la valeur moyenne de la variable objectif, mais si vous la traitez simplement, une fuite se produira, vous devez donc la concevoir. Pour éviter les fuites, vous pouvez prendre des mesures telles que la méthode Leave One Out qui utilise la valeur moyenne autre que la ligne à convertir, ou la division en K et le remplacement par la valeur moyenne autre que le pli qui inclut le secteur cible.
Il existe de nombreuses explications de l'encodage cible dans le monde, donc je ne l'expliquerai pas dans cet article (par exemple, ce site out-TS-% E4% BD% BF% E3% 81% A3% E3% 81% A1% E3% 82% 83% E3% 83% 80% E3% 83% A1) est très utile). Dans cet article, j'essaie de voir comment cela fonctionne en utilisant réellement le merveilleux code d'encodage cible de ce site. Je vais essayer.
--Python: Comment utiliser l'encodage cible (https://blog.amedama.jp/entry/target-mean-encoding-types#Leave-one-out-TS-%E4%BD%BF%E3%81%A3% E3% 81% A1% E3% 82% 83% E3% 83% 80% E3% 83% A1)
Une fonction qui crée un exemple de bloc de données.
import numpy as np
import pandas as pd
def getRandomDataFrame(data, numCol):
if data== 'train':
key = ["A" if x ==0 else 'B' for x in np.random.randint(2, size=(numCol,))]
value = np.random.randint(2, size=(numCol,))
df = pd.DataFrame({'Feature':key, 'Target':value})
return df
elif data=='test':
key = ["A" if x ==0 else 'B' for x in np.random.randint(2, size=(numCol,))]
df = pd.DataFrame({'Feature':key})
return df
else:
print(';)')
Vous pouvez générer un bloc de données avec le code suivant. Si test
est spécifié comme premier argument, la chaîne de variable objectif ne sera pas sortie. Spécifiez le nombre de lignes dans le deuxième argument.
train = getRandomDataFrame('train', 10)
test = getRandomDataFrame('test', 10)
Le contenu est comme indiqué dans la figure ci-dessous.
K-fold Target Encoding
Classe d'encodage cible K-fold. Il a fit
et transform
, donc il peut être utilisé de la même manière que le prétraitement de sklern. L'encodeur Test prend le résultat des données du train comme entrée et ajoute la fonction d'encodage cible.
De plus, la partie (1) écrite dans le commentaire est traitée pour remplir la ligne qui devient nan lorsqu'elle est pliée en K avec la valeur moyenne. Nous verrons cela plus tard.
from sklearn import base
from sklearn.model_selection import KFold
class KFoldTargetEncoderTrain(base.BaseEstimator,
base.TransformerMixin):
"""How to use.
targetc = KFoldTargetEncoderTrain('Feature','Target',n_fold=5)
new_train = targetc.fit_transform(train)
"""
def __init__(self,colnames,targetName,
n_fold=5, verbosity=True,
discardOriginal_col=False):
self.colnames = colnames
self.targetName = targetName
self.n_fold = n_fold
self.verbosity = verbosity
self.discardOriginal_col = discardOriginal_col
def fit(self, X, y=None):
return self
def transform(self,X):
assert(type(self.targetName) == str)
assert(type(self.colnames) == str)
assert(self.colnames in X.columns)
assert(self.targetName in X.columns)
mean_of_target = X[self.targetName].mean()
kf = KFold(n_splits = self.n_fold,
shuffle = False, random_state=2019)
col_mean_name = self.colnames + '_' + 'Kfold_Target_Enc'
X[col_mean_name] = np.nan
for tr_ind, val_ind in kf.split(X):
X_tr, X_val = X.iloc[tr_ind], X.iloc[val_ind]
X.loc[X.index[val_ind], col_mean_name] = X_val[self.colnames].map(X_tr.groupby(self.colnames)[self.targetName].mean())
X[col_mean_name].fillna(mean_of_target, inplace = True) #Remplissez la partie devenue nan avec la valeur moyenne--(1)
if self.verbosity:
encoded_feature = X[col_mean_name].values
print('Correlation between the new feature, {} and, {} is {}.'.format(col_mean_name,self.targetName,
np.corrcoef(X[self.targetName].values,encoded_feature)[0][1]))
if self.discardOriginal_col:
X = X.drop(self.targetName, axis=1)
return X
class TargetEncoderTest(base.BaseEstimator, base.TransformerMixin):
"""How to use.
test_targetc = TargetEncoderTest(new_train,
'Feature',
'Feature_Kfold_Target_Enc')
new_test = test_targetc.fit_transform(test)
"""
def __init__(self,train,colNames,encodedName):
self.train = train
self.colNames = colNames
self.encodedName = encodedName
def fit(self, X, y=None):
return self
def transform(self,X):
mean = self.train[[self.colNames, self.encodedName]].groupby(self.colNames).mean().reset_index()
dd = {}
for index, row in mean.iterrows():
dd[row[self.colNames]] = row[self.encodedName]
X[self.encodedName] = X[self.colNames]
X = X.replace({self.encodedName: dd})
return X
Utilisez-le comme suit. Dans le constructeur de KFoldTargetEncoderTrain
, spécifiez le nom de la colonne de la variable de catégorie à coder, le nom de la colonne de la variable objectif et le nombre de plis. Dans le constructeur de TargetEncoderTest
, spécifiez la trame de données codées, le nom de la colonne de la variable catégorielle codée et le nom de la colonne du montant de la caractéristique codée cible ([nom de la colonne de la variable catégorielle codée] _Kfold_Target_Enc).
targetc = KFoldTargetEncoderTrain('Feature','Target',n_fold=5)
new_train = targetc.fit_transform(train)
test_targetc = TargetEncoderTest(new_train, 'Feature', 'Feature_Kfold_Target_Enc')
new_test = test_targetc.fit_transform(test)
Chacun a le contenu suivant.
Vérifions new_train
. Puisqu'il s'agit d'un 5 fois, les données sont divisées en 5 plis de 2 lignes chacun. Le premier pli correspond aux première et deuxième lignes à partir du haut. Pour encoder les première et deuxième lignes, regardez les données combinées des quatre autres plis, les enregistrements des lignes 3-10. La valeur moyenne de «Cible» dans chaque groupe de A et B est 3/4 = 0,75 pour A et 1/4 = 0,25 pour B. Utilisez cette valeur pour encoder la valeur du premier repli. Les première et deuxième lignes du premier pli sont toutes les deux A, donc codez avec 0,75. Exécutez la procédure ci-dessus pour tous les plis.
Vérifions new_test
. Les données de test sont codées à l'aide de la valeur moyenne des variables catégorielles à coder pour la fonction d'encodage cible des données Train. A est (0,75 + 0,75 + 0,6 + 0,8 + 0,5 + 0,5) / 6 = 0,65 et B est (0,3333333333333333 + 0,33333333333333333 + 0,0 + 0,0) / 4 = 0,166666666666666666.
Ensuite, considérons le cas de devenir nan.
train = getRandomDataFrame('train', 10)
train['Feature'].iloc[0] = "C"
Avec ces données, je dois calculer la valeur moyenne du groupe C dans les plis restants pour encoder la première ligne, mais il n'y a pas de C dans les plis restants. Par conséquent, la fonction d'encodage cible sera nan. Par conséquent, remplissez-le avec la valeur moyenne des variables objectives de toutes les lignes. Par conséquent, C est (1 + 0 + 1 + 0 + 0 + 0 + 0 + 1 + 1 + 1) / 10 = 0,5.
Au fait, si vous commentez la partie (1), vous pouvez la laisser comme np.nan
. Avec LightGBM, vous pouvez apprendre et prédire même avec nan, il peut donc être préférable de ne pas renseigner la valeur moyenne.
Leave-one-out Target Encoding Il est dit que cette méthode ne doit pas être utilisée car elle fuit plus que le codage cible K fois. Cependant, je publierai le code car c'est un gros problème.
class LOOTargetEncoderTrain(base.BaseEstimator,
base.TransformerMixin):
"""How to use.
targetc = LOOTargetEncoderTrain('Feature','Target')
new_train = targetc.fit_transform(train)
"""
def __init__(self,colnames,targetName,
verbosity=True, discardOriginal_col=False):
self.colnames = colnames
self.targetName = targetName
self.verbosity = verbosity
self.discardOriginal_col = discardOriginal_col
def fit(self, X, y=None):
return self
def transform(self,X):
assert(type(self.targetName) == str)
assert(type(self.colnames) == str)
assert(self.colnames in X.columns)
assert(self.targetName in X.columns)
col_mean_name = self.colnames + '_' + 'Kfold_Target_Enc'
X[col_mean_name] = np.nan
self.agg_X = X.groupby(self.colnames).agg({self.targetName: ['sum', 'count']})
X[col_mean_name] = X.apply(self._loo_ts, axis=1)
return X
def _loo_ts(self, row):
group_ts = self.agg_X.loc[row[self.colnames]]
loo_sum = group_ts.loc[(self.targetName, 'sum')] - row[self.targetName]
loo_count = group_ts.loc[(self.targetName, 'count')] - 1
return loo_sum / loo_count
Cette fois, j'ai essayé l'encodage cible K-Fold.
Si la variable objective est binaire, il semble y avoir un moyen d'éviter le surapprentissage, tel que Smoothing.
Recommended Posts