[PYTHON] Conversion d'économie de mémoire des données de journal en quantité de caractéristiques de catégorie séquentielle en tenant compte des séries temporelles

introduction

Bonjour, je suis ingénieur en apprentissage automatique. C'est Kawamoto. Il faisait trop froid et j'ai attrapé un rhume. Aujourd'hui, j'écrirai sur la façon d'effectuer le prétraitement tout en réduisant la consommation de mémoire pour les données de journal qui nécessitent la prise en compte des informations de série chronologique.

Chose que tu veux faire

En supposant que vous disposez d'un ensemble de données avec de tels journaux de comportement pour chaque utilisateur

    userid  itemid  categoryid  timestamp
0        0       3           1 2019-01-04
1        0       4           1 2019-01-08
2        0       4           1 2019-01-19
3        0       5           1 2019-01-02
4        0       7           2 2019-01-17
5        0       8           2 2019-01-07
6        1       0           0 2019-01-06
7        1       1           0 2019-01-14
8        1       2           0 2019-01-20
9        1       6           2 2019-01-01
10       1       7           2 2019-01-12
11       1       8           2 2019-01-18
12       2       3           1 2019-01-16
13       2       4           1 2019-01-15
14       2       5           1 2019-01-10
15       2       5           1 2019-01-13
16       2       6           2 2019-01-03
17       2       7           2 2019-01-05
18       2       8           2 2019-01-11
19       2       8           2 2019-01-21
20       2       9           3 2019-01-09

Données de séries de longueur variable triées par temps pour chaque utilisateur comme indiqué ci-dessous,

Itemid (par ordre chronologique) que chaque utilisateur a contacté
[[5, 3, 8, 4, 7, 4], 
 [6, 0, 7, 1, 8, 2], 
 [6, 7, 9, 5, 8, 5, 4, 3, 8]]
Identifiant de la catégorie (par ordre chronologique) que chaque utilisateur a contacté
[[1, 1, 2, 1, 2, 1], 
 [2, 0, 2, 0, 2, 0], 
 [2, 2, 3, 1, 2, 1, 1, 1, 2]]

Je souhaite créer une variable catégorielle qui considère la série chronologique suivante. Ici, on suppose que le dernier enregistrement est utilisé pour itemid et categoryid.

Le dernier itemid que chaque utilisateur a contacté
[4, 2, 8]
Le dernier identifiant de catégorie que chaque utilisateur a contacté
[1, 0, 2]

Pour les données de série, si vous pouvez créer une telle liste, par exemple, Keras (API fonctionnelle) peut être entrée sous forme de données séquentielles par remplissage comme suit.

import tensorflow as tf
inputs = []
inputs.append(tf.keras.preprocessing.sequence.pad_sequences(
    df['itemid'].values.tolist(), padding='post', truncating='post', maxlen=10))
inputs.append(tf.keras.preprocessing.sequence.pad_sequences(
    df['categoryid'].values.tolist(), padding='post', truncating='post', maxlen=10))

Comment écrire en Pandas

En ce qui concerne les données de série, chez les pandas, si vous écrivez comme suit,

#Identifiant d'utilisateur,Trier par ordre chronologique
df = df.sort_values(by=['userid','timestamp'])
#Regrouper sous forme de liste pour chaque utilisateur
df = df.groupby('userid').agg(list).reset_index(drop=False)

