B.1: Model – Umsetzen des Generativen Sprachmodells#

Das Model repräsentiert die Daten und die Logik der Anwendung. Es enthält Daten und Methoden, die verwendet werden, um die Daten zu verwalten und zu aktualisieren. Ein Modell wird häufig von einer Datenbank abstrahiert und kann aus mehreren Klassen bestehen. Es ist unabhängig von Ansichten und der Ablaufsteuerung.

In diesem Teil der Aufgabe sollen sie zuerst das bestehende Programm aus Task A zur Generierung von Sprache in einem Modell neu refaktorisieren. In Task A haben wir verschiedene ChatBots realisiert. Als einfachstes Beispiel ist ein ngram-Bot vorgegeben worden (siehe da auch Musterlösung). In dem Modell soll folgendes vorgehalten werden:

  • das Generative Modell – dies sind die ausgezählten Wahrscheinlichkeiten als eine Map gespeichert, hierin spiegelt sich die Logik wieder, welches Wort mit welcher Wahrscheinlichkeit auf ein anderes folgt;

  • Methoden, um ein neues Wort zu generieren – abhängig von dem aktuellen Wort und dann entsprechend ein wahrscheinliches Wort ausgewählt (gesampled);

  • Hilfsfuktionen, um eine Textdatei einzulesen und aus dieser die Wahrscheinlichkeits-/Häufigkeitsmap auszuzählen;

  • und schliesslich wollen wir in dem Modell einen internen Zustand mit ablegen, der das zuletzt ausgegebene Wort darstellt.

Diese Funktionalitäten sind zu großen Teilen schon in ngram_bot.cpp implementiert worden und müssen nun in die Modellstruktur eingepasst werden. Siehe hierzu unter Task B 1 und dort in model_ngram_chatbot.h und model_ngram_chatbot.cpp

Grundlegende Struktur#

#ifndef MODEL_NGRAM_CHATBOT_H_ // header guard to prevent multiple inclusions of the same header file
#define MODEL_NGRAM_CHATBOT_H_

#include <string>
#include <map>
#include <random>
#include <vector>

/**
 * TODO
 * - refactor your code from Task A (the part for counting bigrams, ngram_bot) into this model;
 *      you can reuse your (or the example) solutions from count_ngram
 * - introduce a state into the model = what is the current word?
 *      plus access functions
 */

// Define the ChatBotModel class
class ChatBotModel {
public:
    ChatBotModel(); // constructor

    // Function provided that is using the read and the old count functions.
    void loadTextAndComputeBigrams(const std::string& filename);

    // TODO 1: Access functions for current word:
    std::string getCurrentWord();
    void setCurrentWord(const std::string& new_word);

    // TODO 2: Build a function that is generating a single next word for
    // the current set word
    // Further - introduce this method in the methods below ...
    // std::string generateNextWord();

    // Method to get a sentence completion for a given start word
    std::string getSentenceCompletion(const std::string& start_word);

    // Generate a sentence completion using the internal current word
    std::string generateSentence();

private:
    // Random number utilities
    std::random_device rd;
    std::mt19937 gen;

    // n-gram data storage
    std::map<std::string, std::map<std::string, int>> count_ngram;

    // TODO 1: Current word state
    WHICH_TYPE? currentWord;

    // Private methods for internal utility (taken from old task A)
    std::string sampleFromMapOfProbabilities(const std::map<std::string, int>& probabilities);
    std::string returnMostProbableMapEntry(const std::map<std::string, int>& prob_map);

    // Utility to check delimiters
    bool isDelimiterOrStopChar(char ch);
};

#endif // MODEL_NGRAM_CHATBOT_H_
#include "model_ngram_chatbot.h"
#include <iostream>
#include <limits>
#include <fstream>
#include <sstream>
#include <vector>
#include "read_file.h"
#include "count_ngram.h"

// Implementing the different methods for the class.

// Constructor for class:
// Initializes the random number generator
ChatBotModel::ChatBotModel() : gen(rd()) {}

// TODO 1:
// Access functions for current word:
WHICH_TYPE? ChatBotModel::getCurrentWord() {
    return ... // TODO
}

