<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>OOV Token Archives - Nerd Corner</title>
	<atom:link href="https://nerd-corner.com/de/tag/oov-token/feed/" rel="self" type="application/rss+xml" />
	<link>https://nerd-corner.com/de/tag/oov-token/</link>
	<description>Craft your dreams!</description>
	<lastBuildDate>Thu, 01 Jun 2023 10:24:24 +0000</lastBuildDate>
	<language>de</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.2</generator>

<image>
	<url>https://nerd-corner.com/wp-content/uploads/2019/10/cropped-LogoNerdCorner-2-32x32.png</url>
	<title>OOV Token Archives - Nerd Corner</title>
	<link>https://nerd-corner.com/de/tag/oov-token/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>NLP Anwendung: Tensorflow.js vs Tensorflow Python &#8211; Teil 2</title>
		<link>https://nerd-corner.com/de/nlp-anwendung-tensorflow-js-vs-tensorflow-python-teil-2/</link>
					<comments>https://nerd-corner.com/de/nlp-anwendung-tensorflow-js-vs-tensorflow-python-teil-2/#comments</comments>
		
		<dc:creator><![CDATA[Nerds]]></dc:creator>
		<pubDate>Wed, 31 May 2023 17:15:48 +0000</pubDate>
				<category><![CDATA[Software-DE]]></category>
		<category><![CDATA[adam]]></category>
		<category><![CDATA[Decoder]]></category>
		<category><![CDATA[Encoder]]></category>
		<category><![CDATA[GPU]]></category>
		<category><![CDATA[hidden state]]></category>
		<category><![CDATA[JSON]]></category>
		<category><![CDATA[Keras]]></category>
		<category><![CDATA[Long short term memory]]></category>
		<category><![CDATA[loss function]]></category>
		<category><![CDATA[LSTM]]></category>
		<category><![CDATA[Map]]></category>
		<category><![CDATA[Natural Language Processing]]></category>
		<category><![CDATA[NLP]]></category>
		<category><![CDATA[OOV]]></category>
		<category><![CDATA[OOV Token]]></category>
		<category><![CDATA[optimizer]]></category>
		<category><![CDATA[Pad sequences]]></category>
		<category><![CDATA[Padding]]></category>
		<category><![CDATA[rmsprop]]></category>
		<category><![CDATA[Tensorflow]]></category>
		<category><![CDATA[Tensorflow Anleitung]]></category>
		<category><![CDATA[TensorFlow GPU]]></category>
		<category><![CDATA[Tensorflow python]]></category>
		<category><![CDATA[Tensorflow.js]]></category>
		<category><![CDATA[Tensorflow.js vs Tensorflow]]></category>
		<category><![CDATA[Tfjs]]></category>
		<category><![CDATA[Tokenisierung]]></category>
		<category><![CDATA[Tokenizer]]></category>
		<category><![CDATA[Txt Datensatz]]></category>
		<category><![CDATA[WordTokenizer]]></category>
		<guid isPermaLink="false">https://nerd-corner.com/de/?p=1426</guid>

					<description><![CDATA[<p>Im ersten Teil wurde das Einlesen und das Vorbereiten der Daten gezeigt. Außerdem wurde ausführlich die Tokenisierung des Datensatzes besprochen. Veranschaulicht wurden die Punkte anhand &#8230; </p>
<p>The post <a href="https://nerd-corner.com/de/nlp-anwendung-tensorflow-js-vs-tensorflow-python-teil-2/">NLP Anwendung: Tensorflow.js vs Tensorflow Python &#8211; Teil 2</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Im ersten Teil wurde das Einlesen und das Vorbereiten der Daten gezeigt. Außerdem wurde ausführlich die Tokenisierung des Datensatzes besprochen. Veranschaulicht wurden die Punkte anhand eines Beispiels in Tensorflow (Python) und Tensorflow.js (Tfjs). Sowohl bei dem Python Beispiel, als auch bei dem JavaScript Beispiel kann das Modell am Ende aber nur Wörter erkennen, die mindestens einmal im Datensatz vorgekommen sind. Mit neuen Wörtern hat dieses Modell Probleme, denn wir berücksichtigen hier keinen OOV Token.</p>
<p><em><strong>Das könnte dich auch interessieren:</strong> <a href="https://nerd-corner.com/de/nlp-anwendung-tensorflow-js-vs-tensorflow-python/">NLP Anwendung Teil 1 (Daten einlesen, Daten vorbereiten und Tokenisierung)</a></em></p>
<h2>OOV Token</h2>
<p>Bei einem Übersetzer ist es nur eine Frage der Zeit bis ein unbekanntes Wort eingegeben wird. Das kann ein Eigenname, ein Rechtschreibfehler oder ähnliches sein. Es empfiehlt sich daher das Modell auch im Hinblick auf unbekannte Wörter zu trainieren. Hierfür wird ein OOV Token benötigt. OOV steht für „out of vocabulary“. Während des Trainings lernt das Modell, diesen Token zu erzeugen oder entsprechend zu behandeln. In diesem Fall kann ein unbekanntes Wort durch den Token &#8222;&lt;oov&gt;&#8220; ersetzt werden, bevor es an das Modell übergeben wird. Das Modell wird es dann wie jedes andere Token behandeln und eine Antwort auf der Grundlage seines gelernten Verhaltens generieren.</p>
<p>Jetzt fragt man sich vielleicht wie inkludiert man diesen OOV Token in die Trainingsdaten? Ich mache das so, dass mein Datensatz zu Beginn automatisiert nach Wörtern durchsucht wird, die nur 1 Mal vorkommen. Diese seltenen Wörter ersetze ich dann durch „&lt;oov&gt;“, damit kann mein Modell lernen auch unbekannte Wörter zu reagieren.</p>
<h2>Padding</h2>
<p>Bei vielen Modellen des maschinellen Lernens, einschließlich neuronaler Netze, wird erwartet, dass die Eingaben eine feste Größe oder Form haben. Diese Anforderung ergibt sich aus der Struktur und dem Betrieb des zugrunde liegenden Berechnungsgraphen. Eingaben mit derselben Länge vereinfachen die Datenverarbeitungspipeline und ermöglichen eine effiziente Stapelverarbeitung.</p>
<h4>Warum die Eingaben gleich lang sein sollten:</h4>
<ol>
<li>Matrix-Operationen: Neuronale Netze verarbeiten Eingaben in der Regel in Stapeln, und die Stapelverarbeitung ist am effizientesten, wenn die Eingabedaten eine einheitliche Form haben. Die Daten sind in Matrizen organisiert, wobei jede Zeile eine Eingabeinstanz darstellt. Um Matrixoperationen effizient durchführen zu können, müssen alle Eingabeinstanzen die gleiche Form haben.</li>
<li>Gemeinsame Nutzung von Parametern: In vielen neuronalen Netzwerkarchitekturen werden die Modellparameter (Gewichte) auf verschiedene Teile der Eingabesequenz verteilt. In rekurrenten neuronalen Netzen (RNNs) werden beispielsweise dieselben Gewichte für die Verarbeitung jedes Zeitschritts verwendet. Um die gemeinsame Nutzung von Parametern zu ermöglichen, müssen alle Eingabesequenzen die gleiche Länge haben.</li>
<li>Speicherzuweisung: Neuronale Netze weisen den Speicher oft auf der Grundlage der maximalen Länge der Eingabesequenzen zu. Wenn die Sequenzen unterschiedliche Längen haben, ist eine dynamische Speicherzuweisung erforderlich, die komplexer und weniger effizient sein kann.</li>
</ol>
<p>Es ist zwar möglich, Eingaben mit variabler Länge durch Techniken wie Auffüllen und Maskieren zu verarbeiten, aber dies erhöht die Komplexität des Modells und kann zusätzliche Verarbeitungsschritte erfordern. Der Einfachheit und Effizienz halber ist es daher üblich, Sequenzen auf eine feste Länge aufzufüllen oder abzuschneiden, bevor sie in ein neuronales Netzmodell eingespeist werden.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="atomic">from keras.utils import pad_sequences

# pad sequences
encoder_seq = pad_sequences(encoder, maxlen=max_encoder_sequence_len, padding="post")
decoder_inp = pad_sequences([arr[:-1] for arr in decoder], maxlen=max_decoder_sequence_len, padding="post")
decoder_output = pad_sequences([arr[1:] for arr in decoder], maxlen=max_decoder_sequence_len, padding="post")
print(encoder_seq)
print([idx_2_txt_encoder[i] for i in encoder_seq[0]])
print([idx_2_txt_decoder[i] for i in decoder_inp[0]])
print([idx_2_txt_decoder[i] for i in decoder_output[0]])</pre>
<p>Zur besseren Veranschaulichung habe ich die 4 print Befehle hinzugefügt. Anfangs dachte ich, dass der längste Satz im Datensatz die Länge für Input Daten und Output Daten vorgibt. Also die Paddinglänge für Input und Output gleich wäre. Das ist aber nicht der Fall! Input Daten und Output Daten sind auf unterschiedliche Längen normiert!</p>
<p>In dem Beispiel hier, habe ich einen winzigen Datensatz benutzt bei dem der längste englische Satz aus 3 Wörtern besteht und der längste französische Satz aus 10 Wörtern. Demnach wird mit &#8222;&lt;pad&gt;&#8220; bzw 0 jeder Trainingssatz aufgefüllt bis der Input 3 bzw. der Output 10 Wörter erreicht hat.</p>
<p><img fetchpriority="high" decoding="async" class="aligncenter wp-image-1398 zoooom" src="https://nerd-corner.com/wp-content/uploads/2023/05/sampleOutputPadding.png" alt="Sample output padding tensorflow" width="1230" height="351" srcset="https://nerd-corner.com/wp-content/uploads/2023/05/sampleOutputPadding.png 1243w, https://nerd-corner.com/wp-content/uploads/2023/05/sampleOutputPadding-300x86.png 300w, https://nerd-corner.com/wp-content/uploads/2023/05/sampleOutputPadding-1024x292.png 1024w, https://nerd-corner.com/wp-content/uploads/2023/05/sampleOutputPadding-768x219.png 768w" sizes="(max-width: 1230px) 100vw, 1230px" /></p>
<p>Der Decoder Output mit <strong>[arr[1:] for arr in decoder] </strong>entfernt den &#8222;start&#8220; token und der Decoder Input mit <strong>[arr[:-1] for arr in decoder] </strong>entfernt den &#8222;end&#8220; token.</p>
<p>Bei Sequenz-zu-Sequenz-Modellen wird der Decoder darauf trainiert, die Ausgabesequenz auf der Grundlage der Eingabesequenz und der zuvor generierten Token zu erzeugen. Während des Trainings enthält die Eingangssequenz des Decoders den &#8222;Start&#8220;-Token, der als Initialisierungs-Token für den Decoder dient. Beim Training des Decoders soll dieser jedoch den nächsten Token auf der Grundlage der zuvor generierten Token vorhersagen, mit Ausnahme des &#8222;Start&#8220;-Tokens. Daher wird bei der Vorbereitung der Decoder-Ausgabesequenz das &#8222;Start&#8220;-Token aus jeder Sequenz entfernt. Dies geschieht, um die Eingangs- und Ausgangssequenzen des Decoders korrekt aufeinander abzustimmen. Die Decoder-Eingangssequenz enthält den &#8222;Start&#8220;-Token und schließt den &#8222;End&#8220;-Token aus, während die Decoder-Ausgangssequenz den &#8222;End&#8220;-Token enthält und den &#8222;Start&#8220;-Token ausschließt. Auf diese Weise stellen wir sicher, dass der Decoder lernt, die richtige Ausgabesequenz auf der Grundlage der Eingabe zu erzeugen.</p>
<p>Während der Inferenz (Modellanwendung nach dem Training) bzw. der Übersetzung können wir bei der Verwendung des trainierten Modells zur Erzeugung von Übersetzungen mit dem &#8222;Start&#8220;-Token beginnen und iterativ Token erzeugen, bis wir auf das &#8222;End&#8220;-Token stoßen oder eine maximale Sequenzlänge erreichen.</p>
<p>Beim Padding für Tensorflow.js übernehmen wir 1:1 die Vorgehensweise von Python. Leider haben wir auch hier wieder mehr Arbeit und mehr Code Zeilen, da in Tfjs keine padSequences Funktion existiert. Ich habe mir deswegen eine eigene padSequences Funktion geschrieben:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">function padSequences(sequences) {
  const paddedSequences = [];
  const maxlen = findMaxLength(sequences);

  for (const sequence of sequences) {
    if (sequence.length &gt;= maxlen) {
      paddedSequences.push(sequence.slice(0, maxlen));
    } else {
      const paddingLength = maxlen - sequence.length;
      const paddingArray = new Array(paddingLength).fill(0);
      const paddedSequence = sequence.concat(paddingArray);
      paddedSequences.push(paddedSequence);
    }
  }

  return paddedSequences;
}</pre>
<p>Anschließend können wir mit Hilfe dieser Funktion unseren encoder, decoder Input und decoder Output bestimmen:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">function pad(data) {
  const encoderSeq = padSequences(data.en);
  const decoderInp = padSequences(data.de.map((arr) =&gt; arr.slice(0, -1))); // Has startToken
  const decoderOutput = padSequences(data.de.map((arr) =&gt; arr.slice(1))); // Has endToken
  console.log(decoderInp);
}</pre>
<p>Bei mir ist die „1“ der „startToken“ daher sieht der decoder Input beispielsweise so aus:</p>
<p><img decoding="async" class="aligncenter wp-image-1420 zoooom" src="https://nerd-corner.com/wp-content/uploads/2023/06/decoder-Input-example-JS.png" alt="Decoder Input Example JS" width="490" height="376" srcset="https://nerd-corner.com/wp-content/uploads/2023/06/decoder-Input-example-JS.png 499w, https://nerd-corner.com/wp-content/uploads/2023/06/decoder-Input-example-JS-300x230.png 300w" sizes="(max-width: 490px) 100vw, 490px" /></p>
<h2>Modell erstellen</h2>
<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="atomic"># Design LSTM NN (Encoder &amp; Decoder)
# encoder model
encoder_input = Input(shape=(None,), name="encoder_input_layer")
encoder_embedding = Embedding(num_encoder_tokens, 300, input_length=max_encoder_sequence_len, name="encoder_embedding_layer")(encoder_input)
encoder_lstm = LSTM(256, activation="tanh", return_sequences=True, return_state=True, name="encoder_lstm_1_layer")(encoder_embedding)
encoder_lstm2 = LSTM(256, activation="tanh", return_state=True, name="encoder_lstm_2_layer")(encoder_lstm)
_, state_h, state_c = encoder_lstm2
encoder_states = [state_h, state_c]