print('Itemid (par ordre chronologique) que chaque utilisateur a contacté')
pprint.pprint(df['itemid'].values.tolist())
print('Identifiant de la catégorie (par ordre chronologique) que chaque utilisateur a contacté')
pprint.pprint(df['categoryid'].values.tolist()

Vous obtiendrez le résultat ci-dessus.

Itemid (par ordre chronologique) que chaque utilisateur a contacté
[[5, 3, 8, 4, 7, 4], 
 [6, 0, 7, 1, 8, 2], 
 [6, 7, 9, 5, 8, 5, 4, 3, 8]]
Identifiant de la catégorie (par ordre chronologique) que chaque utilisateur a contacté
[[1, 1, 2, 1, 2, 1], 
 [2, 0, 2, 0, 2, 0], 
 [2, 2, 3, 1, 2, 1, 1, 1, 2]]

Aussi, concernant les données de catégorie,

#Grouper par pour obtenir le dernier pour chaque utilisateur
df_cate = df.loc[df.groupby('userid')['timestamp'].idxmax()]

print(df_cate)
print('Le dernier itemid que chaque utilisateur a contacté')
pprint.pprint(df_cate['itemid'].values.tolist())
print('Le dernier identifiant de catégorie que chaque utilisateur a contacté')
pprint.pprint(df_cate['categoryid'].values.tolist())

Vous pouvez obtenir le résultat ci-dessus en écrivant comme.

Le dernier itemid que chaque utilisateur a contacté
[4, 2, 8]
Le dernier identifiant de catégorie que chaque utilisateur a contacté
[1, 0, 2]

Problèmes possibles avec les pandas

Si l'ensemble de données ci-dessus devient volumineux, Pandas provoquera une erreur de mémoire et la conversion par lots ne sera pas possible. De plus, si le jeu de données lui-même ne tient pas dans la mémoire, il ne peut pas non plus le gérer. D'autre part, lors du traitement des ensembles de données séparément, il est nécessaire de conserver les informations de série chronologique des enregistrements qui ne sont pas inclus dans chaque ensemble de données.

Ce que je veux refaire

De l'arrière-plan ci-dessus

Nous avions besoin d'une méthode pour créer des données de série dans un ordre chronologique comme celui-ci.

Ci-dessous, j'écrirai sur la méthode spécifique.

Méthode

Ici, créez une liste contenant des informations sur les séries chronologiques, et en fonction de cela

J'écrirai sur la façon de créer.

Après cela, nous expliquerons quoi faire avec l'ensemble de données fractionné.

Créer une liste à trier

Tout d'abord, créez une liste à partir de laquelle vous pouvez effectuer des opérations dans l'ordre chronologique. Si la valeur que vous souhaitez avoir en tant que données de série dans l'ordre chronologique et pour chaque utilisateur est ʻitem, et les informations de série chronologique de cette valeur sont timestamp`,

[[[item,timestamp],[item,timestamp]...[item,timestamp]],
 [[item,timestamp],[item,timestamp]...[item,timestamp]],
 ...
 [[item,timestamp],[item,timestamp]...[item,timestamp]]]

Créez une liste tridimensionnelle appelée. Ici, la première dimension utilise l'ID utilisateur comme index.

Le processus est le suivant.

def create_list(df, user_index_col, sort_col, target_col, user_num):
    """
    :param user_index_col:Colonne ID utilisateur
    :param sort_col:Colonne contenant la valeur utilisée pour le tri
    :param target_col:Colonne que vous souhaitez trier
    :param user_num:Nombre d'utilisateurs (obtenu à partir de l'encodeur, etc.)
    """
    inputs = [[] for _ in range(user_num)]
    for _, user_index, sort_value, target_value in df[[user_index_col, sort_col, target_col]].itertuples():
        inputs[user_index].append([target_value, sort_value])

    return inputs

Si vous faites cela pour le premier ensemble de données qui sort,

itemid_inputs = create_list(df, user_index_col='userid', sort_col='timestamp', target_col='itemid', user_num=3)
categoryid_inputs = create_list(df, user_index_col='userid', sort_col='timestamp', target_col='categoryid', user_num=3)

print('itemid')
pprint.pprint(itemid_inputs)

print('categoryid')
pprint.pprint(categoryid_inputs)

Une liste comme celle ci-dessous sera créée.

itemid
[[[3, Timestamp('2019-01-04 00:00:00')],
  [4, Timestamp('2019-01-08 00:00:00')],
  [4, Timestamp('2019-01-19 00:00:00')],
  [5, Timestamp('2019-01-02 00:00:00')],
  [7, Timestamp('2019-01-17 00:00:00')],
  [8, Timestamp('2019-01-07 00:00:00')]],
 [[0, Timestamp('2019-01-06 00:00:00')],
  [1, Timestamp('2019-01-14 00:00:00')],
  [2, Timestamp('2019-01-20 00:00:00')],
  [6, Timestamp('2019-01-01 00:00:00')],
  [7, Timestamp('2019-01-12 00:00:00')],
  [8, Timestamp('2019-01-18 00:00:00')]],
 [[3, Timestamp('2019-01-16 00:00:00')],
  [4, Timestamp('2019-01-15 00:00:00')],
  [5, Timestamp('2019-01-10 00:00:00')],
  [5, Timestamp('2019-01-13 00:00:00')],
  [6, Timestamp('2019-01-03 00:00:00')],
  [7, Timestamp('2019-01-05 00:00:00')],
  [8, Timestamp('2019-01-11 00:00:00')],
  [8, Timestamp('2019-01-21 00:00:00')],
  [9, Timestamp('2019-01-09 00:00:00')]]]
categoryid
[[[1, Timestamp('2019-01-04 00:00:00')],
  [1, Timestamp('2019-01-08 00:00:00')],
  [1, Timestamp('2019-01-19 00:00:00')],
  [1, Timestamp('2019-01-02 00:00:00')],
  [2, Timestamp('2019-01-17 00:00:00')],
  [2, Timestamp('2019-01-07 00:00:00')]],
 [[0, Timestamp('2019-01-06 00:00:00')],
  [0, Timestamp('2019-01-14 00:00:00')],
  [0, Timestamp('2019-01-20 00:00:00')],
  [2, Timestamp('2019-01-01 00:00:00')],
  [2, Timestamp('2019-01-12 00:00:00')],
  [2, Timestamp('2019-01-18 00:00:00')]],
 [[1, Timestamp('2019-01-16 00:00:00')],
  [1, Timestamp('2019-01-15 00:00:00')],
  [1, Timestamp('2019-01-10 00:00:00')],
  [1, Timestamp('2019-01-13 00:00:00')],
  [2, Timestamp('2019-01-03 00:00:00')],
  [2, Timestamp('2019-01-05 00:00:00')],
  [2, Timestamp('2019-01-11 00:00:00')],
  [2, Timestamp('2019-01-21 00:00:00')],
  [3, Timestamp('2019-01-09 00:00:00')]]]

Trier par ordre chronologique

Ensuite, ajoutez le processus de tri de la liste créée dans l'ordre chronologique.

def sort_list(inputs, is_descending):
    """
    :param is_descending:Que ce soit par ordre décroissant
    """
    return [sorted(i_input, key=lambda i: i[1], reverse=is_descending) for i_input in inputs]

Lorsque ce processus est effectué,

itemid_inputs = sort_list(itemid_inputs, is_descending=False)
categoryid_inputs = sort_list(categoryid_inputs, is_descending=False)

print('itemid')
pprint.pprint(itemid_inputs)

print('categoryid')
pprint.pprint(categoryid_inputs)

Une liste triée par ordre chronologique est créée comme indiqué ci-dessous.

itemid
[[[5, Timestamp('2019-01-02 00:00:00')],
  [3, Timestamp('2019-01-04 00:00:00')],
  [8, Timestamp('2019-01-07 00:00:00')],
  [4, Timestamp('2019-01-08 00:00:00')],
  [7, Timestamp('2019-01-17 00:00:00')],
  [4, Timestamp('2019-01-19 00:00:00')]],
 [[6, Timestamp('2019-01-01 00:00:00')],
  [0, Timestamp('2019-01-06 00:00:00')],
  [7, Timestamp('2019-01-12 00:00:00')],
  [1, Timestamp('2019-01-14 00:00:00')],
  [8, Timestamp('2019-01-18 00:00:00')],
  [2, Timestamp('2019-01-20 00:00:00')]],
 [[6, Timestamp('2019-01-03 00:00:00')],
  [7, Timestamp('2019-01-05 00:00:00')],
  [9, Timestamp('2019-01-09 00:00:00')],
  [5, Timestamp('2019-01-10 00:00:00')],
  [8, Timestamp('2019-01-11 00:00:00')],
  [5, Timestamp('2019-01-13 00:00:00')],
  [4, Timestamp('2019-01-15 00:00:00')],
  [3, Timestamp('2019-01-16 00:00:00')],
  [8, Timestamp('2019-01-21 00:00:00')]]]
categoryid
[[[1, Timestamp('2019-01-02 00:00:00')],
  [1, Timestamp('2019-01-04 00:00:00')],
  [2, Timestamp('2019-01-07 00:00:00')],
  [1, Timestamp('2019-01-08 00:00:00')],
  [2, Timestamp('2019-01-17 00:00:00')],
  [1, Timestamp('2019-01-19 00:00:00')]],
 [[2, Timestamp('2019-01-01 00:00:00')],
  [0, Timestamp('2019-01-06 00:00:00')],
  [2, Timestamp('2019-01-12 00:00:00')],
  [0, Timestamp('2019-01-14 00:00:00')],
  [2, Timestamp('2019-01-18 00:00:00')],
  [0, Timestamp('2019-01-20 00:00:00')]],
 [[2, Timestamp('2019-01-03 00:00:00')],
  [2, Timestamp('2019-01-05 00:00:00')],
  [3, Timestamp('2019-01-09 00:00:00')],
  [1, Timestamp('2019-01-10 00:00:00')],
  [2, Timestamp('2019-01-11 00:00:00')],
  [1, Timestamp('2019-01-13 00:00:00')],
  [1, Timestamp('2019-01-15 00:00:00')],
  [1, Timestamp('2019-01-16 00:00:00')],
  [2, Timestamp('2019-01-21 00:00:00')]]]

Création de données de série en tenant compte des séries temporelles

Tout d'abord, le processus de création d'entités en série de longueur variable (entités séquentielles) à partir de la liste créée ci-dessus est le suivant.

def create_sequential(inputs):
    #Supprimer la liste des horodatages dans la liste
    return [[i[0] for i in i_input] for i_input in inputs]

Quand tu fais ça,

print('Itemid (par ordre chronologique) que chaque utilisateur a contacté')
pprint.pprint(create_sequential(itemid_inputs))

print('Identifiant de la catégorie (par ordre chronologique) que chaque utilisateur a contacté')
pprint.pprint(create_sequential(categoryid_inputs))

Vous pouvez obtenir le résultat recherché.

Itemid (par ordre chronologique) que chaque utilisateur a contacté
[[5, 3, 8, 4, 7, 4], 
 [6, 0, 7, 1, 8, 2], 
 [6, 7, 9, 5, 8, 5, 4, 3, 8]]

Identifiant de la catégorie (par ordre chronologique) que chaque utilisateur a contacté
[[1, 1, 2, 1, 2, 1], 
 [2, 0, 2, 0, 2, 0], 
 [2, 2, 3, 1, 2, 1, 1, 1, 2]]

Création de données de catégorie en tenant compte des séries chronologiques

Ensuite, le processus pour obtenir le dernier enregistrement de chaque utilisateur en tant que variable catégorielle à partir de la liste créée ci-dessus est le suivant.

def create_category(inputs, n=-1):
    """
    :param n:Quel numéro conserver dans la liste chronologique
    """
    #Supprimer la liste des horodatages dans la liste
    #Ne laissez que les données de la nième série dans l'ordre chronologique
    return [[i[0] for i in i_input][n] for i_input in inputs]

Quand tu fais ça,

print('Le dernier itemid que chaque utilisateur a contacté')
pprint.pprint(create_category(itemid_inputs, -1))

print('Le dernier identifiant de catégorie que chaque utilisateur a contacté')
pprint.pprint(create_category(categoryid_inputs, -1))

Vous pouvez obtenir le résultat recherché comme suit:

Le dernier itemid que chaque utilisateur a contacté
[4, 2, 8]

Le dernier identifiant de catégorie que chaque utilisateur a contacté
[1, 0, 2]

Résumé du traitement

Ici, les fonctions séparées pour l'explication ci-dessus sont intégrées comme suit.


def create_features(
        df, user_index_col, sort_col, target_col, user_num, is_descending, is_sequence, n=-1):
    """
    :param user_index_col:Colonne ID utilisateur
    :param sort_col:Colonne contenant la valeur utilisée pour le tri
    :param target_col:Colonne que vous souhaitez trier
    :param user_num:Nombre d'utilisateurs (obtenu à partir de l'encodeur, etc.)
    :param is_descending:Que ce soit par ordre décroissant
    :param is_sequence:Que ce soit séquentiel
    :param n:Quel numéro conserver dans la liste chronologique (catégorie uniquement)
    """
    #Créer une liste
    inputs = [[] for _ in range(user_num)]
    for _, user_index, sort_value, target_value in df[[user_index_col, sort_col, target_col]].itertuples():
        inputs[user_index].append([target_value, sort_value])

    #Trier la liste
    inputs = [sorted(i_input, key=lambda i: i[1], reverse=is_descending) for i_input in inputs]

    if is_sequence:
        return [[i[0] for i in i_input] for i_input in inputs]
    else:
        return [[i[0] for i in i_input][n] for i_input in inputs]

Comment diviser et lire des données

C'est là que je voulais le plus écrire, si vous créez une liste pour contenir les informations de séries chronologiques comme décrit ci-dessus, si vous ne pouvez pas mettre toutes les données en mémoire, divisez l'ensemble de données et lisez-le avant chaque unité de division Il peut également être utilisé pour le traitement.

À titre d'exemple, supposons que le premier DataFrame soit divisé en trois parties et stocké dans le dictionnaire comme indiqué ci-dessous. (Je ne pense pas que ce soit réellement le cas, mais à titre d'exemple ...)

