paint-brush
Eine Fallstudie zur Textklassifizierung durch maschinelles Lernen mit einer produktgesteuerten Wendungvon@bemorelavender
29,341 Lesungen
29,341 Lesungen

Eine Fallstudie zur Textklassifizierung durch maschinelles Lernen mit einer produktgesteuerten Wendung

von Maria K17m2024/03/12
Read on Terminal Reader

Zu lang; Lesen

Dies ist eine Fallstudie zum maschinellen Lernen mit einer produktorientierten Wendung: Wir werden so tun, als hätten wir ein tatsächliches Produkt, das wir verbessern müssen. Wir werden einen Datensatz untersuchen und verschiedene Modelle wie logistische Regression, wiederkehrende neuronale Netze und Transformatoren ausprobieren. Wir werden prüfen, wie genau sie sind, wie sie das Produkt verbessern, wie schnell sie arbeiten und ob sie leicht zu debuggen sind und vergrößern.
featured image - Eine Fallstudie zur Textklassifizierung durch maschinelles Lernen mit einer produktgesteuerten Wendung
Maria K HackerNoon profile picture


Wir werden so tun, als hätten wir ein tatsächliches Produkt, das wir verbessern müssen. Wir werden einen Datensatz untersuchen und verschiedene Modelle wie logistische Regression, wiederkehrende neuronale Netze und Transformatoren ausprobieren. Wir werden prüfen, wie genau sie sind, wie sie das Produkt verbessern, wie schnell sie arbeiten und ob sie leicht zu debuggen sind und vergrößern.


Sie können den vollständigen Fallstudiencode auf GitHub lesen und das Analysenotizbuch mit interaktiven Diagrammen im Jupyter Notebook Viewer ansehen.


Aufgeregt? Lasst uns anfangen!

Aufgabenstellung

Stellen Sie sich vor, wir besitzen eine E-Commerce-Website. Auf dieser Website kann der Verkäufer die Beschreibungen der Artikel hochladen, die er verkaufen möchte. Außerdem müssen sie die Artikelkategorien manuell auswählen, was sie möglicherweise verlangsamt.


Unsere Aufgabe ist es, die Auswahl der Kategorien basierend auf der Artikelbeschreibung zu automatisieren. Eine falsch automatisierte Auswahl ist jedoch schlimmer als keine Automatisierung, da ein Fehler möglicherweise unbemerkt bleibt und zu Umsatzeinbußen führen kann. Wenn wir uns nicht sicher sind, entscheiden wir uns daher möglicherweise dafür, kein automatisches Label festzulegen.


Für diese Fallstudie verwenden wir die Zenodo E-Commerce-Textdatensatz , enthält Artikelbeschreibungen und Kategorien.


Gut oder schlecht? So wählen Sie das beste Modell aus

Wir werden im Folgenden mehrere Modellarchitekturen betrachten und es empfiehlt sich immer, vor Beginn zu entscheiden, wie die beste Option ausgewählt wird. Wie wird sich dieses Modell auf unser Produkt auswirken? …unsere Infrastruktur?


Natürlich werden wir über eine technische Qualitätsmetrik verfügen, um verschiedene Modelle offline zu vergleichen. In diesem Fall handelt es sich um eine Klassifizierungsaufgabe mit mehreren Klassen. Daher verwenden wir eine ausgeglichene Genauigkeitsbewertung , die unausgeglichene Bezeichnungen gut handhaben kann.


Die typische letzte Phase beim Testen eines Kandidaten ist natürlich das AB-Testen – die Online-Phase, die ein besseres Bild davon vermittelt, wie die Kunden von der Änderung betroffen sind. AB-Tests sind in der Regel zeitaufwändiger als Offline-Tests, daher werden nur die besten Kandidaten aus der Offline-Phase getestet. Dies ist eine Fallstudie und wir haben keine tatsächlichen Benutzer, daher werden wir uns nicht mit AB-Tests befassen.


