Herausforderung
In diesem Artikel möchten wir darüber sprechen, wie wir eine Lösung zum Klassifizieren von Produktnamen aus Belegen in der Anwendung zum Erfassen von Ausgaben für Schecks und den Einkaufsassistenten erstellt haben. Wir wollten den Benutzern die Möglichkeit geben, Statistiken über Einkäufe anzuzeigen, die automatisch auf der Grundlage gescannter Belege gesammelt werden, und zwar alle vom Benutzer gekauften Waren nach Kategorien zu verteilen. Weil es bereits das letzte Jahrhundert ist, den Benutzer zu zwingen, Produkte unabhängig zu gruppieren. Es gibt verschiedene Ansätze zur Lösung dieses Problems: Sie können versuchen, Clustering-Algorithmen mit verschiedenen Arten der Vektordarstellung von Wörtern oder klassischen Klassifizierungsalgorithmen anzuwenden. Wir haben nichts Neues erfunden und möchten in diesem Artikel nur einen kleinen Leitfaden über eine mögliche Lösung des Problems, Beispiele dafür, wie dies nicht funktioniert, eine Analyse, warum andere Methoden nicht funktionierten und auf welche Probleme Sie dabei stoßen könnten, veröffentlichen.
Clustering
Eines der Probleme war, dass die Namen der Waren, die wir aus Schecks erhalten, selbst für eine Person nicht immer leicht zu entziffern sind. Es ist unwahrscheinlich, dass Sie wissen, welche Art von Produkt mit dem Namen
„UTRUSTA krnsht“ in einem der russischen Geschäfte gekauft wurde? Echte Kenner des schwedischen Designs werden uns sicherlich sofort antworten: Halterung für den Ofen von Utrust, aber solche Spezialisten im Hauptquartier zu halten, ist ziemlich teuer. Außerdem hatten wir keine fertige, beschriftete Probe, die für unsere Daten geeignet war und an der wir das Modell trainieren konnten. Daher werden wir zunächst darüber sprechen, wie wir mangels Daten für das Training Clustering-Algorithmen angewendet haben und warum es uns nicht gefallen hat.
Solche Algorithmen basieren auf der Messung von Abständen zwischen Objekten, was deren Vektordarstellung oder die Verwendung einer Metrik zur Messung der Ähnlichkeit von Wörtern erfordert (z. B. Levenshtein-Abstand). In diesem Schritt liegt die Schwierigkeit in der aussagekräftigen Vektordarstellung der Namen. Es ist problematisch, Eigenschaften aus den Namen zu extrahieren, die das Produkt und seine Beziehung zu anderen Produkten vollständig und umfassend beschreiben.
Die einfachste Option ist die Verwendung von Tf-Idf, aber in diesem Fall ist die Dimension des Vektorraums ziemlich groß und der Raum selbst ist spärlich. Darüber hinaus extrahiert dieser Ansatz keine zusätzlichen Informationen aus den Namen. So kann es in einem Cluster viele Produkte aus verschiedenen Kategorien geben, die durch ein gemeinsames Wort vereint sind, wie zum Beispiel „Kartoffel“ oder „Salat“:
Wir können auch nicht steuern, welche Cluster zusammengestellt werden. Das einzige, was angezeigt werden kann, ist die Anzahl der Cluster (wenn Algorithmen verwendet werden, die auf Nichtdichtespitzen im Raum basieren). Wenn Sie jedoch eine zu kleine Menge angeben, wird ein großer Cluster gebildet, der alle Namen enthält, die nicht in andere Cluster passen könnten. Wenn Sie einen ausreichend großen angeben, müssen wir nach dem Funktionieren des Algorithmus Hunderte von Clustern durchsuchen und diese manuell in semantische Kategorien kombinieren.
Die folgenden Tabellen enthalten Informationen zu Clustern, die die Algorithmen KMeans und Tf-Idf zur Vektordarstellung verwenden. Aus diesen Tabellen geht hervor, dass die Abstände zwischen den Zentren der Cluster geringer sind als der durchschnittliche Abstand zwischen den Objekten und den Zentren der Cluster, zu denen sie gehören. Solche Daten können durch die Tatsache erklärt werden, dass es im Raum der Vektoren keine offensichtlichen Dichtespitzen gibt und sich die Zentren der Cluster um den Kreis befinden, wo sich die meisten Objekte außerhalb dieses Kreises befinden. Zusätzlich wird ein Cluster gebildet, der die meisten Vektoren enthält. Am wahrscheinlichsten werden in diesem Cluster Namen verwendet, die Wörter enthalten, die unter allen Produkten aus verschiedenen Kategorien häufiger als andere vorkommen.
Tabelle 1. Abstände zwischen Clustern.Cluster | C1 | C2 | C3 | C4 | C5 | C6 | C7 | C8 | C9 |
---|
C1 | 0.0 | 0,502 | 0,354 | 0,475 | 0,481 | 0,527 | 0,498 | 0,501 | 0,524 |
---|
C2 | 0,502 | 0.0 | 0,614 | 0,685 | 0,696 | 0,728 | 0,706 | 0,709 | 0,725 |
---|
C3 | 0,354 | 0,614 | 0.0 | 0,590 | 0,597 | 0,635 | 0,610 | 0,613 | 0,632 |
---|
C4 | 0,475 | 0,685 | 0,590 | 0.0 | 0,673 | 0,709 | 0,683 | 0,687 | 0,699 |
---|
C5 | 0,481 | 0,696 | 0,597 | 0,673 | 0.0 | 0,715 | 0,692 | 0,694 | 0,711 |
---|
C6 | 0,527 | 0,727 | 0,635 | 0,709 | 0,715 | 0.0 | 0,726 | 0,728 | 0,741 |
---|
C7 | 0,498 | 0,706 | 0,610 | 0,683 | 0,692 | 0,725 | 0.0 | 0,707 | 0,714 |
---|
C8 | 0,501 | 0,709 | 0,612 | 0,687 | 0,694 | 0,728 | 0,707 | 0.0 | 0,725 |
---|
C9 | 0,524 | 0,725 | 0,632 | 0,699 | 0,711 | 0,741 | 0,714 | 0,725 | 0.0 |
---|
Tabelle 2. Kurzinformationen zu ClusternCluster | Anzahl der Objekte | Durchschnittliche Entfernung | Mindestabstand | Maximale Entfernung |
---|
C1 | 62530 | 0,999 | 0,041 | 1.001 |
---|
C2 | 2159 | 0,864 | 0,527 | 0,964 |
---|
C3 | 1099 | 0,934 | 0,756 | 0,993 |
---|
C4 | 1292 | 0,879 | 0,733 | 0,980 |
---|
C5 | 746 | 0,875 | 0,731 | 0,965 |
---|
C6 | 2451 | 0,847 | 0,719 | 0,994 |
---|
C7 | 1133 | 0,866 | 0,724 | 0,986 |
---|
C8 | 876 | 0,863 | 0,704 | 0,999 |
---|
C9 | 1879 | 0,849 | 0,526 | 0,981 |
---|
Aber an einigen Stellen erweisen sich die Cluster als recht anständig, wie zum Beispiel im Bild unten - dort sind fast alle Produkte Katzenfutter.
Doc2Vec ist ein weiterer Algorithmus, mit dem Sie Texte in Vektorform darstellen können. Bei Verwendung dieses Ansatzes wird jeder Name durch einen Vektor mit einer kleineren Dimension als bei Verwendung von Tf-Idf beschrieben. In dem resultierenden Vektorraum sind ähnliche Texte nahe beieinander und verschiedene weit entfernt.
Dieser Ansatz kann das Problem der großen Dimension und des entladenen Raums lösen, das durch das Tf-Idf-Verfahren erhalten wird. Für diesen Algorithmus haben wir die einfachste Option der Tokenisierung verwendet: Wir haben den Namen in separate Wörter aufgeteilt und ihre ursprünglichen Formen angenommen. Er wurde auf folgende Weise auf Daten geschult:
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)
Bei diesem Ansatz haben wir jedoch Vektoren erhalten, die keine Informationen über den Namen enthalten. Mit demselben Erfolg können Sie zufällige Werte verwenden. Hier ist ein Beispiel für die Funktionsweise des Algorithmus: Das Bild zeigt Produkte, die nach Ansicht des Algorithmus dem „Borodino-Brot der Form n pn 0,45 k“ ähnlich sind.
Vielleicht liegt das Problem in der Länge und dem Kontext der Namen: Der Pass im Namen "__ club. Banana 200ml" kann entweder Joghurt, Saft oder eine große Dose Sahne sein. Mit einem anderen Ansatz zur Namens-Tokenisierung können Sie ein besseres Ergebnis erzielen. Wir hatten keine Erfahrung mit dieser Methode, und als die ersten Versuche fehlgeschlagen waren, fanden wir bereits einige markierte Sätze mit Produktnamen. Daher beschlossen wir, diese Methode für eine Weile zu verlassen und auf Klassifizierungsalgorithmen umzusteigen.
Klassifizierung
Datenvorverarbeitung
Die Namen von Waren aus Schecks kommen uns nicht immer klar vor: Latein und Kyrillisch werden in Worten gemischt. Zum Beispiel kann der Buchstabe "a" durch "a" lateinisch ersetzt werden, und dies erhöht die Anzahl der eindeutigen Namen - zum Beispiel werden die Wörter "Milch" und "Milch" als unterschiedlich angesehen. Die Namen enthalten auch viele andere Tippfehler und Abkürzungen.
Wir haben unsere Datenbank untersucht und typische Fehler in den Namen gefunden. Zu diesem Zeitpunkt haben wir auf reguläre Ausdrücke verzichtet, mit deren Hilfe wir die Namen bereinigt und zu einer bestimmten allgemeinen Sichtweise gebracht haben. Mit diesem Ansatz wird das Ergebnis um ca. 7% gesteigert. Zusammen mit einer einfachen SGD-Klassifikatoroption basierend auf der Huber-Verlustfunktion mit verdrillten Parametern haben wir eine Genauigkeit von 81% für F1 erhalten (durchschnittliche Genauigkeit für alle Produktkategorien).
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_
Vergessen Sie auch nicht, dass einige Kategorien von Menschen häufiger einkaufen als andere: Zum Beispiel sind „Tee und Süßigkeiten“ und „Gemüse und Obst“ viel beliebter als „Dienstleistungen“ und „Kosmetik“. Bei einer solchen Datenverteilung ist es besser, Algorithmen zu verwenden, mit denen Sie Gewichte (Wichtigkeitsgrad) für jede Klasse festlegen können. Das Gewicht der Klasse kann umgekehrt mit dem Wert bestimmt werden, der dem Verhältnis der Anzahl der Produkte in der Klasse zur Gesamtzahl der Produkte entspricht. Sie müssen jedoch nicht darüber nachdenken, da bei der Implementierung dieser Algorithmen das Gewicht von Kategorien automatisch ermittelt werden kann.
Neue Daten für das Training erhalten
Unsere Anwendung erforderte geringfügig andere Kategorien als die im Wettbewerb verwendeten, und die Namen der Produkte aus unserer Datenbank unterschieden sich erheblich von den im Wettbewerb präsentierten. Daher mussten wir die Waren von unseren Belegen markieren. Wir haben versucht, dies alleine zu tun, aber wir haben festgestellt, dass es sehr lange dauern wird, selbst wenn wir unser gesamtes Team verbinden. Deshalb haben wir uns für
Yandexs „Toloka“ entschieden .
Dort haben wir diese Zuordnungsform verwendet:
- In jeder Zelle haben wir ein Produkt vorgestellt, dessen Kategorie definiert werden muss
- seine hypothetische Kategorie, die von einem unserer Vorgängermodelle definiert wurde
- Antwortfeld (wenn die vorgeschlagene Kategorie falsch war)
Wir haben detaillierte Anweisungen mit Beispielen erstellt, in denen die Merkmale der einzelnen Kategorien erläutert wurden, und auch Methoden zur Qualitätskontrolle verwendet: ein Set mit Standardantworten, die zusammen mit den üblichen Aufgaben angezeigt wurden (wir haben die Standardantworten selbst implementiert und mehrere hundert Produkte markiert). Entsprechend den Ergebnissen der Antworten auf diese Aufgaben wurden Benutzer, die die Daten falsch markiert hatten, herausgesucht. Für das gesamte Projekt haben wir jedoch nur drei der über 600 Benutzer gesperrt.
Mit den neuen Daten haben wir ein Modell erhalten, das besser zu unseren Daten passt, und die Genauigkeit hat sich etwas mehr erhöht (um ~ 11%) und hat bereits 92% erreicht.
Endmodell
Wir haben den Klassifizierungsprozess mit einer Kombination von Daten aus mehreren Datensätzen mit Kaggle begonnen - 74%. Danach haben wir die Vorverarbeitung verbessert - 81%, einen neuen Datensatz gesammelt - 92% und schließlich den Klassifizierungsprozess verbessert: Zunächst erhalten wir mithilfe der logistischen Regression vorläufige Wahrscheinlichkeiten für die Zugehörigkeit von Waren SGD gab Kategorien, die auf Produktnamen basierten, eine größere Genauigkeit, hatte jedoch immer noch große Werte für die Verlustfunktionen, was die Ergebnisse des endgültigen Klassifikators stark beeinflusste. Darüber hinaus kombinieren wir die erhaltenen Daten mit anderen Daten zum Produkt (Preis des Produkts, des Geschäfts, in dem es gekauft wurde, Statistiken über das Geschäft, Scheck und andere Metainformationen), und XGBoost wird auf all dieses Datenvolumen geschult, was eine Genauigkeit von 98% ergab (Erhöhung) weitere 6%). Wie sich herausstellte, leistete die Qualität der Trainingsstichprobe den größten Beitrag.
Wird auf dem Server ausgeführt
Um die Bereitstellung zu beschleunigen, haben wir einen einfachen Server auf Flask to Docker eingerichtet. Es gab eine Methode, mit der Waren vom Server empfangen wurden, die kategorisiert und Waren mit Kategorien bereits zurückgegeben werden mussten. So konnten wir uns problemlos in das bestehende System integrieren, dessen Zentrum Tomcat war, und wir mussten keine Änderungen an der Architektur vornehmen - wir haben nur einen weiteren Block hinzugefügt.
Erscheinungsdatum
Vor einigen Wochen haben wir eine Kategorisierungsversion bei Google Play veröffentlicht (diese wird nach einer Weile im App Store angezeigt). Es stellte sich so heraus:
In zukünftigen Versionen planen wir, die Möglichkeit zur Korrektur von Kategorien hinzuzufügen, damit wir schnell Kategorisierungsfehler erfassen und das Kategorisierungsmodell neu trainieren können (während wir es selbst tun).
Erwähnte Wettbewerbe bei Kaggle:
www.kaggle.com/c/receipt-categorisationwww.kaggle.com/c/market-basket-analysiswww.kaggle.com/c/prod-price-prediction