sklearn.LabelEncoder
がトレーニングセットに適合している場合、テストセットで使用したときに新しい値が検出されると破損する可能性があります。
このために思いつく唯一の解決策は、テストセットの新しいもの(つまり、既存のクラスに属さないもの)をすべて"<unknown>"
にマップし、対応するクラスをLabelEncoder
に明示的に追加することです。 :
# train and test are pandas.DataFrame's and c is whatever column
le = LabelEncoder()
le.fit(train[c])
test[c] = test[c].map(lambda s: '<unknown>' if s not in le.classes_ else s)
le.classes_ = np.append(le.classes_, '<unknown>')
train[c] = le.transform(train[c])
test[c] = le.transform(test[c])
これは機能しますが、より良い解決策はありますか?
更新
@sapo_cosmicoがコメントで指摘しているように、LabelEncoder.transform
の実装の変更であると仮定すると、上記はもはや機能しないようです。現在、np.searchsorted
を使用しているようですそれが以前のケースだったかどうかを知る)。したがって、<unknown>
クラスを既に抽出されたクラスのLabelEncoder
のリストに追加する代わりに、ソートされた順序で挿入する必要があります。
import bisect
le_classes = le.classes_.tolist()
bisect.insort_left(le_classes, '<unknown>')
le.classes_ = le_classes
しかし、これは全体的にかなり不格好だと感じているので、これにはもっと良いアプローチがあると確信しています。
この目に見えないデータの問題のために、私はパンダの get_dummies に切り替えました。
dummy_train = pd.get_dummies(train)
dummy_new = pd.get_dummies(new_data)
dummy_new.reindex(columns = dummy_train.columns, fill_value=0)
事実上、カテゴリに属する新しい機能は分類子には含まれませんが、何をすべきか分からないため、問題を引き起こすことはないと思います。
LabelEncoderは基本的に辞書です。これを抽出して、将来のエンコードに使用できます。
from sklearn.preprocessing import LabelEncoder
le = preprocessing.LabelEncoder()
le.fit(X)
le_dict = dict(Zip(le.classes_, le.transform(le.classes_)))
単一の新しいアイテムのラベルを取得します。アイテムが見つからない場合は値を不明として設定します
le_dict.get(new_item, '<Unknown>')
データフレーム列のラベルを取得します。
df[your_col].apply(lambda x: le_dict.get(x, <unknown_value>))
あなたがしたことは、この状況に直面したときに他の人がすることと非常に似ているという印象を受けます。
目に見えないラベルをLabelEncoderにエンコードする機能を追加する努力がありました(特に https://github.com/scikit-learn/scikit-learn/pull/348 および https ://github.com/scikit-learn/scikit-learn/pull/3599 )、しかし、既存の動作を変更することは、一見すると思われるよりも実際には難しいです。
今のところ、「語彙外」ラベルの処理はscikit-learnの個々のユーザーに任されているように見えます。
トランスフォーマーとSklearnパイプラインのラッパーの構築に取り組んでいる2人の開発者を知っています。目に見えない値を処理できる2つの堅牢なエンコーダートランスフォーマー(1つはダミーエンコーダー、1つはラベルエンコーダー)を備えています。 skutilライブラリのドキュメントはこちら _skutil.preprocessing.OneHotCategoricalEncoder
_または_skutil.preprocessing.SafeLabelEncoder
_を検索します。 SafeLabelEncoder()
では、見えない値は999999に自動エンコードされます。
私はこの問題に対処しようとしていましたが、LabelEncoderを使用して、または使用せずに、トレインおよびテストセットからカテゴリデータをエンコードする2つの便利な方法を見つけました。新しいカテゴリには、いくつかの既知の「c」(「other」や「missing」など)が含まれています。最初の方法はより高速に動作するようです。それがあなたを助けることを願っています。
import pandas as pd
import time
df=pd.DataFrame()
df["a"]=['a','b', 'c', 'd']
df["b"]=['a','b', 'e', 'd']
#LabelEncoder + map
t=time.clock()
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
suf="_le"
col="a"
df[col+suf] = le.fit_transform(df[col])
dic = dict(Zip(le.classes_, le.transform(le.classes_)))
col='b'
df[col+suf]=df[col].map(dic).fillna(dic["c"]).astype(int)
print(time.clock()-t)
#---
#pandas category
t=time.clock()
df["d"] = df["a"].astype('category').cat.codes
dic =df["a"].astype('category').cat.categories.tolist()
df['f']=df['b'].astype('category',categories=dic).fillna("c").cat.codes
df.dtypes
print(time.clock()-t)
これをサポートするクラスを作成しました。新しいラベルがある場合は、未知のクラスとして割り当てられます。
from sklearn.preprocessing import LabelEncoder
import numpy as np
class LabelEncoderExt(object):
def __init__(self):
"""
It differs from LabelEncoder by handling new classes and providing a value for it [Unknown]
Unknown will be added in fit and transform will take care of new item. It gives unknown class id
"""
self.label_encoder = LabelEncoder()
# self.classes_ = self.label_encoder.classes_
def fit(self, data_list):
"""
This will fit the encoder for all the unique values and introduce unknown value
:param data_list: A list of string
:return: self
"""
self.label_encoder = self.label_encoder.fit(list(data_list) + ['Unknown'])
self.classes_ = self.label_encoder.classes_
return self
def transform(self, data_list):
"""
This will transform the data_list to id list where the new values get assigned to Unknown class
:param data_list:
:return:
"""
new_data_list = list(data_list)
for unique_item in np.unique(data_list):
if unique_item not in self.label_encoder.classes_:
new_data_list = ['Unknown' if x==unique_item else x for x in new_data_list]
return self.label_encoder.transform(new_data_list)
サンプルの使用法:
country_list = ['Argentina', 'Australia', 'Canada', 'France', 'Italy', 'Spain', 'US', 'Canada', 'Argentina, ''US']
label_encoder = LabelEncoderExt()
label_encoder.fit(country_list)
print(label_encoder.classes_) # you can see new class called Unknown
print(label_encoder.transform(country_list))
new_country_list = ['Canada', 'France', 'Italy', 'Spain', 'US', 'India', 'Pakistan', 'South Africa']
print(label_encoder.transform(new_country_list))
私は最近、この問題に遭遇し、問題に対する非常に迅速な解決策を思いつくことができました。私の答えはこの問題だけでは解決しませんが、あなたの問題でも簡単に機能します。 (かなりクールだと思う)
私はpandasデータフレームで作業しており、もともとsklearns labelencoder()を使用してデータをエンコードし、それをプログラムの他のモジュールで使用するためにピクルスします。
ただし、sklearnの前処理のラベルエンコーダーには、エンコードアルゴリズムに新しい値を追加する機能がありません。複数の値をエンコードし、マッピング値をAS WELLでエンコーダに新しい値を追加できるように保存するという問題を解決しました(ここで、私がしたことの大まかな概要を示します)。
encoding_dict = dict()
for col in cols_to_encode:
#get unique values in the column to encode
values = df[col].value_counts().index.tolist()
# create a dictionary of values and corresponding number {value, number}
dict_values = {value: count for value, count in Zip(values, range(1,len(values)+1))}
# save the values to encode in the dictionary
encoding_dict[col] = dict_values
# replace the values with the corresponding number from the dictionary
df[col] = df[col].map(lambda x: dict_values.get(x))
次に、辞書をJSONファイルに保存するだけで、新しい値と対応する整数値を追加することで、辞書を取得して必要な値を追加できます。
Replace()の代わりにmap()を使用する背後にあるいくつかの理由を説明します。 pandas replace()関数を使用すると、約117,000行のコードを繰り返すのに1分以上かかりました。mapを使用すると、その時間はわずか100ミリ秒になりました。
TLDR:sklearnsの前処理を使用する代わりに、マッピングディクショナリを作成し、自分で値をマッピングすることにより、データフレームを操作します。
モデルのトレーニングとテストだけの場合は、データセット全体でラベルをエンコードしないでください。そして、エンコーダーオブジェクトから生成されたクラスを使用します。
encoder = LabelEncoder()
encoder.fit_transform(df["label"])
train_y = encoder.transform(train_y)
test_y = encoder.transform(test_y)