Was sollten wir sonst noch beachten, bevor wir einen Kandidaten zum AB-Test überweisen? Woran können wir in der Offline-Phase denken, um Zeit beim Online-Testen zu sparen und sicherzustellen, dass wir wirklich die bestmögliche Lösung testen?


Technische Kennzahlen in wirkungsorientierte Kennzahlen umwandeln

Die ausgewogene Genauigkeit ist großartig, aber dieser Wert beantwortet nicht die Frage „Wie genau wird sich das Modell auf das Produkt auswirken?“. Um eine produktorientiertere Bewertung zu finden, müssen wir verstehen, wie wir das Modell verwenden werden.


In unserem Fall ist es schlimmer, einen Fehler zu machen, als keine Antwort zu geben, da der Verkäufer den Fehler bemerken und die Kategorie manuell ändern muss. Ein unbemerkter Fehler verringert den Umsatz und verschlechtert die Benutzererfahrung des Verkäufers. Wir riskieren, Kunden zu verlieren.


Um dies zu vermeiden, wählen wir Schwellenwerte für die Modellbewertung so, dass wir uns nur 1 % der Fehler erlauben. Die produktorientierte Metrik kann dann wie folgt festgelegt werden:


Wie viel Prozent der Artikel können wir automatisch kategorisieren, wenn unsere Fehlertoleranz nur 1 % beträgt?


Bei der Auswahl des besten Modells nennen wir dies im Folgenden Automatic categorisation percentage . Den vollständigen Auswahlcode für den Schwellenwert finden Sie hier .


Inferenzzeit

Wie lange braucht ein Model, um eine Anfrage zu bearbeiten?


Dadurch können wir grob vergleichen, wie viel mehr Ressourcen wir vorhalten müssen, damit ein Dienst die Aufgabenlast bewältigen kann, wenn ein Modell einem anderen vorgezogen wird.


Skalierbarkeit

Wenn unser Produkt wachsen soll, wie einfach wird es sein, das Wachstum mithilfe einer bestimmten Architektur zu verwalten?


Mit Wachstum könnten wir Folgendes meinen:

  • mehr Kategorien, höhere Granularität der Kategorien
  • längere Beschreibungen
  • größere Datensätze
  • usw

Müssen wir die Modellwahl überdenken, um dem Wachstum gerecht zu werden, oder reicht eine einfache Umschulung aus?


Interpretierbarkeit

Wie einfach wird es sein, Modellfehler während des Trainings und nach der Bereitstellung zu debuggen?


Modellgröße

Die Modellgröße ist wichtig, wenn:

  • Wir möchten, dass unser Modell auf Kundenseite evaluiert wird
  • Es ist so groß, dass es nicht in den RAM passt


Wir werden später sehen, dass die beiden oben genannten Punkte nicht relevant sind, aber es lohnt sich dennoch, kurz darüber nachzudenken.

Datensatz-Exploration und -Bereinigung

Womit arbeiten wir? Schauen wir uns die Daten an und prüfen, ob sie bereinigt werden müssen!


Der Datensatz enthält zwei Spalten: Artikelbeschreibung und Kategorie, insgesamt 50,5.000 Zeilen.

 file_name = "ecommerceDataset.csv" data = pd.read_csv(file_name, header=None) data.columns = ["category", "description"] print("Rows, cols:", data.shape) # >>> Rows, cols: (50425, 2)


Jedem Artikel ist eine der vier verfügbaren Kategorien zugeordnet: Household , Books , Electronics oder Clothing & Accessories . Hier ist ein Beispiel für eine Artikelbeschreibung pro Kategorie:


  • Haushalt SPK Home decor Ton Handgefertigter Wandbehang Gesicht (Mehrfarbig, H35xB12cm) Machen Sie Ihr Zuhause schöner mit diesem handgefertigten Terrakotta-Wandbehang mit indischer Gesichtsmaske, nie zuvor können Sie dieses handgefertigte Ding auf dem Markt finden. Sie können dies zu Ihrem Wohnzimmer/Eingangsbereich hinzufügen.


  • Bücher BEGF101/FEG1-Foundation Course in English-1 (Neeraj Publications 2018 Edition) BEGF101/FEG1-Foundation Course in English-1


  • Kleidung und Accessoires : Denim-Latzhose für Damen von Broadstar. Verdienen Sie sich mit Latzhosen von Broadstar einen All-Access-Pass. Diese Latzhose aus Denim sorgt für ein angenehmes Tragegefühl. Kombinieren Sie sie mit einem weißen oder schwarzen Oberteil, um Ihren lässigen Look zu vervollständigen.


  • Electronics Caprigo Heavy Duty – 2 Fuß Premium-Deckenhalterung für Projektoren (verstellbar – Weiß – Tragfähigkeit 15 kg)


