<?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>JSON Archives - Nerd Corner</title>
	<atom:link href="https://nerd-corner.com/de/tag/json/feed/" rel="self" type="application/rss+xml" />
	<link>https://nerd-corner.com/de/tag/json/</link>
	<description>Craft your dreams!</description>
	<lastBuildDate>Wed, 11 Dec 2024 13:15:29 +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>JSON Archives - Nerd Corner</title>
	<link>https://nerd-corner.com/de/tag/json/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Erfahrungen aus der Praxis: NestJS auf Vercel hosten</title>
		<link>https://nerd-corner.com/de/erfahrungen-aus-der-praxis-nestjs-auf-vercel-hosten/</link>
					<comments>https://nerd-corner.com/de/erfahrungen-aus-der-praxis-nestjs-auf-vercel-hosten/#respond</comments>
		
		<dc:creator><![CDATA[Nerds]]></dc:creator>
		<pubDate>Thu, 14 Nov 2024 07:55:27 +0000</pubDate>
				<category><![CDATA[Software-DE]]></category>
		<category><![CDATA[angular]]></category>
		<category><![CDATA[Anleitung]]></category>
		<category><![CDATA[cookie-sessions]]></category>
		<category><![CDATA[CORS Fehler]]></category>
		<category><![CDATA[Cors Fehler nest]]></category>
		<category><![CDATA[Datenbank]]></category>
		<category><![CDATA[Datenbank hosten]]></category>
		<category><![CDATA[Datenbank konfigurieren]]></category>
		<category><![CDATA[Einstellungen]]></category>
		<category><![CDATA[Erfahrungsbericht]]></category>
		<category><![CDATA[express]]></category>
		<category><![CDATA[express-sessions]]></category>
		<category><![CDATA[express.js]]></category>
		<category><![CDATA[google cloud]]></category>
		<category><![CDATA[google cloud mysql]]></category>
		<category><![CDATA[Hosten]]></category>
		<category><![CDATA[internal Server Fehler Vercel]]></category>
		<category><![CDATA[Internal Server Fehler Vercel nest]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[JSON]]></category>
		<category><![CDATA[mySql]]></category>
		<category><![CDATA[Nest]]></category>
		<category><![CDATA[NestJs]]></category>
		<category><![CDATA[Redis]]></category>
		<category><![CDATA[redis session management]]></category>
		<category><![CDATA[redis session management nest.js]]></category>
		<category><![CDATA[Schritt für Schritt Anweisung]]></category>
		<category><![CDATA[Server]]></category>
		<category><![CDATA[Session Mamagement]]></category>
		<category><![CDATA[session management]]></category>
		<category><![CDATA[typescript]]></category>
		<category><![CDATA[Vercel]]></category>
		<category><![CDATA[vercel.json]]></category>
		<category><![CDATA[Visual Studio]]></category>
		<guid isPermaLink="false">https://nerd-corner.com/?p=1632</guid>

					<description><![CDATA[<p>Nachdem ich Stunden damit verbracht habe, meine NestJS-App auf Vercel zu hosten und es nicht zum Laufen brachte, dachte ich mir, dass es an der &#8230; </p>
<p>The post <a href="https://nerd-corner.com/de/erfahrungen-aus-der-praxis-nestjs-auf-vercel-hosten/">Erfahrungen aus der Praxis: NestJS auf Vercel hosten</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Nachdem ich Stunden damit verbracht habe, meine NestJS-App auf Vercel zu hosten und es nicht zum Laufen brachte, dachte ich mir, dass es an der Zeit ist, zu dokumentieren, was ich gelernt habe &#8211; nicht nur, um mir in Zukunft Zeit zu sparen, sondern hoffentlich auch, um anderen zu helfen, einige der Fallstricke zu vermeiden, in die ich geraten bin. Hier ist eine Aufschlüsselung dessen, was funktioniert hat, was nicht, und wie ich es schließlich geschafft habe, dass alles reibungslos läuft.</p>
<p><em><strong>Das könnte dich ebenfalls interessieren:</strong> <a href="https://nerd-corner.com/de/swagger-api-doku-zu-node-server-hinzufuegen/" target="_blank" rel="noopener">Swagger zu Node Server hinzufügen</a></em></p>
<h3>Schritt 1: NestJS auf Vercel hosten</h3>
<p>Das Wichtigste zuerst: Die Grundeinstellungen für die Bereitstellung auf Vercel. Vercel ist großartig für Serverless, aber die Arbeit mit NestJS benötigt ein paar Anpassungen. Die Hauptsache ist, eine vercel.json-Konfigurationsdatei einzurichten, die Vercel genau sagt, wie die App zu behandeln ist.</p>
<p>Hier ist die Endkonfiguration:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-theme="beyond">{
  "version": 2,
  "builds": [
    {
      "src": "src/main.ts",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "src/main.ts",
      "methods": [
        "GET",
        "POST",
        "PUT",
        "PATCH",
        "OPTIONS",
        "DELETE",
        "HEAD",
        "CONNECT",
        "TRACE"
      ]
    }
  ]
}
</pre>
<p>Ich habe daraufhin in Vercel folgende Fehlermeldung erhalten:</p>
<pre>This Serverless Function has crashed. Your connection is working correctly. 
Vercel is working correctly. 500: INTERNAL_SERVER_ERROR 
Code: FUNCTION_INVOCATION_FAILED ID: bom1::sgk4v-1711014022883-1e9ed54f4c37</pre>
<p>Bei einem Blick in die Logs stellte ich fest, dass die Datenbankverbindung ein Problem darstellte und außerdem die folgende Logmeldung angezeigt wurde:</p>
<pre>No exports found in module "/var/task/app-name/src/main.js".
Did you forget to export a function or a server?</pre>
<p>Es stellte sich heraus, dass ich den zweiten Teil der Fehlermeldung ignorieren und mich nur auf die Datenbankverbindung konzentrieren musste.</p>
<h3>Schritt 2: Konfigurieren der Datenbank</h3>
<p>Für meine Anwendung habe ich eine mysql-Datenbank mit mehreren Schemata verwendet. Ich habe mehrere kostenlose Angebote ausprobiert, aber sie waren nicht mit dem Ansatz mehrerer Schemata kompatibel. Daher habe ich mich für das Hosting bei Google Cloud entschieden. Ich habe es auf einen Preis von 0,01 $ pro Stunde heruntergeschraubt und das 300 $-Einsteigerangebot genutzt.</p>
<p>Um Vercel eine Verbindung zu ermöglichen, musste in der Konfiguration von Google Cloud die IP-Adresse auf <strong>0.0.0.0/0</strong> gesetzt werden, so dass die Datenbank von jeder IP-Adresse aus zugänglich war.</p>
<h3>Schritt 3: Umgang mit CORS</h3>
<p>Die CORS Fehler haben auch für einige Kopfschmerzen gesorgt. Stelle sicher, dass OPTIONS für CORS Preflight-Anfragen zugelassen wird, da Vercel eine explizite Erlaubnis für Cross-Origin-Anfragen benötigt. Am Ende musste ich eine Menge Header hinzufügen, um sicherzustellen, dass die Anfragen erlaubt waren:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="beyond">app.enableCors({
    origin: 'domain-name',
    credentials: true,
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
    allowedHeaders: [
      'Origin',
      'X-Requested-With',
      'Content-Type',
      'Accept',
      'Authorization',
    ],
  });</pre>
<h3>Schritt 4: Umstellung auf express-session und Redis für die Sitzungsverwaltung</h3>
<p>Einer der kniffligsten Teile war die Sessions zum Laufen zu bringen. Ich begann mit der <em>cookie-session</em> Bibliothek, aber Vercel ignoriert sie komplett. Nach einem Blick in die Dokumentation und einigem Ausprobieren wechselte ich zur <em>express-session</em> Bibliothek, da sie populärer ist und besser mit Vercels serverloser Umgebung funktioniert.</p>
<p>Aus irgendeinem Grund muss die Import-Syntax genau so aussehen:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="beyond">import session = require('express-session');</pre>
<p>Außerdem musste ich die Sitzungsmiddleware mit aktiviertem Vertrauensproxy konfigurieren, da Vercel Anfragen proxifiziert. So sieht die Einstellung aus:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="beyond">const expressApp = app.getHttpAdapter().getInstance();
expressApp.set('trust proxy', true);</pre>
<p>Auch die Einstellung secure: true und sameSite: &#8217;none&#8216; war wichtig, um sicherzustellen, dass Cookies über HTTPS und verschiedene Domainanfragen funktionieren!</p>
<p>Beachten, dass mit Vercel mehrere serverlose Instanzen Anfragen bearbeiten können, was bei parallelen Anfragen leider zu Sitzungskonflikten führt. Um dies zu beheben, habe ich meinen Sitzungsspeicher mit einer Redis-Instanz verbunden. Glücklicherweise war das super einfach.</p>
<p><a href="https://redis.io/">Redis</a> hält die Sitzungsdaten konsistent und vermeidet Konflikte zwischen Anfragen, insbesondere unter Last. Hier der Code:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-theme="beyond">const expressApp = app.getHttpAdapter().getInstance();
expressApp.set('trust proxy', true);

const redisClient = createClient({
    password: process.env.REDIS_PASSWORD,
    socket: {
      host: process.env.REDIS_HOST,
      port: parseInt(process.env.REDIS_PORT, 10),
    },
  });

  redisClient
    .connect()
    .catch((err) =&gt;
      console.log('Could not establish a connection with Redis: ' + err),
    );

  redisClient.on('error', (err) =&gt; console.log('Redis error: ' + err));
  redisClient.on('connect', () =&gt;
    console.log('Connected to Redis successfully'),
  );

  app.use(
    session({
      store: new RedisStore({ client: redisClient }),
      secret: process.env.COOKIE_SECRET,
      cookie: {
        secure: process.env.NODE_ENV !== 'development',
        sameSite: process.env.NODE_ENV === 'development' ? 'lax' : 'none',
        maxAge: 86400000,
      },
    }),
  );</pre>
<h3>Schritt 5: Hinzufügen von withCredentials im Frontend</h3>
<p>Dieser Schritt ist nur eine Randnotiz: Damit Session-Cookies zwischen Frontend und Backend funktionieren, muss withCredentials in den HTTP-Anfragen meines Frontends auf true gesetzt werden. Dies ermöglicht die Einbeziehung von Cookies in herkunftsübergreifende Anfragen, was wichtig ist, wenn das Frontend und das Backend getrennt gehostet werden. Ich musste sicherstellen, dass der HTTP-Client von Angular diese Einstellung aktiviert hat.</p>
<h3>Schritt 6: Schriftart hinzufügen</h3>
<p>Um Schriftdateien in Ihr NestJS-Projekt einzubinden, muss man die CompilerOptions in der nest-cli.json-Datei verwenden, um Assets für die Build-Ausgabe zu definieren, z. B. durch die Angabe von „include“: „**/*.ttf“ und ‚outDir‘: „dist/src“.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-title="nest-cli.json" data-enlighter-theme="beyond">{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "assets": [
      {
        "include": "**/*.ttf",
        "outDir": "dist/src"
      }
    ],
    "deleteOutDir": true
  }
}</pre>
<p>Anschließend kann man nach dem Build die Schriftarten im Code mit path.resolve referenzieren, z. B. path.resolve(__dirname, &#8218;../fonts/Roboto-Regular.ttf&#8216;). Auf diese Weise wird sichergestellt, dass die Schriftartendateien mit dem Build gebündelt werden und während der Laufzeit zugänglich sind.</p>
<h3>Schlussgedanken zum Thema NestJS auf Vercel hosten</h3>
<p>Das Deployment meiner NestJS-App auf Vercel war eine wahre Achterbahnfahrt. Manchmal hatte ich das Gefühl, dass ich kurz davor war, alles perfekt zum Laufen zu bringen, nur um dann mit neuen Fehlern konfrontiert zu werden, die mich zurück in den Fehlersuch-Modus schickten. Es gab Momente der Frustration &#8211; vor allem im Zusammenhang mit der Sessionverarbeitung und CORS-Problemen. Aber jede Lösung brachte ein neues Hoch, und jeder behobene Fehler fühlte sich wie ein kleiner Sieg an.</p>
<p>Jetzt, wo endlich alles reibungslos funktioniert, kann ich sagen, dass es ein großartiges Gefühl ist. Wenn ich sehe, dass meine Anwendung so funktioniert, wie ich es mir vorgestellt habe, ist das all die Kopfschmerzen wert. Es ist eine große Erleichterung, aber noch mehr freut es mich, zu wissen, dass ich jede Hürde überwunden habe und auf das, was ich gelernt habe, zurückblicken kann. Ich hoffe, dieser Leitfaden kann anderen einige dieser Schwierigkeiten ersparen und helfen, diesen „Es funktioniert einfach“-Moment etwas schneller zu erreichen!</p>
<p>The post <a href="https://nerd-corner.com/de/erfahrungen-aus-der-praxis-nestjs-auf-vercel-hosten/">Erfahrungen aus der Praxis: NestJS auf Vercel hosten</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://nerd-corner.com/de/erfahrungen-aus-der-praxis-nestjs-auf-vercel-hosten/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<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=1756249400" 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=1756249400" 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>
		<item>
		<title>Swagger Api Doku zu Node Server hinzufügen</title>
		<link>https://nerd-corner.com/de/swagger-api-doku-zu-node-server-hinzufuegen/</link>
					<comments>https://nerd-corner.com/de/swagger-api-doku-zu-node-server-hinzufuegen/#comments</comments>
		
		<dc:creator><![CDATA[Nerds]]></dc:creator>
		<pubDate>Wed, 26 Oct 2022 17:07:59 +0000</pubDate>
				<category><![CDATA[Software-DE]]></category>
		<category><![CDATA[angular]]></category>
		<category><![CDATA[Api]]></category>
		<category><![CDATA[Backend]]></category>
		<category><![CDATA[Backend mit Swagger]]></category>
		<category><![CDATA[Backend Server]]></category>
		<category><![CDATA[CRUD]]></category>
		<category><![CDATA[Endpoints]]></category>
		<category><![CDATA[Endpunkte]]></category>
		<category><![CDATA[express.js]]></category>
		<category><![CDATA[Github]]></category>
		<category><![CDATA[Informatik]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[JSON]]></category>
		<category><![CDATA[Node]]></category>
		<category><![CDATA[Node mit Swagger und Typescript]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[Programmierung]]></category>
		<category><![CDATA[REST]]></category>
		<category><![CDATA[Schritt für Schritt Anweisung]]></category>
		<category><![CDATA[Server]]></category>
		<category><![CDATA[Swagger]]></category>
		<category><![CDATA[Swagger dokumentation]]></category>
		<category><![CDATA[Swagger express]]></category>
		<category><![CDATA[Swagger.json]]></category>
		<category><![CDATA[typescript]]></category>
		<guid isPermaLink="false">https://nerd-corner.com/?p=1266</guid>

					<description><![CDATA[<p>Ich habe vor kurzem einen Node Server mit express.js in Typescript programmiert. Das ist eine typische Kombination für Backend Entwicklung. Das ist besonders vorteilhaft wenn &#8230; </p>
<p>The post <a href="https://nerd-corner.com/de/swagger-api-doku-zu-node-server-hinzufuegen/">Swagger Api Doku zu Node Server hinzufügen</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Ich habe vor kurzem einen Node Server mit express.js in Typescript programmiert. Das ist eine typische Kombination für Backend Entwicklung. Das ist besonders vorteilhaft wenn bereits Erfahrung mit Frontend Entwicklung in Typescript vorhanden ist. Generell würde ich sowohl im Frontend als auch im Backend empfehlen lieber Typescript anstelle von Javascript zu nutzen. Typescript nutzt Javascript ist aber typbasiert und daher nicht so fehleranfällig!</p>
<p>Auch sollte der Fokus stehts auf clean Code legen und dieser ordentlich dokumentiert sein, besonders wenn ein Projekt größer wird zahlt sich ordentlich dokumentierte Arbeit aus. Um beispielsweise REST Endpunkte zu dokumentieren empfiehlt es sich Swagger zu benutzen. Die Swagger API Dokumentation bietet eine Übersicht über alle Endpunkte und sogar die Möglichkeit mit diesen zu interagieren.</p>
<p><strong><em>Das könnte Sie auch interessieren: </em></strong><em><a href="https://nerd-corner.com/de/wie-man-eine-reactive-angular-form-svg-mit-klickbaren-elementen-erstellt/" target="_blank" rel="noopener">Angular Form SVG mit clickbaren Elementen</a></em></p>
<h2>Liste der Komponenten</h2>
<ul>
<li>Entwicklungsumgebung (z.B. VS Code)</li>
<li>Node.js</li>
</ul>
<h2>Dokumentation mit Swagger</h2>
<p>Obwohl Swagger recht bekannt ist, konnte ich keine detaillierte Anleitung für die Implementation finden. Ich möchte daher 2 Wege zur Implementierung von Swagger in einen bestehenden Node Server erklären. Die erste Möglichkeit besteht darin direkt zu jedem Endpunkt Swagger Parameter hinzuzufügen. Das ist wohl die schnellere Variante, kann allerdings abhängig von der Anzahl der Endpunkte unübersichtlich werden. In der zweiten Variante wird eine „swagger.json“ Datei erstellt, die die  Parameter der Endpunkte zusammenfasst.</p>
<p>Um Swagger nutzen zu können wird folgende Bibliothek bzw. deren Typerweiterung genutzt:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">npm install --save  swagger-ui-express
npm install --save-dev @types/swagger-ui-express</pre>
<p>Swagger erstellt eine Dokumentation der Endpunkte:</p>
<p><img loading="lazy" decoding="async" class="zoooom aligncenter wp-image-1271" src="https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerDocu.jpg" alt="Example Swagger Docu" width="1647" height="853" srcset="https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerDocu.jpg 1847w, https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerDocu-300x155.jpg 300w, https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerDocu-1024x531.jpg 1024w, https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerDocu-768x398.jpg 768w, https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerDocu-1536x796.jpg 1536w" sizes="auto, (max-width: 1647px) 100vw, 1647px" /></p>
<p>Diese können geöffnet werden und man sieht einen beispielhaften Request und einen beispielhaften Response:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1273 zoooom" style="font-size: 1.125rem;" src="https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerEndpoint.jpg" alt="Example Swagger Endpoint 1" width="1657" height="840" srcset="https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerEndpoint.jpg 1757w, https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerEndpoint-300x152.jpg 300w, https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerEndpoint-1024x519.jpg 1024w, https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerEndpoint-768x389.jpg 768w, https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerEndpoint-1536x779.jpg 1536w" sizes="auto, (max-width: 1657px) 100vw, 1657px" /><img loading="lazy" decoding="async" class="aligncenter wp-image-1272 zoooom" src="https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerEndpoint2.png" alt="Example Swagger Endpoint 2" width="1657" height="803" srcset="https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerEndpoint2.png 1757w, https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerEndpoint2-300x145.png 300w, https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerEndpoint2-1024x496.png 1024w, https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerEndpoint2-768x372.png 768w, https://nerd-corner.com/wp-content/uploads/2022/10/exampleSwaggerEndpoint2-1536x744.png 1536w" sizes="auto, (max-width: 1657px) 100vw, 1657px" /></p>
<p>Über den Button &#8222;Try it out&#8220; kann direkt mit dem Endpunkt interagiert werden.</p>
<h2>Swagger API Dokumentation mit Parametern</h2>
<p>Diese Variante empfiehlt sich eher für kleinere Projekte. Zunächst müssen diese beiden Swagger Bibliotheken in das Projekt eingebunden werden:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="generic">npm install --save-dev @types/swagger-jsdoc 
npm install --save swagger-jsdoc</pre>
<p>Die Bibliotheken werden in app.ts bzw. für nicht Typescript Nutzer app.js konfiguriert. Aufgrund der hier festgelegten Konfiguration befindet sich die Dokumentation unter „/api-docs“:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-title="app.ts">import express from "express";
import bodyParser from "body-parser";
import exampleRoutes from "./routes/example-route";
import swaggerJSDoc from "swagger-jsdoc";
import swaggerUi from "swagger-ui-express";

const app = express();

const options = {
  definition: {
    openapi: "3.0.1",
    info: {
      title: "REST API for Swagger Documentation",
      version: "1.0.0",
    },
    schemes: ["http", "https"],
    servers: [{ url: "http://localhost:3000/" }],
  },
  apis: [
    `${__dirname}/routes/example-route.ts`,
    "./dist/routes/example-route.js",
  ],
};

const swaggerSpec = swaggerJSDoc(options);

app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec));
app.use(bodyParser.json());
app.use(exampleRoutes);

app.listen(3000);</pre>
<p>Der Endpunkt wird mit den Parametern folgendermaßen beschrieben:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-title="example-route.ts">import { Router } from "express";
import { exampleFunction } from "../controller/example";

const router = Router();

/**
 * @swagger
 * /example:
 *      post:
 *          summary: Send the text to the server
 *          tags:
 *              - ExampleEndpoints
 *          description: Send a message to the server and get a response added to the original text.
 *          requestBody:
 *              required: true
 *              content:
 *                  application/json:
 *                      schema:
 *                          type: object
 *                          properties:
 *                              responseText:
 *                                  type: string
 *                                  example: This is some example string! This is an endpoint
 *          responses:
 *              201:
 *                  description: Success
 *                  content:
 *                      application/json:
 *                          schema:
 *                              type: object
 *                              properties:
 *                                  text:
 *                                      type: string
 *                                      example: This is some example string!
 *              404:
 *                  description: Not found
 *              500:
 *                  description: Internal server error
 */
router.post("/example", exampleFunction);

export default router;
</pre>
<h2>Swagger API Dokumentation mit swagger.json</h2>
<p>Um eine bessere Übersicht zu haben und um die Dokumentation nicht mit dem eigentlichen Code zu vermischen empfiehlt sich die Nutzung einer, oder gar mehrerer json Dateien. Bei der Nutzung von TypeScript muss die Swagger.json Datei in die rootDirectory:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1276 zoooom" src="https://nerd-corner.com/wp-content/uploads/2022/10/foulderStructureOfSwaggerJson.jpg" alt="Foulder Structure with Swagger.json" width="170" height="344" srcset="https://nerd-corner.com/wp-content/uploads/2022/10/foulderStructureOfSwaggerJson.jpg 199w, https://nerd-corner.com/wp-content/uploads/2022/10/foulderStructureOfSwaggerJson-148x300.jpg 148w" sizes="auto, (max-width: 170px) 100vw, 170px" /></p>
<p>Auch hier wirdzunächst die Konfiguration in app.ts bzw. für nicht Typescript Nutzer in app.js festgelegt. Hier wird ebenfalls der Domainpfad „/api-docs“ für die Dokumentation gewählt:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="typescript" data-enlighter-title="app.ts">import express from "express";
import bodyParser from "body-parser";
import exampleRoutes from "./routes/example-route";
import swaggerUi from "swagger-ui-express";

import * as swaggerDocument from "./swagger.json";

const app = express();

app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));