# decoder model
decoder_input = Input(shape=(None,), name="decoder_input_layer")
decoder_embedding = Embedding(num_decoder_tokens, 300, input_length=max_decoder_sequence_len, name="decoder_embedding_layer")(decoder_input)
decoder_lstm = LSTM(256, activation="tanh", return_state=True, return_sequences=True, name="decoder_lstm_layer")
decoder_outputs, _, _ = decoder_lstm(decoder_embedding, initial_state=encoder_states)
decoder_dense = Dense(num_decoder_tokens+1, activation="softmax", name="decoder_final_layer")
outputs = decoder_dense(decoder_outputs)

model = Model([encoder_input, decoder_input], outputs)</pre>
<p>Das Codebeispiel zeigt den  Entwurf eines neuronalen Netzes mit Long Short-Term Memory (LSTM) für Sequenz-zu-Sequenz-Lernen (Seq2Seq) dar, das typischerweise für Aufgaben wie maschinelle Übersetzung verwendet wird. Der Code definiert zwei Hauptteile:  Encoder-Modell und Decoder-Modell.</p>
<h3>Encoder-Modell:</h3>
<ul>
<li style="list-style-type: none;">
<ul>
<li>Die Encoder-Eingangsschicht (<strong>encoder_input</strong>) stellt die Eingangssequenz des Encoder-Modells dar.</li>
<li>Die Eingangssequenz wird mithilfe einer Einbettungsschicht (<strong>encoder_embedding</strong>) eingebettet, die jedes Token in eine dichte Vektordarstellung umwandelt.</li>
<li>Die eingebettete Sequenz wird dann durch die erste LSTM-Schicht (<strong>encoder_lstm_1_layer</strong>) geleitet, um sequenzielle Informationen zu erfassen. Die LSTM-Schicht gibt die Ausgabesequenz und den endgültigen versteckten Zustand zurück.</li>
<li>Die Ausgabesequenz der ersten LSTM-Schicht wird von der zweiten LSTM-Schicht (<strong>encoder_lstm_2_layer</strong>) weiterverarbeitet. Die zweite LSTM-Schicht liefert nur den endgültigen versteckten Zustand, der die zusammengefasste Information der Eingabesequenz darstellt.</li>
<li>Der final hidden Zustand der zweiten LSTM-Schicht wird in den final hidden Zustand (<strong>state_h</strong>) und den final Zellzustand (<strong>state_c</strong>) aufgeteilt, die als Anfangszustände für das Decodermodell verwendet werden.</li>
<li>Die Zustände des Encoder-Modells sind als <strong>encoder_states</strong> definiert und werden an das Decoder-Modell weitergegeben.</li>
</ul>
</li>
</ul>
<h3>Decoder-Modell:</h3>
<ul>
<li>Die Decoder-Eingangsschicht (<strong>decoder_input</strong>) stellt die Eingangssequenz des Decoder-Modells dar, die aus der um eine Position verschobenen Zielsequenz besteht.</li>
<li>Ähnlich wie beim Encoder wird die Eingangssequenz mit Hilfe einer Einbettungsschicht (<strong>decoder_embedding</strong>) eingebettet.</li>
<li>Die eingebettete Sequenz wird dann durch eine LSTM-Schicht (<strong>decoder_lstm_layer</strong>) geleitet, wobei die Anfangszustände auf die Endzustände des Encoder-Modells gesetzt werden. Dies ermöglicht es dem Decoder, die relevanten Informationen des Encoders zu berücksichtigen.</li>
<li>Die LSTM-Schicht liefert die Ausgabesequenz und die Endzustände.</li>
<li>Die Ausgabesequenz aus der LSTM-Schicht wird durch eine dichte Schicht (<strong>decoder_final_layer</strong>) mit einer Softmax-Aktivierungsfunktion geleitet, die die Wahrscheinlichkeitsverteilung über die ausgegebenen Token vorhersagt.</li>
</ul>
<p>Die Klasse <strong>Model</strong> wird zur Erstellung des Gesamtmodells verwendet, indem die Eingabeschichten (<strong>[encoder_input, decoder_input]</strong>) und die Ausgabeschicht (<strong>outputs</strong>) angegeben werden. Diese Modellarchitektur folgt der Grundstruktur eines Encoder-Decoder-Modells unter Verwendung von LSTMs, bei dem der Encoder die Eingabesequenz verarbeitet und den Kontextvektor (final hidden state) erzeugt, der dann vom Decoder zur Erzeugung der Ausgabesequenz verwendet wird.</p>
<p>Das selbe Modell lässt sich auch in JS umsetzen:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">function createModell(
  numEncoderTokens,
  numDecoderTokens,
  maxEncoderSequenceLen,
  maxDecoderSequenceLen
) {
  // Encoder model
  const encoderInput = tf.input({ shape: [null], name: "encoderInputLayer" });
  const encoderEmbedding = tf.layers
    .embedding({
      inputDim: numEncoderTokens,
      outputDim: 300,
      inputLength: maxEncoderSequenceLen,
      name: "encoderEmbeddingLayer",
    })
    .apply(encoderInput);
  const encoderLstm = tf.layers
    .lstm({
      units: 256,
      activation: "tanh",
      returnSequences: true,
      returnState: true,
      name: "encoderLstm1Layer",
    })
    .apply(encoderEmbedding);
  const [_, state_h, state_c] = tf.layers
    .lstm({
      units: 256,
      activation: "tanh",
      returnState: true,
      name: "encoderLstm2Layer",
    })
    .apply(encoderLstm);
  const encoderStates = [state_h, state_c];

  // Decoder model
  const decoderInput = tf.input({ shape: [null], name: "decoderInputLayer" });
  const decoderEmbedding = tf.layers
    .embedding({
      inputDim: numDecoderTokens,
      outputDim: 300,
      inputLength: maxDecoderSequenceLen,
      name: "decoderEmbeddingLayer",
    })
    .apply(decoderInput);
  const decoderLstm = tf.layers.lstm({
    units: 256,
    activation: "tanh",
    returnState: true,
    returnSequences: true,
    name: "decoderLstmLayer",
  });
  const [decoderOutputs, ,] = decoderLstm.apply(decoderEmbedding, {
    initialState: encoderStates,
  });
  const decoderDense = tf.layers.dense({
    units: numDecoderTokens + 1,
    activation: "softmax",
    name: "decoderFinalLayer",
  });
  const outputs = decoderDense.apply(decoderOutputs);

  const model = tf.model({ inputs: [encoderInput, decoderInput], outputs });
  return model;
}</pre>
<h2>Modell trainieren und speichern</h2>
<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="atomic"># train model
loss = tf.losses.SparseCategoricalCrossentropy()
model.compile(optimizer='rmsprop', loss=loss, metrics=['accuracy'])
callback = tf.keras.callbacks.EarlyStopping(monitor='loss', patience=3)
history = model.fit(
   [encoder_seq, decoder_inp],
   decoder_output,
   epochs=80,  # 80
   batch_size=450,  # 450
   # callbacks=[callback]
)</pre>
<p>Die Funktion model.fit() wird zum Trainieren des Modells verwendet. Die Trainingsdaten bestehen aus den Encoder-Eingangssequenzen (<strong>encoder_seq</strong>), den Decoder-Eingangssequenzen (<strong>decoder_inp</strong>) und den Decoder-Ausgangssequenzen (<strong>decoder_output</strong>). Das Training wird für eine bestimmte Anzahl von Epochen (epochs) und eine Batchgröße von 450 durchgeführt. Der Trainingsfortschritt kann mit dem EarlyStopping-Callback überwacht werden, der das Training abbricht, wenn sich der Verlust nach einer bestimmten Anzahl von Epochen nicht verbessert hat. Der Trainingsverlauf wird in der Variable history gespeichert.</p>
<p>Das Modell in Tensorflow kann sowohl Tensoren als auch Numpy-Arrays als Eingaben verarbeiten. Wenn man Numpy-Arrays als Eingaben an die Funktion <strong>fit</strong> in TensorFlow übergibt, konvertiert diese sie intern automatisch in Tensoren, bevor das Training durchgeführt wird. Im Code werden die <strong>encoder_seq</strong>, <strong>decoder_inp</strong> und <strong>decoder_output</strong> Arrays automatisch in Tensoren umgewandelt, wenn man sie an die <strong>fit</strong> Funktion übergibt. Dies erlaubt es TensorFlow, die notwendigen Berechnungen während des Trainingsprozesses durchzuführen.</p>
<p>In ähnlicher Weise kann die Funktion <strong>fit</strong> in TensorFlow.js sowohl mit Tensoren als auch mit Arrays umgehen. Man kann also direkt sein 2D-Array (<strong>encoderSeq</strong>) als erste Eingabe übergeben und TensorFlow.js wird sie intern in Tensoren für das Training umwandeln. Obwohl man Arrays anstelle von Tensoren übergibt, sind TensorFlow und TensorFlow.js in der Lage, die Konvertierung intern zu handhaben und das Training entsprechend durchzuführen.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="atomic"># save model
model.save("./model-experimental/Translate_Eng_FR.h5")
model.save_weights("./model-experimental/model_NMT")</pre>
<p>Es ist üblich, die Gewichte eines trainierten Modells getrennt von der Modellarchitektur zu speichern. Die separate Speicherung der Gewichte und der Architektur ermöglicht mehr Flexibilität beim Laden und Verwenden des Modells. So kann man beispielsweise nur die Gewichte laden, wenn die Modellarchitektur an anderer Stelle definiert wurde oder wenn die Gewichte in einem anderen Modell mit einer ähnlichen Architektur verwenden werden sollen.</p>
<p>Abschließend auch der Code in JavaScript:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">async function trainModel(data) {
  const encoderSeq = padSequences(data.en);
  const decoderInp = padSequences(data.de.map((arr) =&gt; arr.slice(0, -1))); // Has startToken
  const decoderOutput = padSequences(data.de.map((arr) =&gt; arr.slice(1))); // Has endToken

  data.model.compile({
    optimizer: "rmsprop",
    loss: "sparseCategoricalCrossentropy",
    metrics: ["accuracy"],
  });
  const history = await data.model.fit(
    [encoderSeq, decoderInp],
    decoderOutput,
    {
      epochs: 80,
      batch_size: 450,
    }
  );
}</pre>
<p>An dieser Stelle kommt meine Frustration mit Tensorflow.js ins Spiel. Obwohl jeder Schritt 1:1 dem Schritt in Python entspricht klappt das Training des Modells in Tensorflow.js nicht&#8230; Ich erhalte immer eine Fehlermeldung:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="atomic">C:\Users\[...]\node_modules\@tensorflow\tfjs-layers\dist\tf-layers.node.js:23386
            if (array.shape.length !== shapes[i].length) {
                            ^

TypeError: Cannot read properties of undefined (reading 'length')
    at standardizeInputData</pre>
<h2>Allgemein Verlustfunktion und Optimierer</h2>
<p>Verlustfunktionen und Optimierer sind Schlüsselkomponenten beim Training eines maschinellen Lernmodells. Eine Verlustfunktion, die auch als Ziel- oder Kostenfunktion bezeichnet wird, misst die Leistung eines Modells, indem sie die Unähnlichkeit zwischen den vorhergesagten Ausgaben und den tatsächlichen Zielen quantifiziert. Das Ziel des Trainings eines Modells ist es, diese Verlustfunktion zu minimieren, was im Wesentlichen bedeutet, dass die Fähigkeit des Modells, genaue Vorhersagen zu treffen, verbessert wird. Die Wahl der Verlustfunktion hängt von der jeweiligen Problemstellung ab. Bei Klassifizierungsaufgaben sind beispielsweise die kategoriale Kreuzentropie, die binäre Kreuzentropie und die Softmax-Kreuzentropie gängige Verlustfunktionen, während bei Regressionsaufgaben häufig der mittlere quadratische Fehler (<a href="https://de.wikipedia.org/wiki/Mittlere_quadratische_Abweichung" target="_blank" rel="noopener">MSE</a>) und der mittlere absolute Fehler (MAE) verwendet werden.</p>
<p>Ein Optimierer hingegen ist für die Aktualisierung der Modellparameter (Gewichte und Verzerrungen) während des Trainings verantwortlich, um die Verlustfunktion zu minimieren. Er bestimmt, wie die Parameter des Modells auf der Grundlage der berechneten Gradienten der Verlustfunktion in Bezug auf diese Parameter angepasst werden. Optimierer verwenden verschiedene Algorithmen und Techniken, um effizient nach den optimalen Werten der Parameter zu suchen. Zu den gängigen Optimierern gehören Stochastic Gradient Descent (SGD), Adam, RMSprop und Adagrad. Jeder Optimierer hat seine eigenen Merkmale und Hyperparameter, die den Trainingsprozess und die Konvergenzgeschwindigkeit des Modells beeinflussen können.</p>
<p>Die Wahl der Verlustfunktion und des Optimierers hängt von der spezifischen Aufgabe, der Modellarchitektur und den Eigenschaften des Datensatzes ab. Es ist wichtig, geeignete Verlustfunktionen und Optimierer auszuwählen, um ein effektives Modelltraining und eine Konvergenz zur optimalen Leistung zu gewährleisten.</p>
<h2>Häufig verwendete Verlustfunktionen und Optimierer</h2>
<h4>Verlustfunktionen:</h4>
<ul>
<li>Kategoriale Kreuz-Entropie: Diese Verlustfunktion wird häufig in Sequenz-zu-Sequenz-Modellen für Mehrklassen-Klassifizierungsprobleme verwendet, bei denen jedes Zielwort als eine eigene Klasse behandelt wird.</li>
<li> Spärliche kategoriale Kreuzentropie: Ähnlich wie die kategoriale Kreuzentropie, aber geeignet, wenn die Zielsequenzen als spärliche ganzzahlige Sequenzen dargestellt werden (z. B. unter Verwendung von Wortindizes).</li>
</ul>
<h4>Optimierer:</h4>
<ul>
<li>Adam: Adam ist ein beliebter Optimierer, der die Vorteile des Adaptiven Gradientenalgorithmus (AdaGrad) und der Root Mean Square Propagation (RMSprop) kombiniert. Er passt die Lernrate für jeden Parameter auf der Grundlage früherer Gradienten an, was zu einer schnelleren Konvergenz und einer besseren Handhabung spärlicher Gradienten beiträgt.</li>
<li>RMSprop: RMSprop ist ein Optimierer, der einen gleitenden Durchschnitt der quadrierten Gradienten für jeden Parameter beibehält. Er passt die Lernrate auf der Grundlage der Größe des Gradienten an, was eine schnellere Konvergenz und eine bessere Leistung bei nicht stationären Zielen ermöglicht.</li>
<li>Adagrad: Adagrad passt die Lernrate individuell für jeden Parameter an, basierend auf der historischen Gradientenakkumulation. Es führt größere Updates für seltene Parameter und kleinere Updates für häufige Parameter durch.</li>
</ul>
<h2>Dateien zum Herunterladen</h2>
<ul>
<li><a  data-e-Disable-Page-Transition="true" class="download-link" title="" href="https://nerd-corner.com/de/download/1432/?tmstv=1756332070" rel="nofollow" id="download-link-1432" data-redirect="false" >
	NLP Tensorflow.js code (model has an error!)</a>
</li>
</ul>
<p>The post <a href="https://nerd-corner.com/de/nlp-anwendung-tensorflow-js-vs-tensorflow-python-teil-2/">NLP Anwendung: Tensorflow.js vs Tensorflow Python &#8211; Teil 2</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://nerd-corner.com/de/nlp-anwendung-tensorflow-js-vs-tensorflow-python-teil-2/feed/</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>NLP Anwendung: Tensorflow.js vs Tensorflow Python &#8211; Teil 1</title>
		<link>https://nerd-corner.com/de/nlp-anwendung-tensorflow-js-vs-tensorflow-python-teil-1/</link>
					<comments>https://nerd-corner.com/de/nlp-anwendung-tensorflow-js-vs-tensorflow-python-teil-1/#respond</comments>
		
		<dc:creator><![CDATA[Nerds]]></dc:creator>
		<pubDate>Wed, 31 May 2023 17:01:38 +0000</pubDate>
				<category><![CDATA[Software-DE]]></category>
		<category><![CDATA[CSV Datensatz]]></category>
		<category><![CDATA[CSV einlesen]]></category>
		<category><![CDATA[Decoder]]></category>
		<category><![CDATA[Encoder]]></category>
		<category><![CDATA[GPU]]></category>
		<category><![CDATA[hidden state]]></category>
		<category><![CDATA[JSON]]></category>
		<category><![CDATA[Keras]]></category>
		<category><![CDATA[Long short term memory]]></category>
		<category><![CDATA[loss function]]></category>
		<category><![CDATA[LSTM]]></category>
		<category><![CDATA[Map]]></category>
		<category><![CDATA[natural Bibliothek]]></category>
		<category><![CDATA[Natural Language Processing]]></category>
		<category><![CDATA[NLP]]></category>
		<category><![CDATA[NLP Anwendung]]></category>
		<category><![CDATA[OOV]]></category>
		<category><![CDATA[OOV Token]]></category>
		<category><![CDATA[optimizer]]></category>
		<category><![CDATA[Pad sequences]]></category>
		<category><![CDATA[Padding]]></category>
		<category><![CDATA[rmsprop]]></category>
		<category><![CDATA[Tensorflow]]></category>
		<category><![CDATA[Tensorflow Anleitung]]></category>
		<category><![CDATA[TensorFlow GPU]]></category>
		<category><![CDATA[Tensorflow python]]></category>
		<category><![CDATA[Tensorflow.js]]></category>
		<category><![CDATA[Tensorflow.js vs Tensorflow]]></category>
		<category><![CDATA[Tfjs]]></category>
		<category><![CDATA[Tokenisierung]]></category>
		<category><![CDATA[Txt Datensatz]]></category>
		<category><![CDATA[WordTokenizer]]></category>
		<guid isPermaLink="false">https://nerd-corner.com/?p=1387</guid>

					<description><![CDATA[<p>Ich arbeite aktuell an einem Projekt bei dem ich einen Deutsch zu Bairisch Übersetzer mittels Machine Learning programmieren will. Man bezeichnet das als Natural Language &#8230; </p>
<p>The post <a href="https://nerd-corner.com/de/nlp-anwendung-tensorflow-js-vs-tensorflow-python-teil-1/">NLP Anwendung: Tensorflow.js vs Tensorflow Python &#8211; Teil 1</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Ich arbeite aktuell an einem Projekt bei dem ich einen Deutsch zu Bairisch Übersetzer mittels Machine Learning programmieren will. Man bezeichnet das als Natural Language Processing (NLP). Häufig wird für die Umsetzung eine Google Library namens Tensorflow benutzt. Es existiert sowohl Tensorflow.js als auch Tensorflow (Python). Da ich beruflich mit Angular entwickle und daher mit TypeScript bzw. JavaScript vertraut bin, habe ich mich anfangs direkt für die NLP Anwendung in Tensorflow.js entschieden. Ich war so naiv anzunehmen, dass der einzige Unterschied zwischen den beiden Libraries die verwendete Programmiersprache wäre. Das ist definitiv nicht der Fall! Für mein NLP Projekt fehlen teilweise grundlegende Funktionen in Tensorflow.js (wie beispielsweise ein Tokenizer). <a href="https://nerd-corner.com/de/tensorflow-js-oder-tensorflow-nutzen/">In diesem Beitrag habe ich die allgemeinen Unterschiede zwischen Tensorflow.js und Tensorflow (Python) erklärt.</a></p>
<p>Ich habe viele Abende damit verbracht mein Projekt mit Tensorflow.js zum Laufen zu bringen und bin am Ende gescheitert. Der Wechsel auf Python brachte dann den erhofften Durchbruch! Ich würde jedem empfehlen für NLP Anwendungen Python zu nutzen! Nichtsdestotrotz möchte ich in diesem Beitrag einmal die Unterschiede zwischen Tensorflow.js und Tensorflow im Bezug auf mein Projekt anhand von Codebeispielen verdeutlichen. Zwischendurch werde ich auch so gut wie möglich mein neu angesammeltes Wissen in die jeweiligen Abschnitte einbauen.</p>
<p><em><strong>Das könnte dich auch interessieren:</strong> <a href="https://nerd-corner.com/de/nlp-anwendung-tensorflow-js-vs-tensorflow-python-teil-2/">NLP Anwendung Teil 2 (OOV Token, Padding, Modell erstellen und Modell trainieren)</a></em></p>
<h2>Daten einlesen</h2>
<p>Zunächst einmal braucht man einen Datensatz mit dem später das Modell trainiert werden soll. Hier kann ich <a href="https://www.kaggle.com/">https://www.kaggle.com/</a> empfehlen. Man findet dort eine Vielzahl an Datensätzen zur freien Verfügung und teilweise sogar weiterführende Codebeispiele. Den Datensatz kann man entweder per Link einlesen oder downloaden und dann lokal vom File System einlesen. Ein guter Datensatz sollte über 100.000 Beispiele enthalten. Am besten auch teilweise ganze Paragraphen. So sieht beispielsweise ein Englisch/Französischer Datensatz als CSV aus:</p>
<p><img decoding="async" class="aligncenter wp-image-1389 zoooom" src="https://nerd-corner.com/wp-content/uploads/2023/05/sample-dataset-tensorflow.png" alt="Sample dataset tensorflow CSV" width="1380" height="332" srcset="https://nerd-corner.com/wp-content/uploads/2023/05/sample-dataset-tensorflow.png 1393w, https://nerd-corner.com/wp-content/uploads/2023/05/sample-dataset-tensorflow-300x72.png 300w, https://nerd-corner.com/wp-content/uploads/2023/05/sample-dataset-tensorflow-1024x246.png 1024w, https://nerd-corner.com/wp-content/uploads/2023/05/sample-dataset-tensorflow-768x185.png 768w" sizes="(max-width: 1380px) 100vw, 1380px" /></p>
<p>Zunächst die simple Variante mittels Python:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-title="Read in CSV" data-enlighter-theme="atomic">import pandas as pd

# read in dataSet for training
df = pd.read_csv("./dataset/eng_-french.csv")
df.columns = ["english", "french"]
print(df.head())
print(df.info())</pre>
<p>Wir nutzen die pandas Library und lesen damit die CSV ein. Mit dem head() können wir testen ob es funktioniert hat und uns die ersten 5 Zeilen anzeigen lassen. Mittels info() erhalten wir weitere Infos wie beispielsweise Anzahl der Spalten und Anzahl der Reihen:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1388 zoooom" src="https://nerd-corner.com/wp-content/uploads/2023/05/output-read-in-CSV-python.png" alt="output CSV read in with python pandas lib" width="575" height="516" srcset="https://nerd-corner.com/wp-content/uploads/2023/05/output-read-in-CSV-python.png 583w, https://nerd-corner.com/wp-content/uploads/2023/05/output-read-in-CSV-python-300x269.png 300w" sizes="auto, (max-width: 575px) 100vw, 575px" /></p>
<p>Zum Vergleich in Tensorflow.js (Tfjs) gibt es auch eine Möglichkeit CSV einzulesen:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-title="index.js" data-enlighter-theme="atomic">const tf = require("@tensorflow/tfjs");

async function readInData() {
  await tf.ready();
  const languageDataSet = tf.data.csv("file://" + "./ger_en_trans.csv");

  // Extract language pairs
  const dataset = languageDataSet.map((record) =&gt; ({
    en: record.en,
    de: record.de,
  }));

  const pairs = await dataset.toArray();

  console.log(pairs);
}

readInData();</pre>
<p>Ich habe dabei zunächst versucht den gleichen Datensatz wie in der Python Version einzulesen:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1408 zoooom" src="https://nerd-corner.com/wp-content/uploads/2023/05/Tensorflow.js-ReadIn-Same-CSV.png" alt="read in same CSV as in tf" width="745" height="220" srcset="https://nerd-corner.com/wp-content/uploads/2023/05/Tensorflow.js-ReadIn-Same-CSV.png 758w, https://nerd-corner.com/wp-content/uploads/2023/05/Tensorflow.js-ReadIn-Same-CSV-300x89.png 300w" sizes="auto, (max-width: 745px) 100vw, 745px" /></p>
<p>Anschließend wollte ich die Überschriften kürzen in der originalen CSV, dadurch bekam ich aber seltsamer Weise eine Fehlermeldung beim Einlesen. Selbst als ich den Originalzustand der CSV wiederhergestellt hatte, blieb der Fehler:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1407 zoooom" src="https://nerd-corner.com/wp-content/uploads/2023/05/read-in-modified-CSV-before-it-breaks-tfjs.png" alt="error when reading in CSV" width="610" height="298" srcset="https://nerd-corner.com/wp-content/uploads/2023/05/read-in-modified-CSV-before-it-breaks-tfjs.png 620w, https://nerd-corner.com/wp-content/uploads/2023/05/read-in-modified-CSV-before-it-breaks-tfjs-300x147.png 300w" sizes="auto, (max-width: 610px) 100vw, 610px" /></p>
<p>Letztlich habe ich mich dann dafür entschieden einen anderen Datensatz zu nutzen:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1406 zoooom" src="https://nerd-corner.com/wp-content/uploads/2023/05/CSV-sample-screenshot-en-de.png" alt="other CSV data samples" width="375" height="356" srcset="https://nerd-corner.com/wp-content/uploads/2023/05/CSV-sample-screenshot-en-de.png 389w, https://nerd-corner.com/wp-content/uploads/2023/05/CSV-sample-screenshot-en-de-300x285.png 300w" sizes="auto, (max-width: 375px) 100vw, 375px" /></p>
<p>Der war beim Einlesen auch wesentlich besser lesbar:</p>
<p><img loading="lazy" decoding="async" class="zoooom aligncenter wp-image-1405" src="https://nerd-corner.com/wp-content/uploads/2023/05/reading-in-the-german-english-CSV.png" alt="NLP Anwendung Tensorflow.js" width="825" height="276" srcset="https://nerd-corner.com/wp-content/uploads/2023/05/reading-in-the-german-english-CSV.png 836w, https://nerd-corner.com/wp-content/uploads/2023/05/reading-in-the-german-english-CSV-300x100.png 300w, https://nerd-corner.com/wp-content/uploads/2023/05/reading-in-the-german-english-CSV-768x257.png 768w" sizes="auto, (max-width: 825px) 100vw, 825px" /></p>
<p>Und hier das Endergebnis nach dem Mapping:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1404 zoooom" src="https://nerd-corner.com/wp-content/uploads/2023/05/result-data-read-in-tfjs.png" alt="after mapping csv" width="515" height="99" srcset="https://nerd-corner.com/wp-content/uploads/2023/05/result-data-read-in-tfjs.png 528w, https://nerd-corner.com/wp-content/uploads/2023/05/result-data-read-in-tfjs-300x58.png 300w" sizes="auto, (max-width: 515px) 100vw, 515px" /></p>
<p>Obwohl Tfjs extra eine Funktion zum Einlesen der CSV bietet, hatte ich trotzdem schon mehr Ärger als in der Python Version. Ich habe auch auf die Schnelle keine Möglichkeit gefunden einen Datensatz im txt-Format einzulesen. Allerdings sind txt-Datensätze weit verbreitet!</p>
<h2>Daten vorbereiten</h2>
<p>Ich habe oft gesehen, dass für die Datenvorbereitung eine cleaning Funktion geschrieben wurde und der Output Satz auch einen Start und End Token erhalten hat. Daraufhin habe ich mich gefragt ob auch der Input Satz also der Encoder einen Start und End Token braucht. Im Rahmen von Sequenz-zu-Sequenz-Modellen benötigt der Encoder aber keine expliziten Start- und End-Token. Sein Zweck ist es, die Eingabesequenz so zu verarbeiten, wie sie ist, und eine Darstellung der Eingabe zu erstellen.</p>
<p>Der Decoder hingegen, der die Ausgabesequenz erzeugt, profitiert in der Regel von der Verwendung von Start- und End-Tokens. Diese Token helfen, den Anfang und das Ende der erzeugten Sequenz zu kennzeichnen. Die Verwendung von Start- und End-Token ist daher spezifisch für den Decoder. Während des Trainings enthält die Eingangssequenz des Decoders ein Start-Token am Anfang und schließt ein End-Token am Ende aus. Die Ausgabesequenz des Decoders enthält den End-Token und schließt den Start-Token aus. Auf diese Weise lernt das Modell, die richtige Ausgabesequenz auf der Grundlage der Eingabe zu erzeugen.</p>
<p>Bei der Erstellung von Übersetzungen mit dem trainierten Modell beginnt man mit dem Start-Token und generiert ein Token nach dem anderen, bis man auf den End-Token trifft oder eine maximale Sequenzlänge erreicht. Das Hinzufügen von Start- und End-Token zum Decoder-Satz verbessert die Leistung des NLP-Übersetzermodells. Es hilft bei der Festlegung klarer Sequenzgrenzen und unterstützt den Generierungsprozess, indem es angibt, wo die Übersetzung beginnt und endet.</p>
<h4>Zusammengefasst:</h4>
<ul>
<li>Encoder: Keine Notwendigkeit für Start- und End-Token. Verarbeitet die Eingabesequenz wie sie ist.</li>
<li>Decoder: Start- und End-Token sind hilfreich für die Generierung der Ausgabesequenz.</li>
</ul>
<p>Wir beginnen wieder mit dem einfachen Teil, nämlich Python. Wie wollen die eingelesenen Daten säubern. Das bedeutet alles in Kleinbuchstaben umwandeln und Zeichen, die nicht zum Alphabet gehören oder Satzzeichen sind, entfernen. Dafür brauchen wir die Regex Bibliothek (re).</p>
<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="atomic">import re

def clean(text):
    text = text.lower()  # lower case
    # remove any characters not a-z and ?!,'
    # please note that french has additional characters...I just simplified that
    text = re.sub(u"[^a-z!?',]", " ", text)
    return text


# apply cleaningFunctions to dataframe
data["english"] = data["english"].apply(lambda txt: clean(txt))
data["french"] = data["french"].apply(lambda txt: clean(txt))

# add &lt;start&gt; &lt;end&gt; token to decoder sentence (french)
data["french"] = data["french"].apply(lambda txt: f"&lt;start&gt; {txt} &lt;end&gt;")

print(data.sample(10))</pre>
<p>Ich habe hier vereinfacht. Da es sich um einen französischen Datensatz handelt sollte man eigentlich eine extra cleaning Funktion schreiben, die auch französische Buchstaben wie „ê“ berücksichtigt. Die  sample() Funktion dient nur zum veranschaulichen der Daten:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1391 zoooom" src="https://nerd-corner.com/wp-content/uploads/2023/05/random-sample-of-cleaned-data.png" alt="tensorflow random sample of cleaned data" width="630" height="385" srcset="https://nerd-corner.com/wp-content/uploads/2023/05/random-sample-of-cleaned-data.png 639w, https://nerd-corner.com/wp-content/uploads/2023/05/random-sample-of-cleaned-data-300x184.png 300w" sizes="auto, (max-width: 630px) 100vw, 630px" /></p>
<p>In Tfjs ist der Ablauf absolut identisch. Ich habe eine cleanData() Funktion erstellt und den vorherigen Code modifiziert:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">function cleanData(text) {
  //if necessary also remove any characters not a-z and ?!,'
  return text.toLowerCase();
}</pre>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">const dataset = languageDataSet.map((record) =&gt; ({
   en: cleanData(record.en),
   de: "startToken " + cleanData(record.de) + " endToken",
 }));</pre>