Fehlende Werte

Es gibt nur einen leeren Wert im Datensatz, den wir entfernen werden.

 print(data.info()) # <class 'pandas.core.frame.DataFrame'> # RangeIndex: 50425 entries, 0 to 50424 # Data columns (total 2 columns): # # Column Non-Null Count Dtype # --- ------ -------------- ----- # 0 category 50425 non-null object # 1 description 50424 non-null object # dtypes: object(2) # memory usage: 788.0+ KB data.dropna(inplace=True)


Duplikate

Es gibt jedoch eine ganze Reihe doppelter Beschreibungen. Glücklicherweise gehören alle Duplikate zu einer Kategorie, sodass wir sie bedenkenlos löschen können.

 repeated_messages = data \ .groupby("description", as_index=False) \ .agg( n_repeats=("category", "count"), n_unique_categories=("category", lambda x: len(np.unique(x))) ) repeated_messages = repeated_messages[repeated_messages["n_repeats"] > 1] print(f"Count of repeated messages (unique): {repeated_messages.shape[0]}") print(f"Total number: {repeated_messages['n_repeats'].sum()} out of {data.shape[0]}") # >>> Count of repeated messages (unique): 13979 # >>> Total number: 36601 out of 50424


Nach dem Entfernen der Duplikate verbleiben 55 % des ursprünglichen Datensatzes. Der Datensatz ist ausgewogen.

 data.drop_duplicates(inplace=True) print(f"New dataset size: {data.shape}") print(data["category"].value_counts()) # New dataset size: (27802, 2) # Household 10564 # Books 6256 # Clothing & Accessories 5674 # Electronics 5308 # Name: category, dtype: int64


Beschreibungssprache

Beachten Sie, dass gemäß der Datensatzbeschreibung

Der Datensatz wurde von der indischen E-Commerce-Plattform entfernt.


Die Beschreibungen sind nicht unbedingt auf Englisch verfasst. Einige von ihnen sind in Hindi oder anderen Sprachen unter Verwendung von Nicht-ASCII-Symbolen geschrieben oder in das lateinische Alphabet transkribiert oder verwenden eine Mischung aus Sprachen. Beispiele aus der Kategorie Books :


  • यू जी सी – नेट जूनियर रिसर्च फैलोशिप एवं सहायक प्रोफेसर योग्यता …
  • Prarambhik Bhartiy Itihas
  • History of NORTH INDIA/வட இந்திய வரலாறு/ …


Um das Vorhandensein nicht-englischer Wörter in Beschreibungen zu bewerten, berechnen wir zwei Punkte:


  • ASCII-Score: Prozentsatz der Nicht-ASCII-Symbole in einer Beschreibung
  • Bewertung gültiger englischer Wörter: Wenn wir nur lateinische Buchstaben berücksichtigen, wie viel Prozent der Wörter in der Beschreibung sind auf Englisch gültig? Nehmen wir an, dass gültige englische Wörter diejenigen sind, die in Word2Vec-300 vorhanden sind und auf einem englischen Korpus trainiert wurden.


Mithilfe des ASCII-Scores erfahren wir, dass nur 2,3 % der Beschreibungen aus mehr als 1 % Nicht-ASCII-Symbolen bestehen.

 def get_ascii_score(description): total_sym_cnt = 0 ascii_sym_cnt = 0 for sym in description: total_sym_cnt += 1 if sym.isascii(): ascii_sym_cnt += 1 return ascii_sym_cnt / total_sym_cnt data["ascii_score"] = data["description"].apply(get_ascii_score) data[data["ascii_score"] < 0.99].shape[0] / data.shape[0] # >>> 0.023


