如何反转sklearn.OneHotEncoder转换以恢复原始数据?

时间:2014-03-21 01:50:31

标签: python machine-learning scipy scikit-learn

我使用sklearn.OneHotEncoder对我的分类数据进行了编码,然后将它们输入到随机森林分类器中。一切似乎都有效,我得到了预测的输出。

有没有办法扭转编码并将输出转换回原始状态?

8 个答案:

答案 0 :(得分:22)

一个很好的系统解决方法是从一些测试数据开始,然后使用sklearn.OneHotEncoder源代码。如果您不太关心它是如何工作的,只是想快速回答,请跳到最底层。

X = np.array([
    [3, 10, 15, 33, 54, 55, 78, 79, 80, 99],
    [5, 1, 3, 7, 8, 12, 15, 19, 20, 8]
]).T

n_values _

Lines 1763-1786确定n_values_参数。如果您设置n_values='auto'(默认值),将自动确定。或者,您可以为所有要素(int)指定最大值,或为每个要素(数组)指定最大值。我们假设我们使用的是默认值。所以以下几行执行:

n_samples, n_features = X.shape    # 10, 2
n_values = np.max(X, axis=0) + 1   # [100, 21]
self.n_values_ = n_values

feature_indices _

接下来计算feature_indices_参数。

n_values = np.hstack([[0], n_values])  # [0, 100, 21]
indices = np.cumsum(n_values)          # [0, 100, 121]
self.feature_indices_ = indices

所以feature_indices_只是n_values_的累积和,前缀为0。

稀疏矩阵构造

接下来,从数据构造scipy.sparse.coo_matrix。它从三个数组初始化:稀疏数据(全部),行索引和列索引。

column_indices = (X + indices[:-1]).ravel()
# array([  3, 105,  10, 101,  15, 103,  33, 107,  54, 108,  55, 112,  78, 115,  79, 119,  80, 120,  99, 108])

row_indices = np.repeat(np.arange(n_samples, dtype=np.int32), n_features)
# array([0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9], dtype=int32)

data = np.ones(n_samples * n_features)
# array([ 1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  1.,  1., 1.,  1.,  1.,  1.,  1.,  1.,  1.])

out = sparse.coo_matrix((data, (row_indices, column_indices)),
                        shape=(n_samples, indices[-1]),
                        dtype=self.dtype).tocsr()
# <10x121 sparse matrix of type '<type 'numpy.float64'>' with 20 stored elements in Compressed Sparse Row format>

请注意,coo_matrix会立即转换为scipy.sparse.csr_matrixcoo_matrix用作中间格式,因为它可以促进稀疏格式之间的快速转换。&#34;

active_features _

现在,如果n_values='auto',稀疏的csr矩阵将被压缩为只有具有活动特征的列。如果csr_matrix,则会返回稀疏sparse=True,否则会在返回之前将其设置为密集。

if self.n_values == 'auto':
    mask = np.array(out.sum(axis=0)).ravel() != 0
    active_features = np.where(mask)[0]  # array([  3,  10,  15,  33,  54,  55,  78,  79,  80,  99, 101, 103, 105, 107, 108, 112, 115, 119, 120])
    out = out[:, active_features]  # <10x19 sparse matrix of type '<type 'numpy.float64'>' with 20 stored elements in Compressed Sparse Row format>
    self.active_features_ = active_features

return out if self.sparse else out.toarray()

解码

现在让我们反向工作。鉴于上面详述的X功能返回的稀疏矩阵,我们想知道如何恢复OneHotEncoder。我们假设我们实际运行了上面的代码,方法是在我们的数据OneHotEncoder上实例化一个新的fit_transform并运行X

from sklearn import preprocessing
ohc = preprocessing.OneHotEncoder()  # all default params
out = ohc.fit_transform(X)

解决此问题的关键洞察力是理解active_features_out.indices之间的关系。对于csr_matrix,indices数组包含每个数据点的列号。但是,不保证对这些列号进行排序。要对它们进行排序,我们可以使用sorted_indices方法。

