Si je comprends bien, je mange beaucoup de bonbons, ou le classement des marchandises par chèque dans l'application

Défi


Dans cet article, nous voulons parler de la façon dont nous avons créé une solution pour classer les noms de produits à partir des reçus dans l'application d'enregistrement des dépenses pour les chèques et l'assistant d'achat. Nous voulions donner aux utilisateurs la possibilité de consulter des statistiques sur les achats, collectées automatiquement sur la base de reçus scannés, à savoir, répartir tous les biens achetés par l'utilisateur par catégorie. Parce que forcer l'utilisateur à regrouper ses produits de façon indépendante est déjà le siècle dernier. Il existe plusieurs approches pour résoudre ce problème: vous pouvez essayer d'appliquer des algorithmes de clustering avec différentes façons de représentation vectorielle des mots ou des algorithmes de classification classiques. Nous n'avons rien inventé de nouveau, et dans cet article, nous voulons seulement partager un petit guide sur une solution possible au problème, des exemples de la façon de ne pas le faire, une analyse des raisons pour lesquelles d'autres méthodes n'ont pas fonctionné et des problèmes que vous pourriez rencontrer dans le processus.

Regroupement


L'un des problèmes était que les noms des marchandises que nous obtenons des chèques ne sont pas toujours faciles à déchiffrer, même pour une personne. Il est peu probable que vous sachiez quel type de produit portant le nom «UTRUSTA krnsht» a été acheté dans l'un des magasins russes? Les vrais connaisseurs du design suédois nous répondront certainement tout de suite: support pour le four d'Utrust, mais garder de tels spécialistes au siège coûte assez cher. De plus, nous n'avions pas d'échantillon prêt à l'emploi, étiqueté et adapté à nos données, sur lequel nous pouvions former le modèle. Par conséquent, nous allons d'abord parler de la façon dont, en l'absence de données pour la formation, nous avons appliqué des algorithmes de clustering et pourquoi nous ne l'avons pas aimé.

Ces algorithmes sont basés sur la mesure des distances entre les objets, ce qui nécessite leur représentation vectorielle ou l'utilisation d'une métrique pour mesurer la similitude des mots (par exemple, la distance de Levenshtein). À ce stade, la difficulté réside dans la représentation vectorielle significative des noms. Il est problématique d'extraire des propriétés des noms qui décriront de manière complète et complète le produit et sa relation avec d'autres produits.

L'option la plus simple consiste à utiliser Tf-Idf, mais dans ce cas, la dimension de l'espace vectoriel est assez grande et l'espace lui-même est rare. De plus, cette approche n'extrait aucune information supplémentaire des noms. Ainsi, dans un cluster, il peut y avoir de nombreux produits de différentes catégories, unis par un mot commun, comme par exemple «pomme de terre» ou «salade»:


Nous ne pouvons pas non plus contrôler quels clusters seront assemblés. La seule chose qui peut être indiquée est le nombre de clusters (si des algorithmes basés sur des pics de non-densité dans l'espace sont utilisés). Mais si vous spécifiez une quantité trop petite, alors un énorme cluster est formé, qui contiendra tous les noms qui ne pourraient pas entrer dans d'autres clusters. Si vous en spécifiez un suffisamment grand, après le fonctionnement de l'algorithme, nous devrons parcourir des centaines de clusters et les combiner manuellement en catégories sémantiques.

Les tableaux ci-dessous fournissent des informations sur les clusters utilisant les algorithmes KMeans et Tf-Idf pour la représentation vectorielle. De ces tableaux, nous voyons que les distances entre les centres des grappes sont inférieures à la distance moyenne entre les objets et les centres des grappes auxquels ils appartiennent. Ces données peuvent s'expliquer par le fait que dans l'espace des vecteurs, il n'y a pas de pics de densité évidents et que les centres des grappes sont situés autour du cercle, où la plupart des objets sont situés à l'extérieur de ce cercle. De plus, un cluster est formé, qui contient la plupart des vecteurs. Dans ce groupe, il est fort probable que les noms contiennent des mots que l'on trouve plus souvent que d'autres parmi tous les produits de différentes catégories.

Tableau 1. Distances entre les clusters.
ClusterC1C2C3C4C5C6C7C8C9
C10,00,5020,3540,4750,4810,5270,4980,5010,524
C20,5020,00,6140,6850,6960,7280,7060,7090,725
C30,3540,6140,00,5900,5970,6350,6100,6130,632
C40,4750,6850,5900,00,6730,7090,6830,6870,699
C50,4810,6960,5970,6730,00,7150,6920,6940,711
C60,5270,7270,6350,7090,7150,00,7260,7280,741
C70,4980,7060,6100,6830,6920,7250,00,7070,714
C80,5010,7090,6120,6870,6940,7280,7070,00,725
C90,5240,7250,6320,6990,7110,7410,7140,7250,0

Tableau 2. Informations succinctes sur les clusters
ClusterNombre d'objetsDistance moyenneDistance minimaleDistance maximale
C1625300,9990,0411,001
C221590,8640,5270,964
C310990,9340,7560,993
C412920,8790,7330,980
C57460,8750,7310,965
C624510,8470,7190,994
C711330,8660,7240,986
C88760,8630,7040,999
C918790,8490,5260,981


Mais dans certains endroits, les grappes s'avèrent être assez décentes, comme, par exemple, dans l'image ci-dessous - là, presque tous les produits sont des aliments pour chats.



Doc2Vec est un autre des algorithmes qui vous permettent de représenter des textes sous forme vectorielle. En utilisant cette approche, chaque nom sera décrit par un vecteur de dimension plus petite que l'utilisation de Tf-Idf. Dans l'espace vectoriel qui en résulte, des textes similaires seront proches les uns des autres et différents loin.

Cette approche peut résoudre le problème de la grande dimension et de l'espace déchargé obtenu par la méthode Tf-Idf. Pour cet algorithme, nous avons utilisé l'option de tokenisation la plus simple: nous avons divisé le nom en mots séparés et pris leurs formes initiales. Il a été formé aux données de cette manière:

max_epochs = 100 vec_size = 20 alpha = 0.025 model = doc2vec.Doc2Vec(vector_size=vec_size, alpha=alpha, min_alpha=0.00025, min_count=1, dm =1) model.build_vocab(train_corpus) for epoch in range(max_epochs): print('iteration {0}'.format(epoch)) model.train(train_corpus, total_examples=model.corpus_count, epochs=model.iter) # decrease the learning rate model.alpha -= 0.0002 # fix the learning rate, no decay model.min_alpha = model.epochs 

Mais avec cette approche, nous avons obtenu des vecteurs qui ne portent pas d'informations sur le nom - avec le même succès, vous pouvez utiliser des valeurs aléatoires. Voici un exemple du fonctionnement de l'algorithme: l'image montre des produits similaires de l'avis de l'algorithme au «pain Borodino de la forme n pn 0.45k».


Peut-être que le problème est dans la longueur et le contexte des noms: le laissez-passer dans le nom "__ club. Banane 200ml" peut être soit du yaourt, du jus ou une grande boîte de crème. Vous pouvez obtenir un meilleur résultat en utilisant une approche différente de la tokenisation des noms. Nous n'avions aucune expérience de l'utilisation de cette méthode, et au moment où les premières tentatives avaient échoué, nous avions déjà trouvé quelques ensembles marqués avec des noms de produits, nous avons donc décidé de laisser cette méthode pendant un certain temps et de passer aux algorithmes de classification.

Classification


Prétraitement des données


Les noms des marchandises des chèques nous parviennent de manière pas toujours claire: le latin et le cyrillique sont mélangés dans les mots. Par exemple, la lettre «a» peut être remplacée par «a» latin, ce qui augmente le nombre de noms uniques - par exemple, les mots «lait» et «lait» seront considérés comme différents. Les noms contiennent également de nombreuses autres fautes de frappe et abréviations.

Nous avons examiné notre base de données et trouvé des erreurs typiques dans les noms. À ce stade, nous avons supprimé les expressions régulières, à l'aide desquelles nous avons nettoyé les noms et les avons amenés à une certaine vue d'ensemble. En utilisant cette approche, le résultat est augmenté d'environ 7%. Avec une simple option SGD Classifier basée sur la fonction de perte Huber avec des paramètres tordus, nous avons obtenu une précision de 81% pour F1 (précision moyenne pour toutes les catégories de produits).

 sgd_model = SGDClassifier() parameters_sgd = { 'max_iter':[100], 'loss':['modified_huber'], 'class_weight':['balanced'], 'penalty':['l2'], 'alpha':[0.0001] } sgd_cv = GridSearchCV(sgd_model, parameters_sgd,n_jobs=-1) sgd_cv.fit(tf_idf_data, prod_cat) sgd_cv.best_score_, sgd_cv.best_params_ 

N'oubliez pas non plus que certaines catégories de personnes achètent plus souvent que d'autres: par exemple, «Thé et sucreries» et «Légumes et fruits» sont beaucoup plus populaires que «Services» et «Cosmétiques». Avec une telle distribution de données, il est préférable d'utiliser des algorithmes qui vous permettent de définir des poids (degré d'importance) pour chaque classe. Le poids de la classe peut être déterminé inversement avec la valeur égale au rapport du nombre de produits de la classe au nombre total de produits. Mais vous n'avez pas à y penser, car dans la mise en œuvre de ces algorithmes, il est possible de déterminer automatiquement le poids des catégories.