Der Wert für gültige englische Wörter zeigt, dass nur 1,5 % der Beschreibungen weniger als 70 % gültige englische Wörter unter den ASCII-Wörtern enthalten.

 w2v_eng = gensim.models.KeyedVectors.load_word2vec_format(w2v_path, binary=True) def get_valid_eng_score(description): description = re.sub("[^az \t]+", " ", description.lower()) total_word_cnt = 0 eng_word_cnt = 0 for word in description.split(): total_word_cnt += 1 if word.lower() in w2v_eng: eng_word_cnt += 1 return eng_word_cnt / total_word_cnt data["eng_score"] = data["description"].apply(get_valid_eng_score) data[data["eng_score"] < 0.7].shape[0] / data.shape[0] # >>> 0.015


Daher ist der Großteil der Beschreibungen (ca. 96 %) auf Englisch bzw. größtenteils auf Englisch. Wir können alle anderen Beschreibungen entfernen, lassen sie aber stattdessen unverändert und schauen uns dann an, wie jedes Modell damit umgeht.

Modellieren

Teilen wir unseren Datensatz in drei Gruppen auf:

  • Trainieren Sie 70 % – zum Trainieren der Modelle (19.000 Nachrichten)

  • Test 15 % – für die Auswahl von Parametern und Schwellenwerten (4,1.000 Nachrichten)

  • Evaluieren Sie 15 % – für die Auswahl des endgültigen Modells (4,1.000 Nachrichten)


 from sklearn.model_selection import train_test_split data_train, data_test = train_test_split(data, test_size=0.3) data_test, data_eval = train_test_split(data_test, test_size=0.5) data_train.shape, data_test.shape, data_eval.shape # >>> ((19461, 3), (4170, 3), (4171, 3))


Basismodell: Wortbeutel + logistische Regression

Es ist hilfreich, zunächst etwas Einfaches und Triviales zu tun, um eine gute Ausgangslage zu erhalten. Als Grundlage erstellen wir eine Wortstruktur basierend auf dem Zugdatensatz.


Begrenzen wir außerdem die Wörterbuchgröße auf 100 Wörter.

 count_vectorizer = CountVectorizer(max_features=100, stop_words="english") x_train_baseline = count_vectorizer.fit_transform(data_train["description"]) y_train_baseline = data_train["category"] x_test_baseline = count_vectorizer.transform(data_test["description"]) y_test_baseline = data_test["category"] x_train_baseline = x_train_baseline.toarray() x_test_baseline = x_test_baseline.toarray()


Ich habe vor, die logistische Regression als Modell zu verwenden, daher muss ich vor dem Training die Gegenfunktionen normalisieren.

 ss = StandardScaler() x_train_baseline = ss.fit_transform(x_train_baseline) x_test_baseline = ss.transform(x_test_baseline) lr = LogisticRegression() lr.fit(x_train_baseline, y_train_baseline) balanced_accuracy_score(y_test_baseline, lr.predict(x_test_baseline)) # >>> 0.752


Die logistische Regression mit mehreren Klassen zeigte eine ausgewogene Genauigkeit von 75,2 %. Das ist eine tolle Ausgangslage!


Obwohl die Klassifizierungsqualität insgesamt nicht besonders gut ist, kann uns das Modell dennoch einige Erkenntnisse liefern. Schauen wir uns die Verwirrungsmatrix an, normalisiert durch die Anzahl der vorhergesagten Labels. Die X-Achse bezeichnet die vorhergesagte Kategorie und die Y-Achse die tatsächliche Kategorie. Wenn wir uns jede Spalte ansehen, können wir die Verteilung der realen Kategorien sehen, wenn eine bestimmte Kategorie vorhergesagt wurde.


Verwirrungsmatrix für Basislösung.