out.indices  # array([12,  0, 10,  1, 11,  2, 13,  3, 14,  4, 15,  5, 16,  6, 17,  7, 18, 8, 14,  9], dtype=int32)
out = out.sorted_indices()
out.indices  # array([ 0, 12,  1, 10,  2, 11,  3, 13,  4, 14,  5, 15,  6, 16,  7, 17,  8, 18,  9, 14], dtype=int32)

我们可以看到,在排序之前,索引实际上是沿着行反转的。换句话说,它们的排序是最后一列,最后一列是最后一列。从前两个要素可以看出这一点:[12,0]。 0对应于X第一列中的3,因为3是它分配给第一个活动列的最小元素。 12对应于X的第二列中的5。由于第一行占用10个不同的列,因此第二列(1)的最小元素获得索引10.下一个最小(3)获得索引11,第三个最小(5)获得索引12.排序后,索引是按照我们的预期订购。

接下来我们来看看active_features_

ohc.active_features_  # array([  3,  10,  15,  33,  54,  55,  78,  79,  80,  99, 101, 103, 105, 107, 108, 112, 115, 119, 120])

请注意,有19个元素,对应于数据中不同元素的数量(一个元素,8,重复一次)。另请注意,这些按顺序排列。 X第一列中的功能相同,第二列中的功能简单地用100加总,相当于ohc.feature_indices_[1]

回顾out.indices,我们可以看到最大列数是18,这是我们编码中的19个活动特征。对此处关系的一点想法表明,ohc.active_features_的索引对应于ohc.indices中的列号。有了这个,我们可以解码:

import numpy as np
decode_columns = np.vectorize(lambda col: ohc.active_features_[col])
decoded = decode_columns(out.indices).reshape(X.shape)

这给了我们:

array([[  3, 105],
       [ 10, 101],
       [ 15, 103],
       [ 33, 107],
       [ 54, 108],
       [ 55, 112],
       [ 78, 115],
       [ 79, 119],
       [ 80, 120],
       [ 99, 108]])

我们可以通过从ohc.feature_indices_中减去偏移量来恢复原始要素值:

recovered_X = decoded - ohc.feature_indices_[:-1]
array([[ 3,  5],
       [10,  1],
       [15,  3],
       [33,  7],
       [54,  8],
       [55, 12],
       [78, 15],
       [79, 19],
       [80, 20],
       [99,  8]])

请注意,您需要具有X的原始形状,这只是(n_samples, n_features)

TL; DR

鉴于名为sklearn.OneHotEncoder的{​​{1}}实例,编码数据(ohc)从scipy.sparse.csr_matrixohc.fit_transform输出,称为ohc.transform,原始数据out的形状,使用以下内容恢复原始数据(n_samples, n_feature)

X

答案 1 :(得分:6)

只需使用ohe.active_features_计算编码值的点积。它适用于稀疏和密集表示。例如:

from sklearn.preprocessing import OneHotEncoder
import numpy as np

orig = np.array([6, 9, 8, 2, 5, 4, 5, 3, 3, 6])

ohe = OneHotEncoder()
encoded = ohe.fit_transform(orig.reshape(-1, 1)) # input needs to be column-wise

decoded = encoded.dot(ohe.active_features_).astype(int)
assert np.allclose(orig, decoded)

关键的见解是OHE模型的active_features_属性代表每个二进制列的原始值。因此,我们可以通过简单地使用active_features_计算点积来解码二进制编码的数字。对于每个数据点,只有一个1原始值的位置。

答案 2 :(得分:1)

简短的回答是“不”。编码器获取您的分类数据并自动将其转换为一组合理的数字。

答案越长,“不会自动”。但是,如果使用n_values参数提供显式映射,则可以在另一端实现自己的解码。有关如何完成此操作的一些提示,请参阅documentation

那就是说,这是一个相当奇怪的问题。您可能希望使用DictVectorizer

答案 3 :(得分:1)

如果功能密集,如[1,2,4,5,6],则会遗漏多个号码。然后,我们可以将它们映射到相应的位置。

>>> import numpy as np
>>> from scipy import sparse
>>> def _sparse_binary(y):
...     # one-hot codes of y with scipy.sparse matrix.
...     row = np.arange(len(y))
...     col = y - y.min()
...     data = np.ones(len(y))
...     return sparse.csr_matrix((data, (row, col)))
... 
>>> y = np.random.randint(-2,2, 8).reshape([4,2])
>>> y
array([[ 0, -2],
       [-2,  1],
       [ 1,  0],
       [ 0, -2]])
