天天看點

解決sklearn中使用OrdinalEncoder編碼測試集的類别特征中的未知類别時會報錯的問題

解決sklearn中使用OrdinalEncoder方法将類别特征(categorical/discrete features)變為有序數值特征(ordinal integers)時無法編碼測試集的類别特征中的未知類别的問題

當資料集中存在類别特征時(categorical/discrete features),我們一般的想法是将其轉變為數值型的特征,比如如果是不存在内在高低順序的類别特征,便可以使用sklearn中的OneHotEncoder方法将其轉變為數值型的特征,但是OneHotEncoder也會令資料集中的特征數量激增,以至于模型複雜度升高。

而另一種将類别特征轉變為數值型特征的方法則為OrdinalEncoder。OrdinalEncoder方法的一個特點是其可以根據标簽y來對類别特征進行順序編碼,比如[ [“北京”, 9], [“上海”,11], [“深圳”, 8] ]這個資料中,第一個特征為地點類别特征,第二個假設為标簽,在這裡可以看出不同的地點其标簽是有順序上的差異的,這種情況下的類别特征就很适合使用OrdinalEncoder方法來進行數值型編碼。

但是sklearn中的OrdinalEncoder方法根據訓練集的資料調用其内部的fit_transform()函數進行拟合之後,如果在用其對測試集中的類别特征進行數值型編碼轉換時,測試集的類别特征中有未知類别(即訓練集中此類别特征中未出現過的類别)時,OrdinalEncoder方法内的transform函數則會報錯。sklearn中的OrdinalEncoder方法如下所示,在初始化時是沒有指定怎麼處理測試集中未見類别的參數的 (而OneHotEncoder方法在初始化時則有指定處理測試集中未見類别的參數)。

解決sklearn中使用OrdinalEncoder編碼測試集的類别特征中的未知類别時會報錯的問題

解決這個問題的方法可以從OrdinalEncoder的源碼中找到, OrdinalEncoder方法中的transform函數如下所示:

解決sklearn中使用OrdinalEncoder編碼測試集的類别特征中的未知類别時會報錯的問題

從上圖可以看出OrdinalEncoder類中的transform函數是直接調用OrdinalEncoder類的父類中的self._transform()函數以及一些後續操作來對測試集中的類别特征進行轉化的,而OrdinalEncoder類則是繼承自_BaseEncoder類:

解決sklearn中使用OrdinalEncoder編碼測試集的類别特征中的未知類别時會報錯的問題

是以去檢視_BaseEncoder類中的_transform()函數時可以發現其實_BaseEncoder類中的_transform()函數中是有指定怎麼處理測試集中未見類别的參數 handle_unknown 的,如下圖:

解決sklearn中使用OrdinalEncoder編碼測試集的類别特征中的未知類别時會報錯的問題

從_BaseEncoder類中的_transform()函數源碼中可以看出,當将handle_unknown參數由"error"值改成别的任意值時,測試集類别特征中的未見類别将被編碼為0,而不是直接報錯。

是以當使用OrdinalEncoder去對含有未見類别特征的測試集進行編碼時,可以不使用OrdinalEncoder類中的transform()函數,而是直接調用OrdinalEncoder類所繼承的_BaseEncoder類中的_transform()函數來對測試集進行編碼。此時_transform()函數傳回的為一個元組對象,這個元組對象中的第一個元素為我們想要的對于測試集的編碼結果;而第二個元素則為編碼過程中的副産品mask矩陣,可以舍棄。再對測試集的編碼結果進行astype()操作,最後得到的即為最終的測試集編碼結果。

詳細操作代碼如下:

初始化訓練集與測試集:

from sklearn.preprocessing import OrdinalEncoder
import pandas as pd
import numpy as np

train_data = [["北京","網際網路" ,9],
        ["北京","金融" ,10],
        ["上海","金融" ,11],
        ["深圳","網際網路" ,8],
        ["廣州","網際網路" ,7.5],
        ["天津","銀行" ,6],]
        
test_data = [["杭州","金融"],]

train = pd.DataFrame()
test = pd.DataFrame()

for item in train_data:
    train = train.append({"location": item[0], "occupation": item[1], "salary":item[2]}, ignore_index=True)

for item in test_data:
    test = test.append({"location": item[0], "occupation": item[1]}, ignore_index=True)

# OrdinalEncoder()先拟合訓練集
ordinal_encoder = OrdinalEncoder()
train = ordinal_encoder.fit_transform(X=train.iloc[:,0:-1], y=train.iloc[:,-1])
           

當用OrdinalEncoder類中的transform()函數對含有未見類别特征的測試集進行編碼時,會報如下錯誤,可以看出測試集的地點特征中的 “杭州” 為未見類别。

test = ordinal_encoder.transform(test)
# 報錯如下
ValueError                                Traceback (most recent call last)
<ipython-input-17-7c9a92d31607> in <module>
----> 1 test = ordinal_encoder.transform(test)
      2 test

/opt/conda/lib/python3.7/site-packages/sklearn/preprocessing/_encoders.py in transform(self, X)
    955 
    956         """
--> 957         X_int, _ = self._transform(X)
    958         return X_int.astype(self.dtype, copy=False)
    959 

/opt/conda/lib/python3.7/site-packages/sklearn/preprocessing/_encoders.py in _transform(self, X, handle_unknown)
    120                     msg = ("Found unknown categories {0} in column {1}"
    121                            " during transform".format(diff, i))
--> 122                     raise ValueError(msg)
    123                 else:
    124                     # Set the problematic rows to an acceptable value and

ValueError: Found unknown categories ['杭州'] in column 0 during transform
           

當使用OrdinalEncoder類所繼承的_BaseEncoder類中的_transform()函數,令handle_unknown參數為 ‘ignore’ ,再對測試集的編碼結果進行astype()操作,結果如下:

test = ordinal_encoder._transform(test, handle_unknown='ignore')[0].astype(np.float64, copy=False)
test
# 結果如下
array([[0., 1.]])
           

可以看出_transform()函數将測試集中地點類别特征中的未見類别 “杭州” 可以編碼為0,而不會直接報錯。

以上過程即為解決方法, 在最後再重複強調一下解決辦法:

使用OrdinalEncoder去對含有未見類别特征的測試集進行編碼時,可以不使用OrdinalEncoder類中的transform()函數,而是直接調用OrdinalEncoder類所繼承的_BaseEncoder類中的_transform()函數來對測試集進行編碼。此時_transform()函數傳回的為一個元組對象,這個元組對象中的第一個元素為我們想要的對于測試集的編碼結果;而第二個元素則為編碼過程中的副産品mask矩陣,可以舍棄。再對測試集的編碼結果進行astype()操作,最後得到的即為最終的測試集編碼結果。