Beispielsweise wird Electronics häufig mit Household verwechselt. Aber auch dieses einfache Modell kann Clothing & Accessories recht präzise erfassen.


Hier sind die wichtigen Merkmale bei der Vorhersage der Kategorie Clothing & Accessories :

Wichtige Funktionen für die Basislösung für das Label „Bekleidung und Accessoires“


Top 6 der Wörter mit den meisten Beiträgen zur und gegen die Kategorie Clothing & Accessories :

 women 1.49 book -2.03 men 0.93 table -1.47 cotton 0.92 author -1.11 wear 0.69 books -1.10 fit 0.40 led -0.90 stainless 0.36 cable -0.85


RNNs

Betrachten wir nun fortgeschrittenere Modelle, die speziell für die Arbeit mit Sequenzen entwickelt wurden – wiederkehrende neuronale Netze . GRU und LSTM sind gängige fortgeschrittene Schichten zur Bekämpfung der explodierenden Gradienten, die in einfachen RNNs auftreten.


Wir verwenden pytorch Bibliothek, um Beschreibungen zu tokenisieren und ein Modell zu erstellen und zu trainieren.


Zuerst müssen wir Texte in Zahlen umwandeln:

  1. Teilen Sie Beschreibungen in Wörter auf
  2. Weisen Sie jedem Wort im Korpus basierend auf dem Trainingsdatensatz einen Index zu
  3. Reservieren Sie spezielle Indizes für unbekannte Wörter und Auffüllungen
  4. Transformieren Sie jede Beschreibung in Trainings- und Testdatensätzen in Indexvektoren.


Der Wortschatz, den wir durch die einfache Tokenisierung des Zugdatensatzes erhalten, ist groß – fast 90.000 Wörter. Je mehr Wörter wir haben, desto größer ist der Einbettungsraum, den das Modell lernen muss. Um das Training zu vereinfachen, entfernen wir die seltensten Wörter daraus und lassen nur diejenigen übrig, die in mindestens 3 % der Beschreibungen vorkommen. Dadurch wird der Wortschatz auf 340 Wörter gekürzt.

(Die vollständige CorpusDictionary Implementierung finden Sie hier .)


 corpus_dict = util.CorpusDictionary(data_train["description"]) corpus_dict.truncate_dictionary(min_frequency=0.03) data_train["vector"] = corpus_dict.transform(data_train["description"]) data_test["vector"] = corpus_dict.transform(data_test["description"]) print(data_train["vector"].head()) # 28453 [1, 1, 1, 1, 12, 1, 2, 1, 6, 1, 1, 1, 1, 1, 6,... # 48884 [1, 1, 13, 34, 3, 1, 1, 38, 12, 21, 2, 1, 37, ... # 36550 [1, 60, 61, 1, 62, 60, 61, 1, 1, 1, 1, 10, 1, ... # 34999 [1, 34, 1, 1, 75, 60, 61, 1, 1, 72, 1, 1, 67, ... # 19183 [1, 83, 1, 1, 87, 1, 1, 1, 12, 21, 42, 1, 2, 1... # Name: vector, dtype: object


Als nächstes müssen wir die gemeinsame Länge der Vektoren festlegen, die wir als Eingaben in RNN einspeisen werden. Wir möchten keine vollständigen Vektoren verwenden, da die längste Beschreibung 9,4.000 Token enthält.


Allerdings sind 95 % der Beschreibungen im Zugdatensatz nicht länger als 352 Token – das ist eine gute Länge zum Kürzen. Was passiert mit kürzeren Beschreibungen?


Sie werden mit dem Polsterindex bis zur üblichen Länge gepolstert.

 print(max(data_train["vector"].apply(len))) # >>> 9388 print(int(np.quantile(data_train["vector"].apply(len), q=0.95))) # >>> 352


Als nächstes müssen wir Zielkategorien in 0-1-Vektoren umwandeln, um den Verlust zu berechnen und bei jedem Trainingsschritt eine Rückausbreitung durchzuführen.

 def get_target(label, total_labels=4): target = [0] * total_labels target[label_2_idx.get(label)] = 1 return target data_train["target"] = data_train["category"].apply(get_target) data_test["target"] = data_test["category"].apply(get_target)