void ChatBotModel::setCurrentWord(const std::string& new_word ) {
    // TODO
}

// Refactored from old code.
void ChatBotModel::loadTextAndComputeBigrams(const std::string& filename) {
    // Read text from file and remove punctuation
    std::vector<std::string> full_text = readTextFromFile(filename);
    // Generate bigram counts from text
    count_ngram = countBiGramNestedMapFromText(full_text);
}

// TODO 1: Refactor the code of ngram_bot.cpp into this:
std::string ChatBotModel::sampleFromMapOfProbabilities(const std::map<std::string, int>& probabilities) {
    ...
}

// TODO: Refactor the code of ngram_bot.cpp into this:
std::string ChatBotModel::returnMostProbableMapEntry(const std::map<std::string, int>& prob_map) {
    ...
}

// TODO 1: Refactor the code of ngram_bot.cpp into this:
// Important - there this was handled differently in one function
// = askChatBotForAnswer
// You can start from this function
/** 
 std::string askChatBotForAnswer( const std::map<std::string, std::map<std::string, int>>& count_ngram, const std::string& userInput ) {
  std::string currentToken;
  for (auto &ch : userInput) {
    if (!isDelimiterOrStopChar(ch)) {
      currentToken += ch;
    }
  }
  std::string returnSentence;
  for (int i=0; i < PREDICT_LENGTH; i++) {
    auto iter = count_ngram.find( currentToken );
    if (iter == count_ngram.end()) {
      std::cout << "Word was not present in training data: " << userInput << std::endl;
      return "NO TEXT GENERATED";
    } else {
      //return returnMostProbableMapEntry( iter->second );
      currentToken = sampleFromMapOfProbabilities( iter->second );
    }
    returnSentence += currentToken + ' ';
  }
  return returnSentence;
} */
// and now have to consider how this uses the internal variables.
// a) getSentenceCompletion should simply set the current state 
//    and call the generateSentence function on this
std::string ChatBotModel::getSentenceCompletion(const std::string& start_word) {
    ...
}

// TODO 1:
// b) generate Sentence should deal with the second part of askChatBotForAnswer
//    and iterate in a lopp over producing the next word (by sampling)
std::string ChatBotModel::generateSentence() {
    ...
}


// TODO 2: Implement a function that produces the next word based on the current word.
// Generate the next word based on the current state
// Last: Use this method in the other calls above.
/**std::string ChatBotModel::generateNextWord()
{
    if (count_ngram.find(currentWord) == count_ngram.end()) {
        return "NO TEXT GENERATED"; // No continuation possible
    }
    currentWord = sampleFromMapOfProbabilities(count_ngram[currentWord]);
    return currentWord;
}*/

// Utility function to determine if a character is a delimiter or a stop character
bool ChatBotModel::isDelimiterOrStopChar(char c) {
	return strchr( getDefaultDelimitersAndStopChars(), c ) != NULL;
    //return ispunct(ch) || isspace(ch);
}

Anweisungen

  • Arbeiten sie an der Implementierung des Modells – im src Verzeichnis definiert in model_ngram_chatbot.cpp und im include an der Header Datei.

  • Refactoring des Codes für die Bigramm-Zählung in das Modell: Identifizieren sie den Teil des Codes aus der vorherigen Aufgabe (Task A, ngram_bot.cpp), der sich mit der Zählung von Bigrammen befasst und verschieben sie diese Funktionalität in das ChatBotModel.

  • Fügen sie dem Modell eine private Variable hinzu, die das aktuelle Wort vorhält. Implementieren Sie öffentliche Funktionen (Zugriffsfunktionen), um das aktuelle Wort abzurufen oder zu setzen.

  • Führen sie dann eine Funktion ein, die basierend auf dem aktuellen Zustand / Wort ein nächstes Wort sampled (auswählt über die vorher bestimmten Folgewahrscheinlichkeiten).

Ergebnis, Fragen#

  • Kompilieren sie das main-Program über make und testen einmal die Wortgenerierung.

  • Schauen sie sich einmal auch den Aufruf in der main-Methode an.

Referenzen#