{'df1':    userid  itemid  categoryid  timestamp
0       0       3           1 2019-01-04
1       0       4           1 2019-01-08
2       0       4           1 2019-01-19
3       0       5           1 2019-01-02
4       0       7           2 2019-01-17
5       0       8           2 2019-01-07
6       1       0           0 2019-01-06,
 'df2':     userid  itemid  categoryid  timestamp
7        1       1           0 2019-01-14
8        1       2           0 2019-01-20
9        1       6           2 2019-01-01
10       1       7           2 2019-01-12
11       1       8           2 2019-01-18
12       2       3           1 2019-01-16
13       2       4           1 2019-01-15,
 'df3':     userid  itemid  categoryid  timestamp
14       2       5           1 2019-01-10
15       2       5           1 2019-01-13
16       2       6           2 2019-01-03
17       2       7           2 2019-01-05
18       2       8           2 2019-01-11
19       2       8           2 2019-01-21
20       2       9           3 2019-01-09}

Puisque les informations de série temporelle sont stockées dans la liste, elles peuvent être traitées en modifiant la fonction comme suit, par exemple.


def create_features_by_datasets(
        df_dict, user_index_col, sort_col, target_col, user_num, is_descending, is_sequence, n=-1):
    inputs = [[] for _ in range(user_num)]

    #Traitement pour chaque unité de division de l'ensemble de données
    for df in df_dict.values():
        for _, user_index, sort_value, target_value in df[[user_index_col, sort_col, target_col]].itertuples():
            inputs[user_index].append([target_value, sort_value])

    inputs = [sorted(i_input, key=lambda i: i[1], reverse=is_descending) for i_input in inputs]

    if is_sequence:
        return [[i[0] for i in i_input] for i_input in inputs]
    else:
        return [[i[0] for i in i_input][n] for i_input in inputs]