app.use(bodyParser.json());

app.use(exampleRoutes);

app.listen(3000);</pre>
<p>Wichtiger Hinweis: Um JSON Dateien in ein Typescript Projekt zu importieren muss dies in der tsconfig.json erlaubt werden. Außerdem muss die JSON Datei sich in der root directory befinden:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="json" data-enlighter-title="tsconfig.json">{
  "compilerOptions": {
    "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
    "module": "commonjs" /* Specify what module code is generated. */,
    "rootDir": "./src" /* Specify the root folder within your source files. */,
    "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
    "resolveJsonModule": true /* Enable importing .json files. */,
    "outDir": "./dist" /* Specify an output folder for all emitted files. */,
    "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
    "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
    "strict": true /* Enable all strict type-checking options. */,
    "skipLibCheck": true /* Skip type checking all .d.ts files. */
  }
}</pre>
<p>Die vorherige Swagger Dokumentation mit Parametern würde somit in der swagger.json folgendermaßen aussehen:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="json">{
  "openapi": "3.0.1",
  "info": {
    "title": "REST API for Swagger Documentation",
    "version": "1.0.0"
  },
  "schemes": ["http"],
  "servers": [{ "url": "http://localhost:3000/" }],
  "paths": {
    "/example": {
      "post": {
        "tags": ["ExampleEndpoints"],
        "summary": "Send a text to the server",
        "description": "Send a message to the server and get a response added to the original text.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExampleSchemaHeader"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExampleSchemaBody"
                }
              }
            }
          },
          "404": { "description": "Not found" },
          "500": { "description": "Internal server error" }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ExampleSchemaBody": {
        "properties": {
          "responseText": {
            "type": "string",
            "example": "This is some example string! This is an endpoint"
          }
        }
      },
      "ExampleSchemaHeader": {
        "required": ["text"],
        "properties": {
          "text": {
            "type": "string",
            "example": "This is some example string!"
          }
        }
      }
    }
  }
}
</pre>
<h2>Dateien zum Herunterladen</h2>
<ul>
<li><a href="https://github.com/hanneslim/Node-with-swagger-params" target="_blank" rel="noopener">Github Beispielprojekt für swagger Parameter</a></li>
<li><a href="https://github.com/hanneslim/node-with-swagger-json" target="_blank" rel="noopener">Github Beispielprojekt für swagger.json</a></li>
</ul>
<p>The post <a href="https://nerd-corner.com/de/swagger-api-doku-zu-node-server-hinzufuegen/">Swagger Api Doku zu Node Server hinzufügen</a> appeared first on <a href="https://nerd-corner.com/de">Nerd Corner</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://nerd-corner.com/de/swagger-api-doku-zu-node-server-hinzufuegen/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
	</channel>
</rss>
