Die Huggingface Transformer-Methode „generate“ ist eine einfache Möglichkeit mit einem der aktuell 66.987 text-generation Modelle, automatisch Texte zu generieren.
Dazu kann man z.B. folgenden einfachen Python code verwenden.
from transformers import AutoModelForCausalLM, AutoTokenizer
model_path = '<your-model-path>'
tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False)
model = AutoModelForCausalLM.from_pretrained(
model_path,
device_map="auto",
torch_dtype='auto'
).eval()
messages = [
{"role": "user", "content": "hi"}
]
input_ids = tokenizer.apply_chat_template(conversation=messages, tokenize=True, add_generation_prompt=True, return_tensors='pt')
output_ids = model.generate(input_ids.to('cuda'))
response = tokenizer.decode(output_ids[0][input_ids.shape[1]:], skip_special_tokens=True)
print(response)
Der Code lädt ein Model und den Tokenizer von Huggingface und führ die Inference auf einer CUDA kompatiblen NVIDIA GPU aus. Die Methode „model.generate()“ kann aber noch viel mehr. Die model.generate() Methode nimmt noch folgende optionale Parameter entgegen, mit denen man das Generieren der Antwort vom Text-Generation Model noch optimieren kann.
Huggingface Transformer model.generate Parameter
max_length
: Die maximale Länge der zu generierenden Sequenz. Standardmäßig auf 20 gesetzt.
min_length
: Die minimale Länge der zu generierenden Sequenz. Standardmäßig auf 0 gesetzt.do_sample
: WennFalse
, wird bei der Generierung bei jedem Schritt das Token ausgewählt wird, das die höchste Wahrscheinlichkeit hat. Dies führt dazu, dass der generierte Text konservativer und deterministischer ist, da immer das wahrscheinlichste Token ausgewählt wird.early_stopping
: Wenn aufTrue
gesetzt, wird die Beam-Suche gestoppt, wenn mindestensnum_beams
Sätze pro Batch fertiggestellt sind. Standardmäßig aufFalse
gesetzt.num_beams
: Die „num_beams“-Option gibt an, wie viele alternative Sequenzen das Model während des Generierungsprozesses berücksichtigen soll. Ein höherer Wert für „num_beams“ führt zu einer breiteren Suche nach möglichen Sequenzen und kann die Qualität der generierten Texte verbessern, indem mehr potenziell sinnvolle Sequenzen in Betracht gezogen werden. Muss mindestens 1 sein. 1 bedeutet keine Beam-Suche. Standardmäßig auf 1 gesetzt.temperature
: Der Wert, der verwendet wird, um die Wahrscheinlichkeiten des nächsten Tokens zu modulieren. Muss positiv sein. Standardmäßig auf 1,0 gesetzt. Ein höherer Wert führt zu einer „weicheren“ Wahrscheinlichkeitsverteilung für die Auswahl des nächsten Tokens. Das bedeutet, dass Tokens mit niedrigeren Wahrscheinlichkeiten stärker berücksichtigt werden, was zu einer höheren Diversität im generierten Text führt. Der Text wird vielfältiger, kreativer und möglicherweise weniger vorhersehbar.top_k
: Die Anzahl der wahrscheinlichsten Vokabular-Token, die für die Top-k-Filterung beibehalten werden sollen. Standardmäßig auf 50 gesetzt. Ein niedrigertop_k
-Wert begrenzt die Anzahl der Token, die während des Sampling-Prozesses in Betracht gezogen werden. Nur die Top-k Tokens mit den höchsten Wahrscheinlichkeiten werden berücksichtigt, während alle anderen Tokens ignoriert werden.top_p
: Die kumulative Wahrscheinlichkeit der Parameter, um die wahrscheinlichsten Tokens für das Nucleus-Sampling zu behalten. Muss zwischen 0 und 1 liegen. Standardmäßig auf 1 gesetzt. Ein niedriger top_p-Wert bedeutet, dass das Model nur eine kleine Teilmenge der wahrscheinlichsten nächsten Token in Betracht zieht. Bei einem top_p Wert von 1 berücksichtigt das Model alle möglichen nächsten Token, was zu vielfältigeren Ausgaben führt. Wenn der top_p-Wert auf einen bestimmten Wert (z.B. 0,8) festgelegt ist, wählt das Model dynamisch die kleinste Menge an nächsten Token aus, deren kumulierte Wahrscheinlichkeit 80% entspricht (bzw. so gerade eben überschreitet).repetition_penalty
: Der Parameter für die Wiederholungsstrafe. Wert muss größer oder gleich 1 sein. 1 bedeutet keine Strafe für Wiederholungen. Standardmäßig auf 1 gesetzt. Mit demrepetition_penalty
bestraft man jedes sich wiederholende Token, selbst Token in der Mitte/am Ende eines Wortes, Stoppwörter und Satzzeichen. Wenn derrepetition_penalty
zu hoch ist, kann dies zu unerwünschten Ergebnissen führen.pad_token_id
: Padding-Token. Standardmäßig auf das spezifische Model-Pad-Token-ID oderNone
, wenn es nicht vorhanden ist. Es wird von dem Model verwendet, um Sequenzen auf eine einheitliche Länge zu bringen. Wenn Batches für Training oder Inferenz verwendet werden, müssen alle Sequenzen in einem Batch die gleiche Länge haben. Das Pad-Token wird verwendet, um die Lücken in kürzeren Sequenzen aufzufüllen und sicherzustellen, dass alle Sequenzen im Batch die gleiche Länge haben. Das Pad-Token ist ein spezielles Token, das im Vokabular des Models vorhanden ist und keine semantische Bedeutung hat. Es wird während des Preprocessing-Schritts eingefügt, um kürzere Sequenzen auf die Länge der längsten Sequenz im Batch aufzufüllen.bos_token_id
: (optional
) Standardmäßig aufbos_token_id
wie in der Konfiguration des Models definiert. Das BOS (Beginning of Sentence) Token wird verwendet, um den Anfang einer Sequenz in einem Large Language Model zu kennzeichnen. Das BOS-Token wird am Anfang einer jeden Eingabesequenz eingefügt, um anzuzeigen, dass eine neue Sequenz beginnt. Mit dem BOS-Token kann man dem Model helfen, den Kontext zu verstehen und sich auf den Beginn einer neuen Sequenz zu konzentrieren (Attention is All you Need). Durch die Verwendung des BOS-Tokens kann das Model die semantische Struktur der Eingabe besser interpretieren und möglicherweise bessere Vorhersagen oder Generierungen erzeugen.eos_token_id
: (optional
) Standardmäßig aufeos_token_id
wie in der Konfiguration des Models definiert. Das EOS-Token steht für „End of Sequence“. Das EOS-Token ist sehr wichtig, wenn das Model verwendet wird, um Sequenzen zu generieren, da es dem Model signalisiert, dass es die Generierung der Ausgabe beenden sollte. Wenn das Model beispielsweise dazu verwendet wird, einen Satz zu generieren, wird das EOS-Token am Ende des Satzes platziert, um anzuzeigen, dass der Satz abgeschlossen ist.length_penalty
: wird verwendet, um die Länge der generierten Sequenz zu regulieren, indem er eine Strafe auf längere Sequenzen angewendet wird. Wenn der Längenstrafparameter größer ist, bedeutet es, dass kürzeren Sequenzen stärker bestraft werden, was zu längeren Sequenzen führt.no_repeat_ngram_size
: Wenn auf int > 0 gesetzt, verhindert der Parameter „no_repeat_ngram_size“ (Größe des nicht wiederholenden n-Gramms) die Wiederholung von bestimmten n-Grammen in der generierten Sequenz. Wenn das Model eine Ausgabe generiert, kann es vorkommen, dass bestimmte n-Gramme wiederholt werden, was zu einer unnatürlichen oder repetitiven Ausgabe führen kann. Der Parameter „no_repeat_ngram_size“ steuert dies, indem er festlegt, dass keine n-Gramme in der Ausgabe wiederholt werden sollen, die die angegebene Größe haben. Dieser Parameter ist besonders nützlich, um die Qualität und Natürlichkeit der generierten Ausgabe zu verbessern, indem repetitive Muster und Phrasen vermieden werden, die sonst möglicherweise auftreten würden.bad_words_ids
: Liste von Listen von intbad_words_ids
enthält Token, die nicht generiert werden dürfen. Der Parameter akzeptiert eine Liste von Token-IDs, die als „schlechte Wörter“ definiert sind, um sie in der Ausgabe zu vermeiden. Ein Beispiel für die Verwendung des Parameters „bad_words_ids“ wäre in einem Chatbot, der keine beleidigenden Sprache verwenden soll. Wenn sich ein unerwünschtes Wort aus mehreren Tokens zusammensetzt, müssen alle Tokens desbad_word
in der Liste eingetragen werden. Das führt leider dazu, dass auch andere Wörter, die sich aus diesen Tokens zusammensetzen im generierten Text nicht mehr auftauchen werden – also Vorsicht.num_return_sequences
: Die Anzahl der unabhängig berechneten zurückgegebenen Sequenzen für jedes Element im Batch. Standardmäßig auf 1 gesetzt.attention_mask
Durch die Verwendung desattention_mask
kann das Model die Aufmerksamkeit auf relevante Teile der Eingabe lenken und gleichzeitig irrelevante Teile ausblenden. Es ist eine binäre Maske, die angibt, welche Token in der Eingabe-Sequenz berücksichtigt werden sollen und welche ignoriert werden sollen. Wenn die Generate Methode Paddings verwendet, um Sequenzen auf eine einheitliche Länge zu bringen wird dieattention_mask
verwendet, um das Model darauf hinzuweisen, dass es die Paddings nicht beachten soll, da sie keine tatsächlichen Informationen enthalten und nur dazu dienen, die Sequenzen aufzufüllen.
Wert für dieattention_mask
wäre:
– 1 für Token, die berücksichtigt werden sollen.
– 0 für Token, die ignoriert werden sollen.decoder_start_token_id
: Wird spezifiziert, um dem Decoder mitzuteilen, mit welchem Token er die Generierung der Ausgabe beginnen soll (BOS). Dies ist besonders relevant für die Generierung von Sequenzen, da es dem Model ermöglicht, den Kontext richtig zu initialisieren und die Generierung von einer geeigneten Startposition aus zu beginnen. Insgesamt wird der Parameterdecoder_start_token_id
verwendet, um dem Model den Ausgangspunkt für die Generierung von Ausgaben zu geben und sicherzustellen, dass der Decoder mit einer angemessenen Startkonfiguration arbeitet. (Habe ich persönlich aber noch nie verwendet)use_cache
: Wennuse_cache
True ist, werden vergangene Schlüsselwerte verwendet, um das Decodieren zu beschleunigen, wenn es auf das Model zutrifft. Standardmäßig aufTrue
gesetzt.model_specific_kwargs
: Zusätzliche modelspezifische Argumente, die an dieforward
-Funktion des Models weitergeleitet werden. Wird aber sehr selten verwendet und wird dann hoffentlich in der Doku zu dem Model beschrieben.
Ein paar Beispiele
Im Folgenden werde ich anhand von ein paar Beispielen einige der Parameter exemplarisch erläutern und ihre Funktionen im Kontext der Generierung von Textsequenzen erklären.
Das Model für die Tests
Für den Test verwende ich das „deepseek-ai/deepseek-coder-1.3b-instruct“ Model.
Das Modell erzielt sehr gute Leistung bei mehreren Programmiersprachen und hat gute Ergebnisse bei verschiedenen Benchmarks erreicht. Darunter HumanEval, MultiPL-E, MBPP, DS-1000 und APPS1.
Das Modell wurde von Grund auf mit 2 Billionen Tokens trainiert, wobei 87% Code und 13% Text in Englisch und Chinesisch enthalten sind. Es gibt verschiedene Modellgrößen, von 1,3 Milliarden bis zu 33 Milliarden Parametern, sodass man die für ihre Anforderungen am besten geeignete Konfiguration auswählen können. Ich verwende hier das 1,3B Model, da es schnell antwortet, nur wenig VRAM benötigt und für die Tests mehr als ausreichend ist. Das Model hat eine Kontext-Länge 16K Token und bietet sich daher auch für RAG an.
Test Prompt
Das Prompt Template des „deepseek-ai/deepseek-coder-1.3b-instruct“ Model hat folgenden Aufbau:
prompt = f'''You are an AI programming assistant, utilizing the Deepseek Coder model, developed by Deepseek Company, and you only answer questions related to computer science. For politically sensitive questions, security and privacy issues, and other non-computer science questions, you will refuse to answer.
### Instruction:
{user_message}
### Response:
'''
Der komplette Test-Prompt sieht wie folgt aus. Dabei ist die main Methode aus der Klasse NamespaceChatLauncher.java des Github Projektes https://github.com/mrniko/netty-socketio-demo entnommen (Ein Projekt über das ich später einmal berichten werde 🙂 ).
user_message = """Generate clear and comprehensive JavaDoc documentation for the provided Java method. Describe its purpose, functionality, and important details concisely. Emphasize essential information about input parameters, return values, and potential exceptions. Highlight coding conventions or patterns used in the method, and provide usage examples if applicable. Aim for clarity and completeness in your documentation.
```java
public static void main(String[] args) throws InterruptedException {
Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(9092);
final SocketIOServer server = new SocketIOServer(config);
final SocketIONamespace chat1namespace = server.addNamespace("/chat1");
chat1namespace.addEventListener("message", ChatObject.class, new DataListener<ChatObject>() {
@Override
public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) {
// broadcast messages to all clients
chat1namespace.getBroadcastOperations().sendEvent("message", data);
}
});
final SocketIONamespace chat2namespace = server.addNamespace("/chat2");
chat2namespace.addEventListener("message", ChatObject.class, new DataListener<ChatObject>() {
@Override
public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) {
// broadcast messages to all clients
chat2namespace.getBroadcastOperations().sendEvent("message", data);
}
});
server.start();
Thread.sleep(Integer.MAX_VALUE);
server.stop();
}
```"""
Ich verzichte hier auf ein EmotionPrompt wie unter „Large Language Models Understand and Can Be Enhanced by Emotional Stimuli“ beschrieben auch wenn ich damit bei anderen Sprachmodellen schon sehr gute Ergebnisse erzielt habe. Auch hier werde ich in Zukunft noch einmal einen Blog-Post veröffentlichen bei dem ich die verschiedenen Propt-Techniken in zusammen den Model-Parametern teste und überprüfen, was gut miteinander funktioniert und was nicht.
Test 1: Parameter num_beams
Der Test soll zeigen, wie num_beams die Qualität der Ausgabe beeinflussen kann, da ein hoher Wert hier dafür sorgt, dass die Qualität des generieten Text deutlich verbessern kann.
Ich werde den das Programm mit verschiedenen num_beams Werten aufrufen und dabei den do_sample Parameter = False übergeben um Variationen im generierten Text durch temperature, top_p und top_k zu vermeiden.
Ergebnis: num_beams=1
```java /** * The main method of the application. It initializes a configuration object, sets the hostname and port, creates a new SocketIOServer instance, adds namespaces to it, and adds event listeners to the SocketIONamespace objects. * * @param args The command line arguments. * @throws InterruptedException If the current thread is interrupted while waiting on a thread monitor's lock. */ public static void main(String[] args) throws InterruptedException { // Configuration object to hold the server configuration details. Configuration config = new Configuration(); config.setHostname("localhost"); config.setPort(9092); // Create a new SocketIOServer instance with the configuration. final SocketIOServer server = new SocketIOServer(config); // Add namespaces to the SocketIOServer instance. final SocketIONamespace chat1namespace = server.addNamespace("/chat1"); final SocketIONamespace chat2namespace = server.addNamespace("/chat2"); // Add event listeners to the SocketIONamespace objects. chat1namespace.addEventListener("message", ChatObject.class, new DataListener<ChatObject>() { @Override public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) { // Broadcast messages to all clients. chat1namespace.getBroadcastOperations().sendEvent("message", data); } }); chat2namespace.addEventListener("message", ChatObject.class, new DataListener<ChatObject>() { @Override public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) { // Broadcast messages to all clients. chat2namespace.getBroadcastOperations().sendEvent("message", data); } }); // Start the SocketIOServer instance. server.start(); // Sleep indefinitely to keep the application running. Thread.sleep(Integer.MAX_VALUE); // Stop the SocketIOServer instance. server.stop(); } ``` The `main` method of the application is the entry point of the program. It initializes a `Configuration` object, sets the hostname and port, creates a new `SocketIOServer` instance, adds namespaces to it, and adds event listeners to the `SocketIONamespace` objects. The `SocketIOServer` instance is created with the configuration details provided in the `Configuration` object. The namespaces are added to the `SocketIOServer` instance and the event listeners are added to the `SocketIONamespace` objects. The `SocketIOServer` instance is started, and the application enters a sleep loop to keep the application running. After the application is stopped, the `SocketIOServer` instance is stopped. The `Configuration` object holds the configuration details for the `SocketIOServer` instance, such as the hostname and port. The `SocketIONamespace` objects are the namespaces added to the `SocketIOServer` instance. Each `SocketIONamespace` object represents a namespace in the SocketIO server. The `addEventListener` method is used to add event listeners to the `SocketIONamespace` objects. The first parameter is the event name, the second parameter is the type of the data to be received, and the third parameter is the `DataListener` object. The `DataListener` object is a callback method that is invoked when a data event is received. The `onData` method of the `DataListener` object is invoked when a data event is received. It broadcasts the received data to all clients. The `SocketIOServer` instance is started with the `start` method, and stopped with the `stop` method. The `main` method is the entry point of the application, and it is invoked when the application is started.
Ergebnis: num_beams=2
```java /** * The main entry point of the application. * * This method creates a new SocketIOServer instance with a configuration object, sets the hostname and port, * adds event listeners to two SocketIONamespaces, and starts the server. It then waits indefinitely for the server * to be stopped, after which it stops the server. * * @param args The command line arguments. * @throws InterruptedException If the main thread is interrupted while waiting for the server to stop. */ public static void main(String[] args) throws InterruptedException { // Configuration object for the SocketIOServer. Configuration config = new Configuration(); config.setHostname("localhost"); config.setPort(9092); // Create a new SocketIOServer instance with the configuration object. final SocketIOServer server = new SocketIOServer(config); // Add event listeners to two SocketIONamespaces. final SocketIONamespace chat1namespace = server.addNamespace("/chat1"); chat1namespace.addEventListener("message", ChatObject.class, new DataListener<ChatObject>() { @Override public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) { // Broadcast messages to all clients. chat1namespace.getBroadcastOperations().sendEvent("message", data); } }); final SocketIONamespace chat2namespace = server.addNamespace("/chat2"); chat2namespace.addEventListener("message", ChatObject.class, new DataListener<ChatObject>() { @Override public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) { // Broadcast messages to all clients. chat2namespace.getBroadcastOperations().sendEvent("message", data); } }); // Start the server. server.start(); // Wait indefinitely for the server to be stopped. Thread.sleep(Integer.MAX_VALUE); // Stop the server. server.stop(); } ```
Ergebnis: num_beams=3
```java /** * The main entry point of the application. * * This method initializes a SocketIOServer with a Configuration object, sets the hostname and port for the server, * adds event listeners to two SocketIONamespaces, one for each chat room, and broadcasts messages to all clients. * * @param args The command line arguments. * @throws InterruptedException If the main thread is interrupted while waiting for the server to stop. */ public static void main(String[] args) throws InterruptedException { Configuration config = new Configuration(); config.setHostname("localhost"); config.setPort(9092); final SocketIOServer server = new SocketIOServer(config); final SocketIONamespace chat1namespace = server.addNamespace("/chat1"); chat1namespace.addEventListener("message", ChatObject.class, new DataListener<ChatObject>() { @Override public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) { // broadcast messages to all clients chat1namespace.getBroadcastOperations().sendEvent("message", data); } }); final SocketIONamespace chat2namespace = server.addNamespace("/chat2"); chat2namespace.addEventListener("message", ChatObject.class, new DataListener<ChatObject>() { @Override public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) { // broadcast messages to all clients chat2namespace.getBroadcastOperations().sendEvent("message", data); } }); server.start(); Thread.sleep(Integer.MAX_VALUE); server.stop(); } ```
Der Unterschied zwischen den beiden Varianten num_beams = 1 und num_beams = 2 ist sehr groß und das Ergebnis der num_beams = 2 Variante ist auch deutlich besser, während sich bei num_beams = 3 Variante nicht mehr so viel ändert.
Vergleich von num_beams=2 vs. num_beams=3
Parameter temperature
Die Temperatur beeinflusst die Zufälligkeit und Kreativität der generierten Texte. Angenommen, man verwendet ein LLM, um eine Fortsetzung für den Satz “Die Sonne scheint heute” zu generieren. Wenn die Temperatur hoch ist (z. B. 0,8), könnte die generierte Fortsetzung lauten:
“Die Sonne scheint heute so hell, dass die Vögel in einem fröhlichen Chor singen und die Blumen im Wind tanzen. Selbst die Bäume scheinen zu lächeln.”
In diesem Fall ist der Text kreativ und enthält viele zusätzliche Details.
Wenn wir Temperatur niedrig ist (z. B. 0,2), könnte die generierte Fortsetzung konservativer sein:
“Die Sonne scheint heute.”
Hier ist der Text knapp und direkt.
Die Temperatur ermöglicht es also, den richtigen Kompromiss zwischen Kreativität und Klarheit zu finden. Je höher die Temperatur, desto mehr Variation und Unvorhersehbarkeit gibt es im generierten Text. Je niedriger die Temperatur, desto konservativer und vorhersehbarer ist der Text.
Ergebnis temperature=0.2
```java /** * The main method of the application. It sets up a configuration object with a hostname and port, * creates a new SocketIOServer instance with the configuration, adds two SocketIONamespaces to the server, * and adds event listeners to each namespace. The event listeners are responsible for broadcasting messages * to all clients in the respective namespace. * * @param args Command line arguments. * @throws InterruptedException If the main thread is interrupted while waiting for the server to start or stop. */ public static void main(String[] args) throws InterruptedException { // Configuration object to hold server configuration details. Configuration config = new Configuration(); config.setHostname("localhost"); config.setPort(9092); // Create a new SocketIOServer instance with the configuration. final SocketIOServer server = new SocketIOServer(config); // Add two SocketIONamespaces to the server. final SocketIONamespace chat1namespace = server.addNamespace("/chat1"); final SocketIONamespace chat2namespace = server.addNamespace("/chat2"); // Add event listeners to each namespace. chat1namespace.addEventListener("message", ChatObject.class, new DataListener<ChatObject>() { @Override public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) { // Broadcast messages to all clients in the chat1namespace. chat1namespace.getBroadcastOperations().sendEvent("message", data); } }); chat2namespace.addEventListener("message", ChatObject.class, new DataListener<ChatObject>() { @Override public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) { // Broadcast messages to all clients in the chat2namespace. chat2namespace.getBroadcastOperations().sendEvent("message", data); } }); // Start the server. server.start(); // Sleep indefinitely to keep the main thread alive. Thread.sleep(Integer.MAX_VALUE); // Stop the server. server.stop(); } ```
Ergebnis temperature=0.8
```java /** * The main method is the entry point of the Java application. This method initializes a SocketIOServer, * adds two SocketIONamespaces for two different chats, and two DataListeners for these chats. * The DataListeners are added to the SocketIONamespaces and then event handlers are set up * for SocketIOClient events. On receiving a 'message' event in any of the SocketIONamespaces, * the message payload is broadcasted to all other SocketIONamespaces. * * @param args Command-line arguments. These are not used in this specific method as it is public static void. * @throws InterruptedException If the thread睡眠过程中发生中断。 */ public static void main(String[] args) throws InterruptedException { // ... The rest of the code ... } ``` This Java method named `main` is the entry point of a Java application. It initializes a `SocketIOServer` and two `SocketIONamespaces`: one for chat1 and one for chat2. It then adds the `DataListeners` to the `SocketIONamespaces`. The `DataListeners` are also added to the `SocketIONamespaces` and then event handlers are set up for SocketIOClient events. On receiving a 'message' event in any of the `SocketIONamespace` it broadcasts the message to all other `SocketIONamespaces`. The `SocketIOServer` configuration, `socketHostname` and `socketPort` parameters are set to "localhost" and "9092" respectively. The `main` method is declared as `public static void` and it has a `throws InterruptedException` parameter. This method is thread-safe and does not block other threads. The `main` method is the entry point of the program execution. It initializes a `SocketIOServer` and two `SocketIONamespaces`, adds the `DataListeners` to the `SocketIONamespaces`, and then sets up event handlers for SocketIOClient events. The method is declared as `public` and `static`, allowing it to be accessed directly from other classes, and `void` because it does not return a value.
Vergleich von temperature = 0.2 vs. temperature = 0.8. Die Unterschiede könnten kaum größer sein. Genau das ist ja auch die Funktion des Parameters. Es geht darum die Ausgaben kreativer und unvorhersebarer zu gestalten.
Ergebnis temperature=0.8
Ein zweiter Lauf mit gleichen Parametern ergibt ein komplett anderes Resultat da die Kreativität erneut zugeschlagen hat 🙂
```java /** * This Java method is primarily used to setup and configure a ServerSocket for communicating with clients, * and it listens for messages sent via a given namespace (in this case, "/chat1" and "/chat2"). * * The function takes a Configuration object and sets its hostname and port. * It then creates a new SocketIOServer instance with the provided configuration. * The SocketIOServer is then used to add namespaces for "/chat1" and "/chat2". * Each namespace is associated with a specific event listener, which listens for messages sent via that namespace. * * The event listener is an instance of a custom DataListener class, which is responsible for handling incoming data. * When the data listener receives a message, it broadcasts the message to all connected clients in the respective namespace. * * The function also includes a start() method for starting the server and a stop() method for stopping the server. * * Important details about input parameters and return values of the method are as follows: * - The Configuration object is used to set the server's hostname and port. * - The SocketIOServer instance is created using the Configuration object. * - The SocketIONamespace instances are created using the SocketIOServer and their associated namespaces ("/chat1" and "/chat2"). * - The DataListener Interface is a generic interface that represents a listener for data events. * * Exception handling is accomplished through the use of exceptions thrown by the SocketIOServer's methods. * * Coding conventions or patterns used in the method are as follows: * - It uses the Builder pattern to create a Configuration instance which contributes to code clarity and efficiency. * - It uses a generic type for its DataListener parameter, which contributes to type safety. * * Usage examples may vary depending on the exact requirements of your application. * * @param args Command-line arguments. * @throws InterruptedException If the current thread is interrupted. */ public static void main(String[] args) throws InterruptedException { // ... code from the original method ... } ```
Test mit temperature = 0.0000001
Wie man an dem Vergleich oben sehen kann, variieren die Ergebnisse auch bei extrem niedrigen Werten für temperature ein wenig. Wirklich vergleichbare Ergebnisse erzielt man nur mit dem Parameter do_sample = False (unten). Mit dem Parameter kann man dafür sorgen, dass man wirklich vergleichbare Werte erhält um z.B. Modelle zu validieren bzw unterscheicliche Modelle miteinander vergleichen zu können.
Identische Ergebnisse dank Parameter do_sample = False.
Test top_p
Top-p, auch als nucleus sampling bezeichnet, ist ein Parameter, der bei der Generierung von Text die Wahrscheinlichkeitsverteilung bei der Auswahl des nächsten Tokens beeinflusst. Wenn ein LLM einen Text generiert, berechnet es für jedes mögliche nächste Token die Wahrscheinlichkeit, mit der dieses Token vorkommen wird. Diese Wahrscheinlichkeiten bilden eine Verteilung. Der Top-p-Parameter bestimmt, welche Token in die Verteilung einbezogen werden. Es entfernt die Token mit einer geringen Wahrscheinlichkeit, sodass nur diejenigen mit den höchsten Wahrscheinlichkeiten berücksichtigt werden. Konkret bedeutet dies, dass nur Worte mit einer kumulativen Wahrscheinlichkeit von mindestens p (z. B. 0,5) in die Auswahl einbezogen werden.
Top-p ermöglicht eine ausgewogene Mischung aus häufigen und weniger häufigen Wörtern, was zu vielfältigen und interessanten Texten führt. Es ist ein nützliches Werkzeug, um die Kreativität und Vielseitigkeit von LLMs zu steuern.
Test: temperature=0.0000001 und top_p=0.00001
Mit sehr niedrigen Werten für temperature und top_p kann man auch identische Ergebnisse bei wiederholten Tests erreichen (oben).
Bei einer temperature von 0.6 und einem top_p Wert von 0.6 kann man schon deutliche Unterschiede bei den generierten Texten erkennen.
Eine geringe temperature von 0.000001 und ein top_p von 0.6 kann auch mal komplette falsche Ergebnisse erzeugen was dann z.B. wie folgt aussehen kann.
```java /** * The main method of the application. It sets up a SocketIOServer with a configuration and adds two event listeners to two namespaces. * * The event listeners are responsible for broadcasting messages to all clients in the respective chat namespace. * * The method takes an array of Strings as input parameters, but it doesn't use them. * * The method throws an InterruptedException, which is a checked exception. This means that the method can't return a value and must be declared to throw this exception. * * The method has two parameters of type Configuration and SocketIONamespace. * * The method returns void, which means that the method doesn't return a value. * * The method uses a DataListener to handle incoming data events. The DataListener is responsible for broadcasting the received data to all clients. * * The method uses a SocketIOServer to manage the connection to the server. The server is configured with a hostname and a port. * * The method adds two event listeners to two different chat namespaces. The event listeners are responsible for broadcasting messages to all clients in the respective chat namespace. * * The method starts the server and then enters an infinite loop to keep the application running. After the server is stopped, the application ends. * * The method is declared as static, which means that it can be accessed from any class without creating an instance of the class. * * The method is declared as public, which means that it can be accessed from any class. * * The method is declared as final, which means that it can't be overridden in subclasses. * * The method is declared as throws InterruptedException, which means that the method can't return a value and must be declared to throw this exception. * * The method is declared as synchronized, which means that the method can only be called once at a time. * * The method is declared as void, which means that the method doesn't return a value. * * The method is declared as static, which means that it can be accessed from any class without creating an instance of the class. * * The method is declared as public, which means that it can be accessed from any class. * * The method is declared as final, which means that it can't be overridden in subclasses. * * The method is declared as synchronized, which means that the method can only be called once at a time. * * The method is declared as void, which means that the method doesn't return a value. * * The method is declared as static, which means that it can be accessed from any class without creating an instance of the class. * * The method is declared as public, which means that it can be accessed from any class. * * The method is declared as final, which means that it can't be overridden in subclasses. * * The method is declared as synchronized, which means that the method can only be called once at a time. * * The method is declared as void, which means that the method doesn't return a value. * * The method is declared as static, which means that it can be accessed from any class without creating an instance of the class. * * The method is declared as public, which means that it can be accessed from any class. * * The method is declared as final, which means that it can't be overridden in subclasses. * * The method is declared as synchronized, which means that the method can only be called once at a time. * * The method is declared as void, which means that the method doesn't return a value. * * The method is declared as static, which means that it can be accessed from any class without creating an instance of the class. * * The method is declared as public, which means that it can be accessed from any class. * * The method is declared as final, which means that it can't be overridden in subclasses. * * The method is declared as synchronized, which means that the method can only be called once at a time. * * The method is declared as void, which means that the method doesn't return a value. * * The method is declared as static, which means that it can be accessed from any class without creating an instance of the class. * * The method is declared as public, which means that it can be accessed from any class. * * The method is declared as final, which means that it can't be overridden in subclasses. * * The method is declared as synchronized, which means that the method can only be called once at a time. * * The method is declared as void, which means that the method doesn't return a value. * * The method is declared as static, which means that it can be accessed from any class without creating an instance of the class. * * The method is declared as public, which means that it can be accessed from any class. * * The method is declared as final, which means that it can't be overridden in subclasses. * * The method is declared as synchronized, which means that the method can only be called once at a time. * * The method is declared as void, which means that the method doesn't return a value. * * The method is declared as static, which means that it can be accessed from any class without creating an instance of the class. * * The method is declared as public, which means that it can be accessed from any class. * * The method is declared as final, which means that it can't be overridden in subclasses. * * The method is declared as synchronized, which means that the method can only be called once at a time. * * The method is declared as void, which means that the method doesn't return a value. * * The method is declared as static, which means that it can be accessed from any class without creating an instance of the class. * * The method is declared as public, which means that it can be accessed from any class. * * The method is declared as final, which means that it can't be overridden in subclasses. * * The method is declared as synchronized, which means that the method can only be called once at a time. * * The method is declared as void, which means that the method doesn't return a value. * * The method is declared as static, which means that it can be accessed from any class without creating an instance of the class. * * The method is declared as public, which means that it can be accessed from any class. * * The method is declared as final, which means that it can't be overridden in subclasses. * * The method is declared as synchronized, which means that the method can only be called once at a time. * * The method is declared as void, which means that the method doesn't return a value. * * The method is declared as static, which means that it can be accessed from any class without creating an instance of the class. * * The method is declared as public, which means that it can be accessed from any class. * * The method is declared as final, which means that it can't be overridden in subclasses. * * The method is declared as synchronized, which means that the method can only be called once at a time. * * The method is declared as void, which means that the method doesn't return a value. * * The method is declared as static, which means that it can be accessed from any class without creating an instance of the class. * * The method is declared as public, which means that it can be accessed from any class. * * The method is declared as final, which means that it can't be overridden in subclasses. * * The method is declared as synchronized, which means that the method can only be called once at a time. * * The method is declared as void, which means that the method doesn't return a value. * * The method is declared as static, which means that it can be accessed from any class without creating an instance of the class. * * The method is declared as public, which means that it can be accessed from any class. * * The method is declared as final, which means that it can't be overridden in subclasses. * * The method is declared as synchronized, which means that the method can only be called once at a time. * * The method is declared as void, which means that the method doesn't return a value. * * The method is declared as static, which means
Die Kombination von niedrigem Wert für den Parameter “temperature” und hohem Wert für den Parameter “top_p” kann negative Folgen haben.
Die “Temperature” ist ein Hyperparameter, der die Zufälligkeit der generierten Texte steuert, während der “top-p”-Wert ist ein Parameter ist, der die Wahrscheinlichkeit steuert, mit der die nächsten Tokens ausgewählt werden. Wählt man also einen sehr niedrigen Wert für “temperature” und einen hohen Wert für “top-p”, kann dies zu folgenden Problemen führen:
- Die Ausgaben sind möglicherweise zu vorhersehbar und wiederholen sich.
- Das Modell kann in einem begrenzten Token-Bereich gefangen sein. Genau das ist bei dem obigen Beispiel passiert.
- Die Texte könnten eintönig wirken, da das Modell nur auf die wahrscheinlichsten Optionen beschränkt ist.
- Ein zu hoher “top-p”-Wert kann dazu führen, dass das Modell den Kontext verliert und unzusammenhängende Sätze generiert.
Es ist wichtig, die Werte für “temperature” und “top-p” sorgfältig auszuwählen, um eine ausgewogene Mischung aus Kreativität, Vielfalt und Zusammenhang zu erzielen. Für ein optimales Ergebnis hilft es nur zu experimentieren.
Test top_k
Der top_k-Parameter beeinflusst die Auswahl der Tokens während der Textgenerierung. Wird keine Einschränkung durch den top_k-Parameter vorgenommen, werden alle Token bei der Auswahl des nächsten Tokens berücksichtigt. Dies kann zu einer zu breiten Vielfalt an möglichen Token führen. Wird der top_k-Parameter auf beispielsweise 2 gesetzt, werden nur die beiden wahrscheinlichsten Token in die Auswahl aufgenommen. Dadurch werden die generierten Texte fokussierter.
Test top_k = 10
```java /** * The main method of the application. It creates a configuration object, sets the hostname and port, * creates a new SocketIOServer instance with the configuration, adds namespaces for two chat channels, * and adds event listeners for the "message" event on both namespaces. * * The event listeners are responsible for broadcasting messages to all clients. * * @param args Command line arguments. * @throws InterruptedException If the current thread is interrupted. * * @author Your Name * @since 1.0 */ public static void main(String[] args) throws InterruptedException { Configuration config = new Configuration(); config.setHostname("localhost"); config.setPort(9092); final SocketIOServer server = new SocketIOServer(config); final SocketIONamespace chat1namespace = server.addNamespace("/chat1"); chat1namespace.addEventListener("message", ChatObject.class, new DataListener<ChatObject>() { @Override public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) { // broadcast messages to all clients chat1namespace.getBroadcastOperations().sendEvent("message", data); } }); final SocketIONamespace chat2namespace = server.addNamespace("/chat2"); chat2namespace.addEventListener("message", ChatObject.class, new DataListener<ChatObject>() { @Override public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) { // broadcast messages to all clients chat2namespace.getBroadcastOperations().sendEvent("message", data); } }); server.start(); Thread.sleep(Integer.MAX_VALUE); server.stop(); } ```
Test top_k = 2
```java /** * The main method of the application. It sets up a SocketIOServer with a configuration and adds two event listeners to two namespaces. * * The event listeners are responsible for broadcasting messages to all clients. * * @param args Command line arguments. Not used in this method. * @throws InterruptedException If the current thread is interrupted while waiting. */ public static void main(String[] args) throws InterruptedException { // Configuration object for the SocketIOServer. Configuration config = new Configuration(); config.setHostname("localhost"); config.setPort(9092); // Create a new SocketIOServer instance with the provided configuration. final SocketIOServer server = new SocketIOServer(config); // Add two namespaces to the SocketIOServer. final SocketIONamespace chat1namespace = server.addNamespace("/chat1"); final SocketIONamespace chat2namespace = server.addNamespace("/chat2"); // Define a listener for the "message" event in the first namespace. chat1namespace.addEventListener("message", ChatObject.class, new DataListener<ChatObject>() { @Override public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) { // Broadcast the message to all clients in the first namespace. chat1namespace.getBroadcastOperations().sendEvent("message", data); } }); // Define a listener for the "message" event in the second namespace. chat2namespace.addEventListener("message", ChatObject.class, new DataListener<ChatObject>() { @Override public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) { // Broadcast the message to all clients in the second namespace. chat2namespace.getBroadcastOperations().sendEvent("message", data); } }); // Start the SocketIOServer. server.start(); // Sleep indefinitely to keep the application running. Thread.sleep(Integer.MAX_VALUE); // Stop the SocketIOServer. server.stop(); } ```
Schwer zu sagen, was hier besser ist. Ich persönlich nutze top_k nur selten, da ich lieber mit temperature und top_p arbeite. Der top_k Parameter begrenzt die Anzahl der Wörter, die das Modell bei der Auswahl des nächsten Tokens berücksichtigt während der top_p Parameter eine Wahrscheinlichkeitsgrenze beeinflusst. Das Modell berücksichtigt also Wörter, bis die kumulative Wahrscheinlichkeit den festgelegten Schwellenwert erreicht. In der Praxis hängt die Wahl zwischen top_k und top_p von dem spezifischen Anwendungsfall ab. Für eine ausgewogene Mischung aus Präzision und Vielfalt kann man die beiden auch miteinander kombinieren.
Test length_penalty
Für den length_penalty habe ich das Model jeweils 10-mal mit einem length_penalty = 10 und 10-mal mit einem length_penalty = 0.1 laufen lassen und die Anzahl der Token gezählt.
Das Ergebnis ist:
Parameter | Anz. Token Min | Anz. Token Max | Anz. Token Mean |
length_penalty=0.1 | 828 | 943 | 895,4 |
length_penalty=10 | 997 | 2461 | 1533,7 |
Wenn der Wert des length_penalty groß ist, werden die erzeugten Texte tendenziell länger. Dies liegt daran, dass der Parameter die Länge der generierten Sequenzen reguliert. Ein höherer Wert führt dazu, dass das Modell längere Sätze bevorzugt, während ein niedrigerer Wert kürzere Sätze bevorzugt.
Fazit
Die Optimierung von Large Language Models (LLMs) erfordert die Feinabstimmung und Konfiguration verschiedener Parameter, um das gewünschte Ergebnis zu erzielen. In dem Post habe ich die wichtigsten Parameter anhand einiger einfacher Beispiele demonstriert:
temperature:
- Die Temperatur beeinflusst die Kreativität des generierten Texts.
- Ein höherer Wert (typischerweise zwischen 0 und 2) macht den Text zufälliger und kreativer.
- Ein niedriger Wert macht den Text deterministischer, indem er das wahrscheinlichste nächste Wort auswählt.
top-k
- Steuert die Wortauswahl während der Textgenerierung.
- Begrenzt die Auswahl auf die top-k wahrscheinlichsten Wörter gemäß den Modellvorhersagen.
- Verbessert die Kohärenz des generierten Texts und vermeidet seltene oder kontextuell irrelevante Wörter.
length_penalty
- Reguliert die Länge der generierten Sequenzen.
- Ein höherer Wert fördert längere Sätze, während ein niedrigerer Wert kürzere Sätze bevorzugt.
- Die Wahl hängt von der gewünschten Ausgabe ab: längere Texte vs. präzisere.
Top-p
- Ähnlich wie Top-k, aber basierend auf der kumulativen Wahrscheinlichkeit der Wörter.
Die Optimierung dieser Parameter ist entscheidend, um LLMs effizient, präzise und anwendungsspezifisch zu gestalten.