Jetzt können wir einen benutzerdefinierten pytorch Datensatz und Dataloader erstellen, um ihn in das Modell einzuspeisen. Die vollständige Implementierung PaddedTextVectorDataset finden Sie hier .

 ds_train = util.PaddedTextVectorDataset( data_train["description"], data_train["target"], corpus_dict, max_vector_len=352, ) ds_test = util.PaddedTextVectorDataset( data_test["description"], data_test["target"], corpus_dict, max_vector_len=352, ) train_dl = DataLoader(ds_train, batch_size=512, shuffle=True) test_dl = DataLoader(ds_test, batch_size=512, shuffle=False)


Lassen Sie uns zum Schluss ein Modell erstellen.


Die minimale Architektur ist:

  • Einbettungsschicht
  • RNN-Schicht
  • lineare Schicht
  • Aktivierungsschicht


Beginnend mit kleinen Parameterwerten (Größe des Einbettungsvektors, Größe einer verborgenen Schicht im RNN, Anzahl der RNN-Schichten) und ohne Regularisierung können wir das Modell schrittweise komplizierter machen, bis es starke Anzeichen einer Überanpassung zeigt, und es dann ausgleichen Regularisierung (Ausfälle in der RNN-Schicht und vor der letzten linearen Schicht).


 class GRU(nn.Module): def __init__(self, vocab_size, embedding_dim, n_hidden, n_out): super().__init__() self.vocab_size = vocab_size self.embedding_dim = embedding_dim self.n_hidden = n_hidden self.n_out = n_out self.emb = nn.Embedding(self.vocab_size, self.embedding_dim) self.gru = nn.GRU(self.embedding_dim, self.n_hidden) self.dropout = nn.Dropout(0.3) self.out = nn.Linear(self.n_hidden, self.n_out) def forward(self, sequence, lengths): batch_size = sequence.size(1) self.hidden = self._init_hidden(batch_size) embs = self.emb(sequence) embs = pack_padded_sequence(embs, lengths, enforce_sorted=True) gru_out, self.hidden = self.gru(embs, self.hidden) gru_out, lengths = pad_packed_sequence(gru_out) dropout = self.dropout(self.hidden[-1]) output = self.out(dropout) return F.log_softmax(output, dim=-1) def _init_hidden(self, batch_size): return Variable(torch.zeros((1, batch_size, self.n_hidden)))


Wir verwenden Adam Optimierer und cross_entropy als Verlustfunktion.


 vocab_size = len(corpus_dict.word_to_idx) emb_dim = 4 n_hidden = 15 n_out = len(label_2_idx) model = GRU(vocab_size, emb_dim, n_hidden, n_out) opt = optim.Adam(model.parameters(), 1e-2) util.fit( model=model, train_dl=train_dl, test_dl=test_dl, loss_fn=F.cross_entropy, opt=opt, epochs=35 ) # >>> Train loss: 0.3783 # >>> Val loss: 0.4730 

Zug- und Testverluste pro Epoche, RNN-Modell

Dieses Modell zeigte eine ausgewogene Genauigkeit von 84,3 % im Bewertungsdatensatz. Wow, was für ein Fortschritt!


Einführung vorab trainierter Einbettungen

Der größte Nachteil beim Training des RNN-Modells von Grund auf besteht darin, dass es die Bedeutung der Wörter selbst lernen muss – das ist die Aufgabe der Einbettungsschicht. Vorab trainierte word2vec Modelle stehen zur Verwendung als vorgefertigte Einbettungsschicht zur Verfügung, wodurch die Anzahl der Parameter reduziert und den Tokens viel mehr Bedeutung verliehen wird. Verwenden wir eines der in pytorch verfügbaren word2vec Modelle – glove, dim=300 .