Si vous procédez comme suit,

print('Itemid (par ordre chronologique) que chaque utilisateur a contacté')
pprint.pprint(create_features_by_datasets(df_dict, user_index_col='userid', sort_col='timestamp', target_col='itemid', user_num=3, is_descending=False, is_sequence=True))
print('Le dernier itemid que chaque utilisateur a contacté')
pprint.pprint(create_features_by_datasets(df_dict, user_index_col='userid', sort_col='timestamp', target_col='itemid', user_num=3, is_descending=False, is_sequence=False))

Le résultat est le même que ci-dessus.

Itemid (par ordre chronologique) que chaque utilisateur a contacté
[[5, 3, 8, 4, 7, 4], 
 [6, 0, 7, 1, 8, 2],
 [6, 7, 9, 5, 8, 5, 4, 3, 8]]

Le dernier itemid que chaque utilisateur a contacté
 [4, 2, 8]

Tri selon des informations autres que les séries chronologiques

De plus, cette fois, nous avons réduit les critères de tri aux informations de séries chronologiques, mais il est également possible de trier par d'autres colonnes ou par ordre décroissant. En changeant les variables passées pour le traitement ci-dessus, l'ordre croissant / décroissant et le tri en spécifiant des colonnes sont possibles.

Par exemple, dans l'ensemble de données suivant

    userid  itemid  categoryid     score
