web-dev-qa-db-ja.com

sklearn.LabelEncoderの前に値が表示されない

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

しかし、これは全体的にかなり不格好だと感じているので、これにはもっと良いアプローチがあると確信しています。

52
cjauvin

この目に見えないデータの問題のために、私はパンダの get_dummies に切り替えました。

  • トレーニングデータにダミーを作成する
    dummy_train = pd.get_dummies(train)
  • 新しい(見えないデータ)にダミーを作成する
    dummy_new = pd.get_dummies(new_data)
  • 新しいデータのインデックスをトレーニングデータの列に付け直し、欠損値を0で埋めます
    dummy_new.reindex(columns = dummy_train.columns, fill_value=0)

事実上、カテゴリに属する​​新しい機能は分類子には含まれませんが、何をすべきか分からないため、問題を引き起こすことはないと思います。

34
sapo_cosmico

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>))
8
Rani

あなたがしたことは、この状況に直面したときに他の人がすることと非常に似ているという印象を受けます。

目に見えないラベルをLabelEncoderにエンコードする機能を追加する努力がありました(特に https://github.com/scikit-learn/scikit-learn/pull/348 および https ://github.com/scikit-learn/scikit-learn/pull/3599 )、しかし、既存の動作を変更することは、一見すると思われるよりも実際には難しいです。

今のところ、「語彙外」ラベルの処理はscikit-learnの個々のユーザーに任されているように見えます。

7
lmjohns3

トランスフォーマーとSklearnパイプラインのラッパーの構築に取り組んでいる2人の開発者を知っています。目に見えない値を処理できる2つの堅牢なエンコーダートランスフォーマー(1つはダミーエンコーダー、1つはラベルエンコーダー)を備えています。 skutilライブラリのドキュメントはこちら _skutil.preprocessing.OneHotCategoricalEncoder_または_skutil.preprocessing.SafeLabelEncoder_を検索します。 SafeLabelEncoder()では、見えない値は999999に自動エンコードされます。

3
Jason

私はこの問題に対処しようとしていましたが、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)
2
Yury Wallet

これをサポートするクラスを作成しました。新しいラベルがある場合は、未知のクラスとして割り当てられます。

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))
2

私は最近、この問題に遭遇し、問題に対する非常に迅速な解決策を思いつくことができました。私の答えはこの問題だけでは解決しませんが、あなたの問題でも簡単に機能します。 (かなりクールだと思う)

私は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の前処理を使用する代わりに、マッピングディクショナリを作成し、自分で値をマッピングすることにより、データフレームを操作します。

2
Ethan Kulla

モデルのトレーニングとテストだけの場合は、データセット全体でラベルをエンコードしないでください。そして、エンコーダーオブジェクトから生成されたクラスを使用します。

encoder = LabelEncoder()
encoder.fit_transform(df["label"])
train_y = encoder.transform(train_y)
test_y = encoder.transform(test_y)
0
Namrata Tolani