.. _artificielcategoryhashrst: ===================== Hashing et catégories ===================== .. only:: html **Links:** :download:`notebook `, :downloadlink:`html `, :download:`PDF `, :download:`python `, :downloadlink:`slides `, :githublink:`GitHub|_doc/notebooks/lectures/artificiel_category_hash.ipynb|*` Le hashing est utilise lorsque le nombre de catégories est trop grand. .. code:: ipython3 %matplotlib inline On construit un jeu très simple avec deux catégories, une entière, une au format texte, distributé selon une loi `multinomial `__. .. code:: ipython3 import pandas import numpy prob = numpy.ones(100) / 100 rnd = numpy.random.binomial(100, 0.5, 10000) df = pandas.DataFrame(dict(cat_int=rnd, cat_text=['cat%d' % i for i in rnd])) df.head() .. raw:: html
cat_int cat_text
0 51 cat51
1 51 cat51
2 53 cat53
3 49 cat49
4 56 cat56
.. code:: ipython3 dfc = df.drop('cat_int', axis=1).copy() dfc['count'] = 1 gr = dfc.groupby('cat_text').sum().sort_values('count', ascending=False) pandas.concat([gr.head(), gr.tail()]) .. raw:: html
count
cat_text
cat48 780
cat49 771
cat50 770
cat52 755
cat51 746
cat67 3
cat31 2
cat32 1
cat33 1
cat69 1
.. code:: ipython3 len(set(df.cat_text)) .. parsed-literal:: 38 On utilise le module `Category Encoders `__ implémente la classe `Hashing `__ qui applique une fonction de *hash* pour retourner une séquence de nombre binaires. Il y a 100 catégories distinctes. Il faut au pire 8 colonnes binaires pour représenter l’information. On vérifie avec un encoder binaire qui encode chaque catégorie de façon binaire. .. code:: ipython3 from category_encoders import BinaryEncoder BinaryEncoder(cols=['cat_text']).fit_transform(df).head() .. raw:: html
cat_text_0 cat_text_1 cat_text_2 cat_text_3 cat_text_4 cat_text_5 cat_int
0 0 0 0 0 0 0 51
1 0 0 0 0 0 0 51
2 0 0 0 0 0 1 53
3 0 0 0 0 1 0 49
4 0 0 0 0 1 1 56
.. code:: ipython3 from category_encoders import HashingEncoder HashingEncoder(cols=['cat_text']).fit_transform(df).head() .. raw:: html
col_0 col_1 col_2 col_3 col_4 col_5 col_6 col_7 cat_int
0 0 0 0 0 0 0 0 1 51
1 0 0 0 0 0 0 0 1 51
2 0 0 0 0 1 0 0 0 53
3 0 0 0 1 0 0 0 0 49
4 0 0 1 0 0 0 0 0 56
On peut réduire le nombre de colonnes. L’information est compressée mais pas de façon réversible. L’information est codée sur 4 colonnes. .. code:: ipython3 res = HashingEncoder(cols=['cat_text'], n_components=4, hash_method="sha256").fit_transform(df) res.head() .. raw:: html
col_0 col_1 col_2 col_3 cat_int
0 0 1 0 0 51
1 0 1 0 0 51
2 0 0 0 1 53
3 0 0 0 1 49
4 0 1 0 0 56
.. code:: ipython3 res['col_int'] = res.col_0 + 2 * res.col_1 + 4 * res.col_2 + 8 * res.col_3 res.head() .. raw:: html
col_0 col_1 col_2 col_3 cat_int col_int
0 0 1 0 0 51 2
1 0 1 0 0 51 2
2 0 0 0 1 53 8
3 0 0 0 1 49 8
4 0 1 0 0 56 2
.. code:: ipython3 res[['col_int', 'cat_int']].groupby('col_int').count() .. raw:: html
cat_int
col_int
1 3913
2 2021
4 2
8 4064
Pas tout-à-fait ce à quoi je m’attendais. On peut aussi utiliser quelque chose comme ceci : hash + encoding binaire. .. code:: ipython3 dfc = df.copy() dfc['cat_hash'] = df["cat_text"].apply(lambda x: abs(hash(x)) % (2**4)) res = BinaryEncoder(cols=['cat_hash']).fit_transform(dfc) res.head() .. raw:: html
cat_hash_0 cat_hash_1 cat_hash_2 cat_hash_3 cat_int cat_text
0 0 0 0 0 51 cat51
1 0 0 0 0 51 cat51
2 0 0 0 1 53 cat53
3 0 0 1 0 49 cat49
4 0 0 1 1 56 cat56
.. code:: ipython3 res['col_int'] = res.cat_hash_0 + 2 * res.cat_hash_1 + 4 * res.cat_hash_2 + 8 * res.cat_hash_3 res.head() .. raw:: html
cat_hash_0 cat_hash_1 cat_hash_2 cat_hash_3 cat_int cat_text col_int
0 0 0 0 0 51 cat51 0
1 0 0 0 0 51 cat51 0
2 0 0 0 1 53 cat53 8
3 0 0 1 0 49 cat49 4
4 0 0 1 1 56 cat56 12
.. code:: ipython3 res[['col_int', 'cat_int', 'cat_text']].groupby(['col_int', 'cat_text'], as_index=False) \ .count().pivot('cat_text', 'col_int', 'cat_int').astype(str).replace("nan", "") .. raw:: html
col_int 0 1 2 3 4 5 6 8 9 10 11 12 13 14
cat_text
cat31 2.0
cat32 1.0
cat33 1.0
cat34 6.0
cat35 9.0
cat36 13.0
cat37 23.0
cat38 45.0
cat39 65.0
cat40 101.0
cat41 168.0
cat42 196.0
cat43 314.0
cat44 373.0
cat45 486.0
cat46 583.0
cat47 691.0
cat48 780.0
cat49 771.0
cat50 770.0
cat51 746.0
cat52 755.0
cat53 668.0
cat54 598.0
cat55 473.0
cat56 404.0
cat57 303.0
cat58 220.0
cat59 162.0
cat60 105.0
cat61 68.0
cat62 43.0
cat63 23.0
cat64 17.0
cat65 9.0
cat66 4.0
cat67 3.0
cat69 1.0
Ce qu’on espère : que deux classes sur-représentées ne soient pas encodées par la même valeur, ce qui est le cas ici. Il faudra donc augmenter la taille du hash (16 ici).