>>> yc = [_sparse_binary(y[:,i]) for i in xrange(2)]
>>> for i in yc: print i.todense()
... 
[[ 0.  0.  1.  0.]
 [ 1.  0.  0.  0.]
 [ 0.  0.  0.  1.]
 [ 0.  0.  1.  0.]]
[[ 1.  0.  0.  0.]
 [ 0.  0.  0.  1.]
 [ 0.  0.  1.  0.]
 [ 1.  0.  0.  0.]]
>>> [i.shape for i in yc]
[(4, 4), (4, 4)]

这是一种妥协且简单的方法,但是argmax()可以很容易地反转,例如:

>>> np.argmax(yc[0].todense(), 1) + y.min(0)[0]
matrix([[ 0],
        [-2],
        [ 1],
        [ 0]])

答案 4 :(得分:1)

如何进行单热编码

请参阅https://stackoverflow.com/a/42874726/562769

import numpy as np
nb_classes = 6
data = [[2, 3, 4, 0]]

def indices_to_one_hot(data, nb_classes):
    """Convert an iterable of indices to one-hot encoded labels."""
    targets = np.array(data).reshape(-1)
    return np.eye(nb_classes)[targets]

如何撤销

def one_hot_to_indices(data):
    indices = []
    for el in data:
        indices.append(list(el).index(1))
    return indices


hot = indices_to_one_hot(orig_data, nb_classes)
indices = one_hot_to_indices(hot)

print(orig_data)
print(indices)

给出:

[[2, 3, 4, 0]]
[2, 3, 4, 0]

答案 5 :(得分:0)

熊猫方法: 要将分类变量转换为二进制变量,pd.get_dummies可以执行此操作并将其转换回,您可以使用pd.Series.idxmax()找到值为1的值的索引。然后,您可以映射到列表(根据原始数据在其中索引)或字典。

import pandas as pd
import numpy as np

col = np.random.randint(1,5,20)
df = pd.DataFrame({'A': col})
df.head()

    A
0   2
1   2
2   1
3   1
4   3

df_dum = pd.get_dummies(df['A'])
df_dum.head()

    1   2   3   4
0   0   1   0   0
1   0   1   0   0
2   1   0   0   0
3   1   0   0   0
4   0   0   1   0


df_n = df_dum.apply(lambda x: x.idxmax(), axis = 1)
df_n.head()

0    2
1    2
2    1
3    1
4    3

答案 6 :(得分:0)

从scikit-learn的0.20版本开始,active_features_类的OneHotEncoder属性已被弃用,因此我建议改为使用categories_属性。

以下功能可以帮助您从经过一键编码的矩阵中恢复原始数据:

def reverse_one_hot(X, y, encoder):
    reversed_data = [{} for _ in range(len(y))]
    all_categories = list(itertools.chain(*encoder.categories_))
    category_names = ['category_{}'.format(i+1) for i in range(len(encoder.categories_))]
    category_lengths = [len(encoder.categories_[i]) for i in range(len(encoder.categories_))]

    for row_index, feature_index in zip(*X.nonzero()):
        category_value = all_categories[feature_index]
        category_name = get_category_name(feature_index, category_names, category_lengths)
        reversed_data[row_index][category_name] = category_value
        reversed_data[row_index]['target'] = y[row_index]

    return reversed_data


def get_category_name(index, names, lengths):

    counter = 0
    for i in range(len(lengths)):
        counter += lengths[i]
        if index < counter:
            return names[i]
    raise ValueError('The index is higher than the number of categorical values')

为了进行测试,我创建了一个小的数据集,其中包含用户对用户的评分