Obtention de nouvelles données pour la formation


Notre candidature nécessitait des catégories légèrement différentes de celles utilisées lors du concours, et les noms des produits de notre base de données étaient sensiblement différents de ceux présentés lors du concours. Par conséquent, nous devions marquer les marchandises de nos reçus. Nous avons essayé de le faire par nous-mêmes, mais nous avons réalisé que même si nous connectons toute notre équipe, cela prendra beaucoup de temps. Par conséquent, nous avons décidé d'utiliser la «Toloka» de Yandex .

Là, nous avons utilisé cette forme de mission:

  • dans chaque cellule nous avons présenté un produit dont la catégorie doit être définie
  • sa catégorie hypothétique définie par l'un de nos modèles précédents
  • champ de réponse (si la catégorie proposée était incorrecte)

Nous avons créé des instructions détaillées avec des exemples qui expliquaient les caractéristiques de chaque catégorie, et avons également utilisé des méthodes de contrôle de la qualité: un ensemble avec des réponses standard qui ont été montrées avec les tâches habituelles (nous avons implémenté les réponses standard nous-mêmes, marquant plusieurs centaines de produits). Selon les résultats des réponses à ces tâches, les utilisateurs qui ont incorrectement annoté les données ont été éliminés. Cependant, pour l'ensemble du projet, nous n'avons interdit que trois des 600+ utilisateurs.


Avec les nouvelles données, nous avons obtenu un modèle qui convenait mieux à nos données, et la précision a augmenté un peu plus (de ~ 11%) et a déjà atteint 92%.

Modèle final


Nous avons commencé le processus de classification avec une combinaison de données de plusieurs ensembles de données avec Kaggle - 74%, après quoi nous avons amélioré le prétraitement - 81%, collecté un nouvel ensemble de données - 92% et finalement amélioré le processus de classification: initialement, en utilisant la régression logistique, nous obtenons des probabilités préliminaires d'appartenance des marchandises SGD a donné une plus grande précision aux catégories basées sur les noms de produits, mais avait toujours de grandes valeurs sur les fonctions de perte, ce qui a gravement affecté les résultats du classificateur final. De plus, nous combinons les données obtenues avec d'autres données sur le produit (prix du produit, le magasin dans lequel il a été acheté, statistiques sur le magasin, chèque et autres méta-informations), et XGBoost est formé sur tout ce volume de données, ce qui a donné une précision de 98% (augmentation 6%). Il s'est avéré que la plus grande contribution a été apportée par la qualité de l'échantillon de formation.

En cours d'exécution sur le serveur


Pour accélérer le déploiement, nous avons monté un serveur simple sur Flask vers Docker. Il y avait une méthode qui recevait des marchandises du serveur qui devait être catégorisée et retournait déjà des marchandises avec des catégories. Ainsi, nous nous sommes facilement intégrés au système existant, dont Tomcat était le centre, et nous n'avons pas eu à modifier l'architecture - nous y avons simplement ajouté un bloc de plus.

Date de sortie


Il y a quelques semaines, nous avons publié une version de catégorisation sur Google Play (elle apparaîtra sur l'App Store après un certain temps). Il s'est avéré comme ceci:


Dans les versions futures, nous prévoyons d'ajouter la possibilité de corriger les catégories, ce qui nous permettra de collecter rapidement les erreurs de catégorisation et de recycler le modèle de catégorisation (pendant que nous le faisons nous-mêmes).

Concours mentionnés à Kaggle:

www.kaggle.com/c/receipt-categorisation
www.kaggle.com/c/market-basket-analysis
www.kaggle.com/c/prod-price-prediction

Source: https://habr.com/ru/post/fr430216/


All Articles