0        0       3           1  0.730968
1        0       3           1  0.889117
2        0       3           1  0.714828
3        0       4           1  0.430737
4        0       5           1  0.734746
5        0       7           2  0.412346
6        1       0           0  0.660430
7        1       3           1  0.095672
8        1       4           1  0.985072
9        1       5           1  0.629274
10       1       6           2  0.617733
11       1       7           2  0.636219
12       1       8           2  0.246769
13       1       8           2  0.020140
14       2       0           0  0.812525
15       2       1           0  0.671100
16       2       2           0  0.174011
17       2       2           0  0.164321
18       2       3           1  0.783329
19       2       4           1  0.068837
20       2       5           1  0.265281

Même s'il existe une colonne appelée score, si vous souhaitez créer des données de série et des données de catégorie dans l'ordre décroissant, le processus est le suivant.

print('Ordre des scores (itemid)')
pprint.pprint(create_features(df, user_index_col='userid', sort_col='score', target_col='itemid', user_num=3, is_descending=True, is_sequence=True))
print('Score maximum (itemid)')
pprint.pprint(create_features(df, user_index_col='userid', sort_col='score', target_col='itemid', user_num=3, is_descending=True, is_sequence=False, n=0))

Le résultat est le suivant.

Ordre des scores (itemid)
[[3, 5, 3, 3, 4, 7], 
 [4, 0, 7, 5, 6, 8, 3, 8], 
 [0, 3, 1, 5, 2, 2, 4]]

Score maximum (itemid)
[3, 4, 0]

À la fin

Cette fois, j'ai écrit sur la façon de convertir les données de journal en fonctionnalités de série et en fonctionnalités de catégorie de manière à économiser la mémoire tout en tenant compte des informations de série chronologique. S'il existe un meilleur moyen, veuillez me le faire savoir dans les commentaires. Merci beaucoup.

Recommended Posts

Conversion d'économie de mémoire des données de journal en quantité de caractéristiques de catégorie séquentielle en tenant compte des séries temporelles
Conversion matricielle d'économie de mémoire des données de journal
Conversion des données de temps en notation 25 heures
Résumé des outils nécessaires pour analyser les données en Python
Ingéniosité pour gérer les données avec Pandas de manière à économiser la mémoire
Comment obtenir un aperçu de vos données dans Pandas
Django a changé pour enregistrer beaucoup de données à la fois
Une commande pour lister tous les fichiers par ordre de nom de fichier
Essayez d'extraire les caractéristiques des données de capteur avec CNN
Comment extraire des fonctionnalités de données de séries chronologiques avec les bases de PySpark
Comment créer une grande quantité de données de test dans MySQL? ??