Wir müssen nur geringfügige Änderungen an der Datensatzerstellung vornehmen – wir möchten jetzt einen Vektor mit glove Indizes für jede Beschreibung und die Modellarchitektur erstellen.

 ds_emb_train = util.PaddedTextVectorDataset( data_train["description"], data_train["target"], emb=glove, max_vector_len=max_len, ) ds_emb_test = util.PaddedTextVectorDataset( data_test["description"], data_test["target"], emb=glove, max_vector_len=max_len, ) dl_emb_train = DataLoader(ds_emb_train, batch_size=512, shuffle=True) dl_emb_test = DataLoader(ds_emb_test, batch_size=512, shuffle=False)
 import torchtext.vocab as vocab glove = vocab.GloVe(name='6B', dim=300) class LSTMPretrained(nn.Module): def __init__(self, n_hidden, n_out): super().__init__() self.emb = nn.Embedding.from_pretrained(glove.vectors) self.emb.requires_grad_ = False self.embedding_dim = 300 self.n_hidden = n_hidden self.n_out = n_out self.lstm = nn.LSTM(self.embedding_dim, self.n_hidden, num_layers=1) self.dropout = nn.Dropout(0.5) self.out = nn.Linear(self.n_hidden, self.n_out) def forward(self, sequence, lengths): batch_size = sequence.size(1) self.hidden = self.init_hidden(batch_size) embs = self.emb(sequence) embs = pack_padded_sequence(embs, lengths, enforce_sorted=True) lstm_out, (self.hidden, _) = self.lstm(embs) lstm_out, lengths = pad_packed_sequence(lstm_out) dropout = self.dropout(self.hidden[-1]) output = self.out(dropout) return F.log_softmax(output, dim=-1) def init_hidden(self, batch_size): return Variable(torch.zeros((1, batch_size, self.n_hidden)))


Und wir sind bereit zum Training!

 n_hidden = 50 n_out = len(label_2_idx) emb_model = LSTMPretrained(n_hidden, n_out) opt = optim.Adam(emb_model.parameters(), 1e-2) util.fit(model=emb_model, train_dl=dl_emb_train, test_dl=dl_emb_test, loss_fn=F.cross_entropy, opt=opt, epochs=11) 

Trainings- und Testverluste pro Epoche, RNN-Modell + vorab trainierte Einbettungen

Jetzt erreichen wir eine ausgewogene Genauigkeit von 93,7 % für den Bewertungsdatensatz. Umwerben!


BERT

Moderne, hochmoderne Modelle für die Arbeit mit Sequenzen sind Transformatoren. Um einen Transformator von Grund auf zu trainieren, bräuchten wir jedoch riesige Datenmengen und Rechenressourcen. Was wir hier versuchen können, ist, eines der vorab trainierten Modelle so zu optimieren, dass es unseren Zweck erfüllt. Dazu müssen wir ein vorab trainiertes BERT-Modell herunterladen und Dropout und eine lineare Ebene hinzufügen, um die endgültige Vorhersage zu erhalten. Es wird empfohlen, ein abgestimmtes Modell für 4 Epochen zu trainieren. Um Zeit zu sparen, habe ich nur zwei zusätzliche Epochen trainiert – ich habe dafür 40 Minuten gebraucht.


 from transformers import BertModel class BERTModel(nn.Module): def __init__(self, n_out=12): super(BERTModel, self).__init__() self.l1 = BertModel.from_pretrained('bert-base-uncased') self.l2 = nn.Dropout(0.3) self.l3 = nn.Linear(768, n_out) def forward(self, ids, mask, token_type_ids): output_1 = self.l1(ids, attention_mask = mask, token_type_ids = token_type_ids) output_2 = self.l2(output_1.pooler_output) output = self.l3(output_2) return output


 ds_train_bert = bert.get_dataset( list(data_train["description"]), list(data_train["target"]), max_vector_len=64 ) ds_test_bert = bert.get_dataset( list(data_test["description"]), list(data_test["target"]), max_vector_len=64 ) dl_train_bert = DataLoader(ds_train_bert, sampler=RandomSampler(ds_train_bert), batch_size=batch_size) dl_test_bert = DataLoader(ds_test_bert, sampler=SequentialSampler(ds_test_bert), batch_size=batch_size)


 b_model = bert.BERTModel(n_out=4) b_model.to(torch.device("cpu")) def loss_fn(outputs, targets): return torch.nn.BCEWithLogitsLoss()(outputs, targets) optimizer = optim.AdamW(b_model.parameters(), lr=2e-5, eps=1e-8) epochs = 2 scheduler = get_linear_schedule_with_warmup( optimizer, num_warmup_steps=0, num_training_steps=total_steps ) bert.fit(b_model, dl_train_bert, dl_test_bert, optimizer, scheduler, loss_fn, device, epochs=epochs) torch.save(b_model, "models/bert_fine_tuned")