<p>Das Ergebnis ist daher auch identisch zum Python Ansatz:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1410 zoooom" src="https://nerd-corner.com/wp-content/uploads/2023/05/cleaned-up-tfjs-data.png" alt="cleaned up tfjs data" width="630" height="146" srcset="https://nerd-corner.com/wp-content/uploads/2023/05/cleaned-up-tfjs-data.png 639w, https://nerd-corner.com/wp-content/uploads/2023/05/cleaned-up-tfjs-data-300x69.png 300w" sizes="auto, (max-width: 630px) 100vw, 630px" /></p>
<p>Wenn die Wörter &#8222;start&#8220; und &#8222;end&#8220; Teil regulärer Sätze sind und nicht als spezielle Token zur Markierung von Anfang und Ende von Sequenzen verwendet werden, dann sollten sie bei der Tokenisierung auf keinen Fall durch entsprechende Indizes ersetzt werden. Bei der Tokenisierung ist es wichtig, spezielle Token zu wählen, die in den eigentlichen Eingabedaten wahrscheinlich nicht vorkommen werden. Dadurch wird sichergestellt, dass das Modell sie von normalen Wörtern unterscheiden kann und lernt, die entsprechenden Ausgabesequenzen zu erzeugen.</p>
<p>Wenn die Wörter &#8220; start&#8220; und &#8222;end&#8220; reguläre Wörter in den Eingabesätzen sind, sollte man in Erwägung ziehen, verschiedene spezielle Token zu verwenden, um den Anfang und das Ende von Sequenzen zu markieren. Eine gängige Wahl sind &#8220; &lt;start&gt;&#8220; und &#8222;&lt;end&gt;&#8220;. Durch die Verwendung spezieller Token, die wahrscheinlich nicht zum regulären Vokabular gehören, kann sichergestellt werden, dass sie vom Modell während des Trainings und der Generierung richtig identifiziert und verarbeitet werden können.</p>
<h5>Beispielsweise würden die tokenisierten Sequenzen wie folgt aussehen:</h5>
<p>Decoder Eingabe: [&#8222;&lt;start&gt;&#8220;, &#8222;hallo&#8220;, &#8222;welt&#8220;]<br />
Decoder Ausgabe: [&#8222;hallo&#8220;, &#8222;welt&#8220;, &#8222;&lt;end&gt;&#8220;]</p>
<h5>Daher nachfolgendes VERMEIDEN:</h5>
<p>Decoder Eingabe: [&#8222;start&#8220;, &#8222;hallo&#8220;, &#8222;welt&#8220;]<br />
Decoder Ausgabe: [&#8222;hallo&#8220;, &#8222;welt&#8220;, &#8222;end&#8220;]</p>
<h2>Tokenisierung</h2>
<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="atomic"># tokenization
import tensorflow as tf
from tensorflow import keras
from keras.preprocessing.text import Tokenizer
import numpy as np

# english tokenizer
english_tokenize = Tokenizer(filters='#$%&amp;()*+,-./:;&lt;=&gt;@[\\]^_`{|}~\t\n')
english_tokenize.fit_on_texts(data["english"])
num_encoder_tokens = len(english_tokenize.word_index)+1
# print(num_encoder_tokens)
encoder = english_tokenize.texts_to_sequences(data["english"])
# print(encoder[:5])
max_encoder_sequence_len = np.max([len(enc) for enc in encoder])
# print(max_encoder_sequence_len)

# french tokenizer
french_tokenize = Tokenizer(filters="#$%&amp;()*+,-./:;&lt;=&gt;@[\\]^_`{|}~\t\n")
french_tokenize.fit_on_texts(data["french"])
num_decoder_tokens = len(french_tokenize.word_index)+1
# print(num_decoder_tokens)
decoder = french_tokenize.texts_to_sequences(data["french"])
# print(decoder[:5])
max_decoder_sequence_len = np.max([len(dec) for dec in decoder])
# print(max_decoder_sequence_len)

</pre>
<p>Dieser Code führt Tokenisierung und Sequenzvorverarbeitung mit der <strong>Tokenizer</strong> Klasse in TensorFlow durch.</p>
<ol>
<li><strong>english_tokenize = Tokenizer(filters=&#8217;#$%&amp;()*+,-./:;&lt;=&gt;@[\\]^_`{|}~\t\n&#8216;)</strong> Initialisiert ein Tokenizer-Objekt für englische Sätze. Der Parameter `filters` gibt Zeichen an, die während der Tokenisierung herausgefiltert werden sollen. Wir haben die Daten im Cleaning Prozess bereits gefiltert, es ist daher eigentlich nicht notwendig hier nochmal zu filtern.</li>
<li><strong>english_tokenize.fit_on_texts(data[&#8222;english&#8220;])</strong> Aktualisiert das interne Vokabular des Tokenizers basierend auf den englischen Sätzen in der Variable <strong>data</strong>. Jedem Wort im Vokabular wird ein eindeutiger Index zugewiesen.</li>
<li><strong>num_encoder_tokens = len(english_tokenize.word_index) + 1</strong> Bestimmt die Anzahl der eindeutigen Token (Wörter) im englischen Vokabular. Das Attribut <strong>word_index</strong> des Tokenizers gibt ein Wörterbuch zurück, das Wörter auf ihre jeweiligen Indizes abbildet.</li>
<li><strong>encoder = english_tokenize.texts_to_sequences(data[&#8222;english&#8220;])</strong> Konvertiert die englischen Sätze in der Variablen <strong>data</strong> in Sequenzen von Token-Indizes unter Verwendung des Tokenizers. Jeder Satz wird durch eine Folge von Ganzzahlen ersetzt, die die entsprechenden Wörter darstellen.</li>
<li><strong>max_encoder_sequence_len = np.max([len(enc) for enc in encoder])</strong> Berechnet die maximale Länge (Anzahl der Token) unter allen kodierten Sequenzen. Sie verwendet die Funktion <strong>max</strong> von NumPy, um den maximalen Wert in einem Listenverständnis zu finden.</li>
</ol>
<p>Diese Schritte helfen, die Sätze für die weitere Verarbeitung in einem NLP-Modell vorzubereiten. Das ist für beide Sprachen notwendig!</p>
<p>Die Sätze wurden jetzt in Token umgewandelt, anschließend in Sequenzen von Token-Indizes konvertiert und die maximale Sequenzlänge bestimmt. Ein Beispielsatz aus dem Datensatz sieht jetzt so aus: [[148], [252], [59], [14], [111]]. Hierbei könnte die 148 für „I“, 252 für „am“, 59 für „very“, 14 für „hungry“ und 111 für „now“ stehen.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="atomic">idx_2_txt_decoder = {k: i for i, k in french_tokenize.word_index.items()}
# print(idx_2_txt_decoder)
idx_2_txt_encoder = {k: i for i, k in english_tokenize.word_index.items()}
# print(idx_2_txt_encoder)

idx_2_txt_decoder[0] = "&lt;pad&gt;"
idx_2_txt_encoder[0] = "&lt;pad&gt;"</pre>
<p>Der Codeschnipsel <strong>idx_2_txt_encoder = {k: i for i, k in english_tokenize.word_index.items()} </strong>erstellt ein Wörterbuch Verzeichnis <strong>idx_2_txt_encoder</strong>, das Token-Indizes den entsprechenden Wörtern im englischen Vokabular zuordnet: <strong>{k: i for i, k in english_tokenize.word_index.items()}</strong> ist ein Verzeichnis, das über die Schlüssel-Wert-Paare in <strong>english_tokenize.word_index</strong> iteriert. Bei jeder Iteration steht der Key (<strong>k</strong>) für ein Wort im Vokabular, und der Wert (<strong>i</strong>) für den entsprechenden Index. Das Verständnis erstellt ein neues Wörterbuch, dessen Keys die Indizes (<strong>i</strong>) und die Werte die Wörter (<strong>k</strong>) sind.</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1395 zoooom" src="https://nerd-corner.com/wp-content/uploads/2023/05/index2textencoder-printed.png" alt="index 2 tokenizer dicitonary sample" width="1730" height="253" srcset="https://nerd-corner.com/wp-content/uploads/2023/05/index2textencoder-printed.png 1742w, https://nerd-corner.com/wp-content/uploads/2023/05/index2textencoder-printed-300x44.png 300w, https://nerd-corner.com/wp-content/uploads/2023/05/index2textencoder-printed-1024x150.png 1024w, https://nerd-corner.com/wp-content/uploads/2023/05/index2textencoder-printed-768x112.png 768w, https://nerd-corner.com/wp-content/uploads/2023/05/index2textencoder-printed-1536x225.png 1536w" sizes="auto, (max-width: 1730px) 100vw, 1730px" /></p>
<p>Das resultierende <strong>idx_2_txt_encoder </strong>&#8211; Wörterbuch ermöglicht es, das Wort, das einem bestimmten Index entspricht, im englischen Wortschatz nachzuschlagen. <strong>english_tokenize.word_index </strong>würde übrigens die Anzeigen genau vertauschen. Hier wäre der Key das Wort sein und der Wert der Index. Die zweite Zeile, <strong>idx_2_txt_encoder[0] = &#8222;&lt;pad&gt;&#8220;</strong>, fügt dem Wörterbuch einen speziellen Eintrag hinzu. Hier wird das Wort &#8222;&lt;pad&gt;&#8220; dem Index &#8222;0&#8220; zugeordnet, um einen Auffüllungs-Token anzugeben, der beim Padding von Sequenzen verwendet wird.</p>
<p>Anschließend sollte man das Wörterbuch Verzeichnis abspeichern, denn später wenn das Modell trainiert wurde und eingesetzt wird, werden die Übersetzungen des Modells ebenfalls eine Reihe von Indizes sein, die mit Hilfe des Wörterbuchs in lesbare Sätze zurücktransformiert werden.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="python" data-enlighter-theme="atomic"># Saving the dicitionaries
pickle.dump(idx_2_txt_encoder, open("./saves/idx_2_word_input.txt", "wb"))
pickle.dump(idx_2_txt_decoder, open("./saves/idx_2_word_target.txt", "wb"))</pre>
<p>Der gleiche Ablauf wie in Python lässt sich auch für die NLP Anwendung in Tensorflow.js konstruieren. Selbstverständlich benötigt man etwas mehr Codezeilen und der Arbeitsaufwand ist insgesamt höher. Die erste Hürde hierbei ist der Tokenizer. Leider besitzt Tfjs im Gegensatz zu Tensorflow (Python) keinen eigenen Tokenizer. Nach ausgiebiger Recherche fand ich zum Glück den natural.WordTokenizer. Hierbei möchte ich darauf hinweisen, dass definitv ein Node.js Projekt benötigt wird. Tfjs lässt sich zwar über einen &lt;script&gt; Tag einbinden, der natural.WordTokenizer, dagegen aber nicht!</p>
<p>Ein weiterer wichtiger Punkt ist, dass der WordTokenizer &#8222;&lt;&#8220; und &#8222;&gt;&#8220; entfernt. Aus einem Output Satz &#8222;&lt;start&gt; ich esse &lt;end&gt;&#8220; wird daher einfach [&#8217;start&#8216;, &#8218;ich&#8216;, &#8218;esse&#8216;, &#8218;end&#8216;]. Somit ist der &#8222;&lt;start&gt;&#8220; und &#8222;&lt;end&gt;&#8220; Token nicht mehr klar erkennbar! Ich habe sie daher im JS Code von Anfang ersetzt durch &#8222;startToken&#8220; und &#8222;endToken&#8220;.</p>
<p>Zunächst tokenisieren wir wieder jeden einzelnen Satz aus dem Datensatz und erstellen anschließend ein Vokabelverzeichnis für jede der beiden Sprachen. Abschließend ersetzen wir alle Wörter durch die Indizes aus dem Vokabelverzeichnis:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">const natural = require("natural");

function tokenize(data) {
  const tokenizer = new natural.WordTokenizer();

  enData = data.map((row) =&gt; tokenizer.tokenize(row.en));
  deData = data.map((row) =&gt; tokenizer.tokenize(row.de));

  const enVocabulary = new Map();
  const deVocabulary = new Map();

  // Insert &lt;pad&gt; at index 0
  enVocabulary.set("&lt;pad&gt;", 0);
  deVocabulary.set("&lt;pad&gt;", 0);

  const fillVocabulary = (langData, vocabMap) =&gt; {
    langData.forEach((sentence) =&gt; {
      sentence.forEach((word) =&gt; {
        if (!vocabMap.has(word)) {
          const newIndex = vocabMap.size;
          vocabMap.set(word, newIndex);
        }
      });
    });
  };

  fillVocabulary(enData, enVocabulary);
  fillVocabulary(deData, deVocabulary);

  // Replace words with indexes
  const indexedEnData = enData.map((element) =&gt;
    element.map((word) =&gt; enVocabulary.get(word))
  );
  const indexedDeData = deData.map((element) =&gt;
    element.map((word) =&gt; deVocabulary.get(word))
  );

  return { en: indexedEnData, de: indexedDeData };
}</pre>
<p>Um später die Resultate unseres Modells wieder in Wörter umwandeln zu können und um auch später im realen Anwendungsfall das Modell nutzen zu können, speichern wir die beiden Vokabelverzeichnisse ab. Ich habe dabei die Key und Value Paare getauscht, aber letztlich ist das nicht zwingend erforderlich:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="js" data-enlighter-theme="atomic">const fs = require("fs");

// store the input and output key value pairs
  fs.writeFileSync(
    "vocabulary/inputVocableSet.json",
    JSON.stringify(switchKeysAndValues(Object.fromEntries(enVocabulary)))
  );
  fs.writeFileSync(
    "vocabulary/outputVocableSet.json",
    JSON.stringify(switchKeysAndValues(Object.fromEntries(deVocabulary)))
  );

function switchKeysAndValues(obj) {
  const switchedObj = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      const value = obj[key];
      switchedObj[value] = key;
    }
  }
  return switchedObj;
}</pre>
<p>Als Resultat erhalten wir ein JSON Objekt mit unseren Vokabeln:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1411 zoooom" src="https://nerd-corner.com/wp-content/uploads/2023/06/excerpt-of-the-stored-json-map.png" alt="excerpt of the stored json map" width="240" height="553" srcset="https://nerd-corner.com/wp-content/uploads/2023/06/excerpt-of-the-stored-json-map.png 249w, https://nerd-corner.com/wp-content/uploads/2023/06/excerpt-of-the-stored-json-map-130x300.png 130w" sizes="auto, (max-width: 240px) 100vw, 240px" /></p>
<p>Anschließend returnen wir das Ergebnis unserer Funktion:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1412 zoooom" src="https://nerd-corner.com/wp-content/uploads/2023/06/result-after-tokenization.png" alt="result after tokenization" width="890" height="679" srcset="https://nerd-corner.com/wp-content/uploads/2023/06/result-after-tokenization.png 895w, https://nerd-corner.com/wp-content/uploads/2023/06/result-after-tokenization-300x229.png 300w, https://nerd-corner.com/wp-content/uploads/2023/06/result-after-tokenization-768x586.png 768w" sizes="auto, (max-width: 890px) 100vw, 890px" /></p>
<h2>Dateien zum Herunterladen</h2>
<ul>
<li><a  data-e-Disable-Page-Transition="true" class="download-link" title="" href="https://nerd-corner.com/de/download/1432/?tmstv=1756332070" rel="nofollow" id="download-link-1432" data-redirect="false" >
	NLP Tensorflow.js code (model has an error!)</a>
</li>
</ul>
<p>The post <a href="https://nerd-corner.com/de/nlp-anwendung-tensorflow-js-vs-tensorflow-python-teil-1/">NLP Anwendung: Tensorflow.js vs Tensorflow Python &#8211; Teil 1</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://nerd-corner.com/de/nlp-anwendung-tensorflow-js-vs-tensorflow-python-teil-1/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
