Как преобразовать sklearn.OneHotEncoder для восстановления исходных данных?

Я закодировал свои категориальные данные, используя sklearn.OneHotEncoder и sklearn.OneHotEncoder их произвольному классификатору леса. Кажется, все работает, и я получил свой предсказанный результат.

Есть ли способ отменить кодировку и преобразовать мой вывод обратно в исходное состояние?

Ответ 1

Хороший систематический способ понять это - начать с некоторых тестовых данных и работать через источник 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_

Строки 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_matrix. coo_matrix используется как промежуточный формат, потому что "облегчает быстрое преобразование среди разреженных форматов".

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 код выше, 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 массив индексов содержит номера столбцов для каждой точки данных. Однако эти номера столбцов не гарантируются для сортировки. Чтобы отсортировать их, мы можем использовать метод 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 соответствует 3 в первом столбце X, так как 3 является минимальным элементом, который был назначен первому активному столбцу. 12 соответствует 5 во втором столбце X Поскольку первая строка занимает 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 экземпляр под названием ohc, кодированные данные (scipy.sparse.csr_matrix), выводимым из ohc.fit_transform или ohc.transform называемого out, и формы исходных данных (n_samples, n_feature), восстановить исходные данные X с:

recovered_X = np.array([ohc.active_features_[col] for col in out.sorted_indices().indices])
                .reshape(n_samples, n_features) - ohc.feature_indices_[:-1]

Ответ 2

Просто вычислите dot-произведение закодированных значений с помощью 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)

Ключевое понимание состоит в том, что атрибут active_features_ модели active_features_ представляет исходные значения для каждого двоичного столбца. Таким образом, мы можем декодировать двоично-кодированный номер, просто вычислив dot-продукт с active_features_. Для каждого данных указывают есть только один 1 позицию исходного значения.

Ответ 3

Короткий ответ - нет". Кодер принимает ваши категориальные данные и автоматически преобразует его в разумный набор чисел.

Более длинный ответ "не автоматически". Если вы предоставляете явное сопоставление с использованием параметра n_values, вы можете, вероятно, реализовать собственное декодирование с другой стороны. См. Документацию для некоторых советов о том, как это можно сделать.

Тем не менее, это довольно странный вопрос. Вместо этого вы можете использовать DictVectorizer

Ответ 4

Если функции плотные, например [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]])

Ответ 5

Как однострочный кодировать

См. fooobar.com/info/81943/...

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]