Trainingsprotokoll:

 2024-02-29 19:38:13.383953 Epoch 1 / 2 Training... 2024-02-29 19:40:39.303002 step 40 / 305 done 2024-02-29 19:43:04.482043 step 80 / 305 done 2024-02-29 19:45:27.767488 step 120 / 305 done 2024-02-29 19:47:53.156420 step 160 / 305 done 2024-02-29 19:50:20.117272 step 200 / 305 done 2024-02-29 19:52:47.988203 step 240 / 305 done 2024-02-29 19:55:16.812437 step 280 / 305 done 2024-02-29 19:56:46.990367 Average training loss: 0.18 2024-02-29 19:56:46.990932 Validating... 2024-02-29 19:57:51.182859 Average validation loss: 0.10 2024-02-29 19:57:51.182948 Epoch 2 / 2 Training... 2024-02-29 20:00:25.110818 step 40 / 305 done 2024-02-29 20:02:56.240693 step 80 / 305 done 2024-02-29 20:05:25.647311 step 120 / 305 done 2024-02-29 20:07:53.668489 step 160 / 305 done 2024-02-29 20:10:33.936778 step 200 / 305 done 2024-02-29 20:13:03.217450 step 240 / 305 done 2024-02-29 20:15:28.384958 step 280 / 305 done 2024-02-29 20:16:57.004078 Average training loss: 0.08 2024-02-29 20:16:57.004657 Validating... 2024-02-29 20:18:01.546235 Average validation loss: 0.09


Schließlich zeigt das fein abgestimmte BERT-Modell eine ausgewogene Genauigkeit von satten 95,1 % für den Bewertungsdatensatz.


Auswahl unseres Gewinners

Wir haben bereits eine Liste mit Überlegungen zusammengestellt, die Sie berücksichtigen sollten, um eine endgültige, fundierte Entscheidung zu treffen.

Hier sind Diagramme mit messbaren Parametern:

Leistungsmetriken der Modelle


Obwohl das fein abgestimmte BERT qualitativ führend ist, liegt RNN mit der vorab trainierten Einbettungsschicht LSTM+EMB knapp dahinter und fällt nur um 3 % der automatischen Kategoriezuweisungen zurück.


Andererseits ist die Inferenzzeit des fein abgestimmten BERT 14-mal länger als die LSTM+EMB . Dies wird zu Backend-Wartungskosten führen, die wahrscheinlich die Vorteile überwiegen, die das fein abgestimmte BERT gegenüber LSTM+EMB bietet.


Was die Interoperabilität betrifft, ist unser grundlegendes logistisches Regressionsmodell bei weitem das am besten interpretierbare und jedes neuronale Netzwerk hat in dieser Hinsicht die Nase vorn. Gleichzeitig ist die Basislinie wahrscheinlich am wenigsten skalierbar – das Hinzufügen von Kategorien verringert die ohnehin schon geringe Qualität der Basislinie.


Auch wenn BERT mit seiner hohen Genauigkeit wie der Superstar erscheint, entscheiden wir uns am Ende für das RNN mit einer vorab trainierten Einbettungsschicht. Warum? Es ist ziemlich genau, nicht zu langsam und wird nicht zu kompliziert in der Handhabung, wenn die Dinge groß werden.


Ich hoffe, Ihnen hat diese Fallstudie gefallen. Für welches Modell hätten Sie sich entschieden und warum?