data = [
    {'user_id': 'John', 'item_id': 'The Matrix', 'rating': 5},
    {'user_id': 'John', 'item_id': 'Titanic', 'rating': 1},
    {'user_id': 'John', 'item_id': 'Forrest Gump', 'rating': 2},
    {'user_id': 'John', 'item_id': 'Wall-E', 'rating': 2},
    {'user_id': 'Lucy', 'item_id': 'The Matrix', 'rating': 5},
    {'user_id': 'Lucy', 'item_id': 'Titanic', 'rating': 1},
    {'user_id': 'Lucy', 'item_id': 'Die Hard', 'rating': 5},
    {'user_id': 'Lucy', 'item_id': 'Forrest Gump', 'rating': 2},
    {'user_id': 'Lucy', 'item_id': 'Wall-E', 'rating': 2},
    {'user_id': 'Eric', 'item_id': 'The Matrix', 'rating': 2},
    {'user_id': 'Eric', 'item_id': 'Die Hard', 'rating': 3},
    {'user_id': 'Eric', 'item_id': 'Forrest Gump', 'rating': 5},
    {'user_id': 'Eric', 'item_id': 'Wall-E', 'rating': 4},
    {'user_id': 'Diane', 'item_id': 'The Matrix', 'rating': 4},
    {'user_id': 'Diane', 'item_id': 'Titanic', 'rating': 3},
    {'user_id': 'Diane', 'item_id': 'Die Hard', 'rating': 5},
    {'user_id': 'Diane', 'item_id': 'Forrest Gump', 'rating': 3},
]

data_frame = pandas.DataFrame(data)
data_frame = data_frame[['user_id', 'item_id', 'rating']]
ratings = data_frame['rating']
data_frame.drop(columns=['rating'], inplace=True)

如果我们要构建预测模型,则必须记住在对DataFrame进行编码之前,先将其从变量ratings = data_frame['rating'] data_frame.drop(columns=['rating'], inplace=True) 中删除。

ohc = OneHotEncoder()
encoded_data = ohc.fit_transform(data_frame)
print(encoded_data)

然后我们继续进行编码

  (0, 2)    1.0
  (0, 6)    1.0
  (1, 2)    1.0
  (1, 7)    1.0
  (2, 2)    1.0
  (2, 5)    1.0
  (3, 2)    1.0
  (3, 8)    1.0
  (4, 3)    1.0
  (4, 6)    1.0
  (5, 3)    1.0
  (5, 7)    1.0
  (6, 3)    1.0
  (6, 4)    1.0
  (7, 3)    1.0
  (7, 5)    1.0
  (8, 3)    1.0
  (8, 8)    1.0
  (9, 1)    1.0
  (9, 6)    1.0
  (10, 1)   1.0
  (10, 4)   1.0
  (11, 1)   1.0
  (11, 5)   1.0
  (12, 1)   1.0
  (12, 8)   1.0
  (13, 0)   1.0
  (13, 6)   1.0
  (14, 0)   1.0
  (14, 7)   1.0
  (15, 0)   1.0
  (15, 4)   1.0
  (16, 0)   1.0
  (16, 5)   1.0

这将导致:

reverse_one_hot

编码后,我们可以使用上面定义的reverse_data = matrix_utils.reverse_one_hot(encoded_data, ratings, ohc) print(pandas.DataFrame(reverse_data)) 函数反转,就像这样:

   category_1    category_2  target
0        John    The Matrix       5
1        John       Titanic       1
2        John  Forrest Gump       2
3        John        Wall-E       2
4        Lucy    The Matrix       5
5        Lucy       Titanic       1
6        Lucy      Die Hard       5
7        Lucy  Forrest Gump       2
8        Lucy        Wall-E       2
9        Eric    The Matrix       2
10       Eric      Die Hard       3
11       Eric  Forrest Gump       5
12       Eric        Wall-E       4
13      Diane    The Matrix       4
14      Diane       Titanic       3
15      Diane      Die Hard       5
16      Diane  Forrest Gump       3

哪个给了我们

parsing

答案 7 :(得分:0)

numpy.argmax()axis = 1一起使用。

示例:

ohe_encoded = np.array([[0, 0, 1], [0, 1, 0], [0, 1, 0], [1, 0, 0]])
ohe_encoded
> array([[0, 0, 1],
         [0, 1, 0],
         [0, 1, 0],
         [1, 0, 0]])

np.argmax(ohe_encoded, axis = 1)
> array([2, 1, 1, 0], dtype=int64)