省メモリOneHotEncoder


OneHotEncoderは、エンコーディングを行う際に、入力データをnumpyに転送する.arrayオブジェクトを使用して、速度を最適化します.これにより、カラムの値が300000文字のテキストであると、メモリが不足するなど、中性子タイプのメモリが不足する可能性があります.これに対して、このバージョンのスーパーメモリを節約します.
OneHotEncoderとget_feature_namesは使えますので、TransformMixinクラスを継承してメモリを非常に節約したバージョンを書きました.400 Wサンプル27列の処理が必要なデータセットでOneHotEncoderは2分35秒、本プログラムは1分35秒かかります.
##本コードは、大規模なテキストが含まれている場合###本コードは、大規模なテキストが含まれている場合###本コードは、大規模なテキストが含まれている場合##に適用されます.
実際には,各列の各テキストの代わりにintを用いることで,OneHotEncoderに入力することでその速度を発揮できる.
from sklearn.base import TransformerMixin
import numpy as np
import scipy.sparse as sp
from collections import defaultdict as ddt
from itertools import chain


class LittleOntHotEncoder(TransformerMixin):
    """ OneHotEncoder, """
    def __init__(self, *args, **kwargs):
        super(TransformerMixin, self).__init__(*args, **kwargs)
        
        self._per_feature = ddt(lambda : set())
        self._feat2idx = ddt(lambda : dict())
        self._feat2default = {
     }
        self._all_feature_number = -1
    
    def get_feature_names(self):
        items = chain.from_iterable([[(f"{i}_{e}", idx) for e, idx in dic.items()] for i, dic in self._feat2idx.items()])
        return [feat for feat, _ in sorted(items, key=lambda x:x[1])]

    def fit(self, cate_smples):
        per_feature = self._per_feature
        for line in cate_smples:
            for i, e in enumerate(line):
                per_feature[i].add(e)
        self._per_feature = per_feature
        
        _all_feature_number = 0
        
        feat2idx = self._feat2idx
        items = sorted(per_feature.items(), key=lambda x:x[0])
        for idx, feat_set in items:
            for e in feat_set:
                feat2idx[idx][e] = _all_feature_number
                self._feat2default[idx] = _all_feature_number
                _all_feature_number += 1
        self._feat2idx = feat2idx
        self._all_feature_number = _all_feature_number
        
    def transform(self, cate_smples):
        M = len(cate_smples)
        n = len(cate_smples[0])
        N = self._all_feature_number
        feat2idx = self._feat2idx
        feat2default = self._feat2default
        
        _data = np.ones((M*n), dtype=np.int8)
        _indices = np.array([[feat2idx[feat_idx].get(e, feat2default[feat_idx]) for feat_idx, e in enumerate(line)] for line in cate_smples])
        _indptr = np.array([i*n for i in range(M)] + [M])
        return sp.csr_matrix((_data, np.reshape(_indices, (M*n,)), _indptr), shape=(M, N))
    
    def fit_transform(self, cate_smples):
        self.fit(cate_smples)
        return self.transform(cate_smples)