def fit(self):
        # initializing the classifier
        self.__classifier = KNNClassifier(self.__nn, self.__metric)

        # relative bow-matrices by category (weighted)
        rel_category_bow_dict = {
            cat: self.__wght(self.category_bow_dict[cat])
            for cat in self.category_bow_dict
        }

        # initializing the cross-validator (5 folds as in main)
        n_folds = 4
        self.__cross_validator = CrossValidation(
            category_bow_dict=rel_category_bow_dict, n_folds=n_folds)
示例#2
0
def aufgabe2():

    # Laden des Brown Corpus
    CorpusLoader.load()
    brown = CorpusLoader.brown_corpus()
    brown_categories = brown.categories()

    # In dieser Aufgabe sollen unbekannte Dokumente zu bekannten Kategorien
    # automatisch zugeordnet werden.
    #
    # Die dabei erforderlichen numerischen Berechnungen lassen sich im Vergleich
    # zu einer direkten Implementierung in Python erheblich einfacher mit
    # NumPy / SciPy durchfuehren. Die folgende Aufgabe soll Ihnen die Unterschiede
    # anhand eines kleinen Beispiels verdeutlichen.
    #
    # Geben Sie fuer jede Katgorie des Brown Corpus die durchschnittliche Anzahl
    # von Woertern pro Dokument aus. Bestimmen Sie auch die Standardabweichung.
    # Stellen Sie diese Statistik mit einem bar plot dar. Verwenden Sie dabei
    # auch Fehlerbalken (siehe visualization.hbar_plot)
    #
    # Berechnen Sie Mittelwert und Standardabweichung jeweils:
    #
    #  - nur mit Python Funktion
    #    hilfreiche Funktionen: sum, float, math.sqrt, math.pow
    #
    #  - mit NumPy
    #    hilfreiche Funktionen: np.array, np.mean, np.std
    #
    # http://docs.python.org/2/library/math.html
    # http://wiki.scipy.org/Tentative_NumPy_Tutorial
    # http://docs.scipy.org/doc/numpy/reference/generated/numpy.mean.html
    # http://docs.scipy.org/doc/numpy/reference/generated/numpy.std.html

    for cat in brown_categories:
        # python standard libs only
        # calculating the mean
        doc_words = 0
        docs = brown.fileids(categories=cat)
        n = float(len(docs))
        for doc in docs:
            doc_words += len(brown.words(doc))
        mean = float(doc_words) / n
        # claculating the std
        std = 0.
        for doc in docs:
            std += math.pow(float(len(brown.words(doc))) - mean, 2)
        std = math.sqrt(std / n)

        # using numpy
        arr_doc_words = np.array([
            len(brown.words(doc)) for doc in brown.fileids(categories=cat)
        ]).astype(float)
        np_std = np.std(arr_doc_words)

        # printing the std for each category
        print("{}\npython: {}, mean={}\nnp:{}, mean={}".format(
            cat, std, mean, np_std, np.mean(arr_doc_words)))

    # ********************************** ACHTUNG **************************************
    # Die nun zu implementierenden Funktionen spielen eine zentrale Rolle im weiteren
    # Verlauf des Fachprojekts. Achten Sie auf eine effiziente und 'saubere' Umsetzung.
    # Verwenden Sie geeignete Datenstrukturen und passende Python Funktionen.
    # Wenn Ihnen Ihr Ansatz sehr aufwaendig vorkommt, haben Sie vermutlich nicht die
    # passenden Datenstrukturen / Algorithmen / (highlevel) Python / NumPy Funktionen
    # verwendet. Fragen Sie in diesem Fall!
    #
    # Schauen Sie sich jetzt schon gruendlich die Klassen und deren Interfaces in den
    # mitgelieferten Modulen an. Wenn Sie Ihre Datenstrukturen von Anfang an dazu
    # passend waehlen, erleichtert dies deren spaetere Benutzung. Zusaetzlich bieten
    # diese Klassen bereits etwas Inspiration fuer Python-typisches Design, wie zum
    # Beispiel Duck-Typing.
    #
    # Zu einigen der vorgebenen Intefaces finden Sie Unit Tests in dem Paket 'test'.
    # Diese sind sehr hilfreich um zu ueberpruefen, ob ihre Implementierung zusammen
    # mit anderen mitgelieferten Implementierungen / Interfaces funktionieren wird.
    # Stellen Sie immer sicher, dass die Unit tests fuer die von Ihnen verwendeten
    # Funktionen erfolgreich sind.
    # Hinweis: Im Verlauf des Fachprojekts werden die Unit Tests nach und nach erfolg-
    # reich sein. Falls es sie zu Beginn stoert, wenn einzelne Unit Tests fehlschlagen
    # koennen Sie diese durch einen 'decorator' vor der Methodendefinition voruebergehend
    # abschalten: @unittest.skip('')
    # https://docs.python.org/2/library/unittest.html#skipping-tests-and-expected-failures
    # Denken Sie aber daran sie spaeter wieder zu aktivieren.
    #
    # Wenn etwas unklar ist, fragen Sie!
    # *********************************************************************************

    #
    # Klassifikation von Dokumenten
    #
    # Nachdem Sie sich nun mit der Struktur und den Eigenschaften des Brown
    # Corpus vertraut gemacht haben, soll er die Datengrundlage fuer die
    # Evaluierung von Algorithmen zur automatischen Klassifikation von
    # Dokumenten bilden.
    # In der Regel bestehen diese Algorithmen aus drei Schritten:
    #  - Vorverarbeitung
    #  - Merkmalsberechnung
    #  - Klassifikation
    #
    # Bei der Anwendung auf Dokumente (Texte) werden diese Schritte wie folgt
    # umgesetzt:
    #  - Vorverarbeitung: Filterung von stopwords und Abbildung von Woertern auf
    #                     Wortstaemme.
    #  - Merkmalsberechnung: Jedes Dokument wird numerisch durch einen Vektor
    #                        repraesentiert (--> NumPy), der moeglichst die
    #                        bzgl. der Klassifikation bedeutungsunterscheidenden
    #                        Informationen enthaehlt.
    #  - Klassifikation: Jedem Merkmalsvektor (Dokument) wird ein Klassenindex
    #                    (Kategorie) zugeordnet.
    #
    # Details finden Sie zum Beispiel in:
    # http://www5.informatik.uni-erlangen.de/fileadmin/Persons/NiemannHeinrich/klassifikation-von-mustern/m00-www.pdf (section 1.3)
    #
    # Eine sehr verbreitete Merkmalsrepraesentation fuer (textuelle) Dokumente sind
    # sogenannte Bag-of-Words.
    # Dabei wird jedes Dokument durch ein Histogram (Verteilung) ueber Wortfrequenzen
    # repraesentiert. Man betrachtet dazu das Vorkommen von 'typischen' Woertern, die
    # durch ein Vokabular gegeben sind.
    #
    # Bestimmen Sie ein Vokabular, also die typischen Woerter, fuer den Brown Corpus.
    # Berechnen Sie dazu die 500 haeufigsten Woerter (nach stemming und Filterung von
    # stopwords und Satzzeichen)

    normalizer = WordListNormalizer()
    normalized_words = normalizer.normalize_words(brown.words())
    vocabulary = BagOfWords.most_freq_words(normalized_words[1], 500)
    print(vocabulary)
    print('finished vocab')

    # Berechnen Sie Bag-of-Words Repraesentationen fuer jedes Dokument des Brown Corpus.
    # Verwenden Sie absolute Frequenzen.
    # Speichern Sie die Bag-of-Word Repraesentationen fuer jede Kategorie in einem
    # 2-D NumPy Array. Speichern Sie den Bag-of-Words Vektor fuer jedes Dokument in
    # einer Zeile, so dass das Array (ndarray) folgende Dimension hat:
    #   |Dokument_kat| X |Vokabular|
    #
    # |Dokument_kat| entspricht der Anzahl Dokumente einer Kategorie.
    # |Vokabular| entspricht der Anzahl Woerter im Vokabular (hier 500).
    #
    # Eine einfache Zuordnung von Kategorie und Bag-of-Words Matrix ist durch ein
    # Dictionary moeglich.
    #
    # Implementieren Sie die Funktion BagOfWords.category_bow_dict im Modul features.
    bow = BagOfWords(vocabulary)

    cat_word_dict = {
        cat: [brown.words(doc) for doc in brown.fileids(categories=cat)]
        for cat in brown_categories
    }
    print('calculating cat_to_bow')
    cat_to_bow = bow.category_bow_dict(cat_word_dict)
    print(cat_to_bow)

    # Um einen Klassifikator statistisch zu evaluieren, benoetigt man eine Trainingsstichprobe
    # und eine Teststichprobe der Daten die klassifiziert werden sollen.
    # Die Trainingsstichprobe benoetigt man zum Erstellen oder Trainieren des Klassifikators.
    # Dabei werden in der Regel die Modellparameter des Klassifikators statistisch aus den
    # Daten der Traingingsstichprobe geschaetzt. Die Klassenzugehoerigkeiten sind fuer die
    # Beispiele aus der Trainingsstichprobe durch so genannte Klassenlabels gegeben.
    #
    # Nachdem man den Klassifikator trainiert hat, interessiert man sich normalerweise dafuer
    # wie gut er sich unter realen Bedingung verhaelt. Das heisst, dass der Klassifikator bei
    # der Klassifikation zuvor unbekannter Daten moeglichst wenige Fehler machen soll.
    # Dies simuliert man mit der Teststichprobe. Da auch fuer jedes Beispiel aus der Teststichprobe
    # die Klassenzugehoerigkeit bekannt ist, kann man am Ende die Klassifikationsergebnisse mit
    # den wahren Klassenlabels (aus der Teststichprobe) vergleichen und eine Fehlerrate angeben.

    # In dem gegebenen Brown Corpus ist keine Aufteilung in Trainings und Testdaten vorgegeben.
    #
    # Waehlen Sie daher die ersten 80% der Dokumente UEBER ALLE KATEGORIEN als Trainingstichprobe
    # und die letzten 20% der Dokumente UEBER ALLE KATEGORIEN als Teststichprobe.
    #
    # Erklaeren Sie, warum Sie die Stichproben ueber alle Kategorien zusammenstellen MUESSEN.
    #
    # Bitte beachten Sie, dass wir im Rahmen des Fachprojekts keinen Test auf unbekannten Testdaten
    # simulieren. Wir haben ja bereits fuer die Erstellung der Vokabulars (haeufigste Woerter,
    # siehe oben) den kompletten Datensatz verwendet.
    # Stattdessen betrachten wir hier ein so genanntes Validierungsszenario, in dem wir die
    # Klassifikationsleistung auf dem Brown Corpus optimieren. Die Ergebnisse lassen sich somit
    # nur sehr bedingt auf unbekannte Daten uebertragen.
    #
    # Erstellen Sie nun die NumPy Arrays train_samples, train_labels, test_samples und test_labels,
    # so dass diese mit den estimate und classify Methoden der Klassen im classificaton Modul
    # verwendet werden koennen. Teilen Sie die Daten wie oben angegeben zu 80% in
    # Trainingsdaten und 20% in Testdaten auf.
    #
    # Hinweis: Vollziehen Sie nach, wie die Klasse CrossValidation im evaluation Modul
    # funktioniert. Wenn Sie moechten, koennen die Klasse zur Aufteilung der Daten
    # verwenden.

    cross_validator = CrossValidation(cat_to_bow, 5)
    train_samples, train_labels, test_samples, test_labels = cross_validator.corpus_fold(
        0)
    print('finished cross validation')

    # Klassifizieren Sie nun alle Dokumente der Teststichprobe nach dem Prinzip des k-naechste-
    # Nachbarn Klassifikators. Dabei wird die Distanz zwischen dem Merkmalsvektors eines
    # Testbeispiels und allen Merkmalsvektoren aus der Trainingstichprobe berechnet. Das
    # Klassenlabel des Testbeispiels wird dann ueber einen Mehrheitsentscheid der Klassenlabels
    # der k aehnlichsten Merkmalsvektoren aus der Trainingsstichprobe bestimmt.
    # http://www5.informatik.uni-erlangen.de/fileadmin/Persons/NiemannHeinrich/klassifikation-von-mustern/m00-www.pdf (Abschnitt 4.2.7)
    #
    # Bestimmen Sie die Distanzen von Testdaten zu Trainingsdaten mit cdist:
    # http://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html
    # Bestimmen Sie die k-naechsten Nachbarn auf Grundlage der zuvor berechneten
    # Distanzen mit argsort:
    # http://docs.scipy.org/doc/numpy/reference/generated/numpy.argsort.html
    # Ueberlegen Sie, welche zuvor von Ihnen implementierte Funktion Sie wiederverwenden
    # koennen, um den Mehrheitsentscheid umzusetzen.
    #
    # Implementieren Sie die Funktionen estimate und classify in der Klasse KNNClassifier
    # im Modul classification.
    #
    # Verwenden Sie die Euklidische Distanz und betrachten Sie zunaechst nur
    # den naechsten Nachbarn (k=1).
    #
    # HINWEIS: Hier ist zunaechst nur die Implementierung eines naechster Nachbar
    # Klassifikators erforderlich. Diese soll aber in der naechsten Aufgabe zu einer
    # Implementierung eines k-naechste Nachbarn Klassifikators erweitert werden.
    # Beruechsichtigen Sie das in ihrer Implementierung. Um die beiden Varianten
    # zu testen, finden Sie zwei Unit-Test Methoden test_nn und test_knn in dem Modul
    # test.classification_test
    #
    knn_1 = KNNClassifier(1, 'euclidean')
    knn_1.estimate(train_samples, train_labels)

    nearest_1 = knn_1.classify(test_samples)
    print(nearest_1)
    print('finished classification')

    # Nachdem Sie mit dem KNNClassifier fuer jedes Testbeispiel ein Klassenlabel geschaetzt haben,
    # koennen Sie dieses mit dem tatsaechlichen Klassenlabel vergleichen. Dieses koennen Sie wie
    # bei den Traingingsdaten dem Corpus entnehmen.
    #
    # Ermitteln Sie eine Gesamtfehlerrate und je eine Fehlerrate pro Kategorie.
    # Implementieren Sie dazu die Klasse ClassificationEvaluator im evaluation Modul.
    #
    # Warum ist diese Aufteilung der Daten in Training und Test problematisch? Was sagen die Ergebnisse aus?
    evaluator = ClassificationEvaluator(nearest_1, test_labels)

    cat_err_rates = evaluator.category_error_rates()
    print(evaluator.error_rate(None))
    print(cat_err_rates)
    print('done')
示例#3
0
def aufgabe3():

    # ********************************** ACHTUNG **************************************
    # Die nun zu implementierenden Funktionen spielen eine zentrale Rolle im weiteren
    # Verlauf des Fachprojekts. Achten Sie auf eine effiziente und 'saubere' Umsetzung.
    # Verwenden Sie geeignete Datenstrukturen und passende Python Funktionen.
    # Wenn Ihnen Ihr Ansatz sehr aufwaendig vorkommt, haben Sie vermutlich nicht die
    # passenden Datenstrukturen / Algorithmen / (highlevel) Python / NumPy Funktionen
    # verwendet. Fragen Sie in diesem Fall!
    #
    # Schauen Sie sich jetzt schon gruendlich die Klassen und deren Interfaces in den
    # mitgelieferten Modulen an. Wenn Sie Ihre Datenstrukturen von Anfang an dazu
    # passend waehlen, erleichtert dies deren spaetere Benutzung. Zusaetzlich bieten
    # diese Klassen bereits etwas Inspiration fuer Python-typisches Design, wie zum
    # Beispiel Duck-Typing.
    #
    # Zu einigen der vorgebenen Intefaces finden Sie Unit Tests in dem Paket 'test'.
    # Diese sind sehr hilfreich um zu ueberpruefen, ob ihre Implementierung zusammen
    # mit anderen mitgelieferten Implementierungen / Interfaces funktionieren wird.
    # Stellen Sie immer sicher, dass die Unit tests fuer die von Ihnen verwendeten
    # Funktionen erfolgreich sind.
    # Hinweis: Im Verlauf des Fachprojekts werden die Unit Tests nach und nach erfolg-
    # reich sein. Falls es sie zu Beginn stoert, wenn einzelne Unit Tests fehlschlagen
    # koennen Sie diese durch einen 'decorator' vor der Methodendefinition voruebergehend
    # abschalten: @unittest.skip('')
    # https://docs.python.org/2/library/unittest.html#skipping-tests-and-expected-failures
    # Denken Sie aber daran sie spaeter wieder zu aktivieren.
    #
    # Wenn etwas unklar ist, fragen Sie!
    # *********************************************************************************

    print('loading brown')
    CorpusLoader.load()
    brown = CorpusLoader.brown_corpus()

    # Um eine willkuerliche Aufteilung der Daten in Training und Test zu vermeiden,
    # (machen Sie sich bewusst warum das problematisch ist)
    # verwendet man zur Evaluierung von Klassifikatoren eine Kreuzvalidierung.
    # Dabei wird der gesamte Datensatz in k disjunkte Ausschnitte (Folds) aufgeteilt.
    # Jeder dieser Ausschnitte wird einmal als Test Datensatz verwendet, waehrend alle
    # anderen k-1 Ausschnitte als Trainings Datensatz verwendet werden. Man erhaehlt also
    # k Gesamtfehlerraten und k klassenspezifische Fehlerraten ide man jeweils zu einer
    # gemeinsamen Fehlerrate fuer die gesamte Kreuzvalidierung mittelt. Beachten Sie,
    # dass dabei ein gewichtetes Mittel gebildet werden muss, da die einzelnen Test Folds
    # nicht unbedingt gleich gross sein muessen.

    # Fuehren Sie aufbauend auf den Ergebnissen aus aufgabe2 eine 5-Fold Kreuzvalidierung
    # fuer den k-Naechste-Nachbarn Klassifikator auf dem Brown Corpus durch. Dazu koennen
    # Sie die Klasse CrossValidation im evaluation Modul verwenden.
    #
    # Vollziehen Sie dazu nach wie die Klasse die Daten in Trainging und Test Folds aufteilt.
    # Fertigen Sie zu dem Schema eine Skizze an. Diskutieren Sie Vorteile und Nachteile.
    # Schauen Sie sich an, wie die eigentliche Kreuzvalidierung funktioniert. Erklaeren Sie
    # wie das Prinzip des Duck-Typing hier angewendet wird.
    #
    # Hinweise:
    #
    # Die Klasse CrossValidator verwendet die Klasse ClassificationEvaluator, die Sie schon
    # fuer aufgabe2 implementieren sollten. Kontrollieren Sie Ihre Umsetzung im Sinne der
    # Verwendung im CrossValidator.
    #
    # Fuer das Verstaendnis der Implementierung der Klasse CrossValidator ist der Eclipse-
    # Debugger sehr hilfreich.

    brown_categories = brown.categories()

    n_neighbours = 1
    metric = 'euclidean'
    classifier = KNNClassifier(n_neighbours, metric)

    normalizer = WordListNormalizer()
    normalized_words = normalizer.normalize_words(brown.words())
    vocabulary = BagOfWords.most_freq_words(normalized_words[1], 500)
    bow = BagOfWords(vocabulary)
    cat_word_dict = {
        cat: [brown.words(doc) for doc in brown.fileids(categories=cat)]
        for cat in brown_categories
    }

    n_folds = 5
    category_bow_dict = bow.category_bow_dict(cat_word_dict)
    cross_validator = CrossValidation(category_bow_dict=category_bow_dict,
                                      n_folds=n_folds)

    crossval_overall_result, crossval_class_results = cross_validator.validate(
        classifier)
    print("ran cross validation for {}-nearest neighbour".format(n_neighbours))
    print(crossval_overall_result)
    print(crossval_class_results)

    # Bag-of-Words Weighting
    #
    # Bisher enthalten die Bag-of-Words Histogramme absolute Frequenzen.
    # Dadurch sind die Repraesentationen abhaengig von der absoluten Anzahl
    # von Woertern in den Dokumenten.
    # Dies kann vermieden werden, in dem man die Bag-of-Words Histogramme mit
    # einem Normalisierungsfaktor gewichtet.
    #
    # Normalisieren Sie die Bag-of-Words Histogramme so, dass relative Frequenzen
    # verwendet werden. Implementieren und verwenden Sie die Klasse RelativeTermFrequencies
    # im features Modul.
    #
    # Wie erklaeren Sie das Ergebnis? Schauen Sie sich dazu noch einmal die
    # mittelere Anzahl von Woertern pro Dokument an (aufgabe2).
    #
    # Wie in der Literatur ueblich, verwenden wir den
    # Begriff des "Term". Ein Term bezeichnet ein Wort aus dem Vokabular ueber
    # dem die Bag-of-Words Histogramme gebildet werden. Ein Bag-of-Words Histogramm
    # wird daher auch als Term-Vektor bezeichnet.

    rel_category_bow_dict = {
        cat: RelativeTermFrequencies.weighting(category_bow_dict[cat])
        for cat in category_bow_dict
    }

    cross_validator = CrossValidation(category_bow_dict=rel_category_bow_dict,
                                      n_folds=n_folds)
    crossval_overall_result, crossval_class_results = cross_validator.validate(
        classifier)
    print("ran cross validation for {}-nearest neighbour (relative)".format(
        n_neighbours))
    print(crossval_overall_result)
    print(crossval_class_results)

    # Zusaetzlich kann man noch die inverse Frequenz von Dokumenten beruecksichtigen
    # in denen ein bestimmter Term vorkommt. Diese Normalisierung wird als
    # inverse document frequency bezeichnet. Die Idee dahinter ist Woerter die in
    # vielen Dokumenten vorkommen weniger stark im Bag-of-Words Histogramm zu gewichten.
    # Die zugrundeliegende Annahme ist aehnlich wie bei den stopwords (aufgabe1), dass
    # Woerter, die in vielen Dokumenten vorkommen, weniger Bedeutung fuer die
    # Unterscheidung von Dokumenten in verschiedene Klassen / Kategorien haben als
    # Woerter, die nur in wenigen Dokumenten vorkommen.
    # Diese Gewichtung laesst sich statistisch aus den Beispieldaten ermitteln.
    #
    # Zusammen mit der relativen Term Gewichtung ergibt sich die so genannte
    # "term frequency inverse document frequency"
    #
    #                            Anzahl von term in document                       Anzahl Dokumente
    # tfidf( term, document )  = ----------------------------   x   log ( ---------------------------------- )
    #                             Anzahl Woerter in document              Anzahl Dokumente die term enthalten
    #
    # http://www.tfidf.com
    #
    # Eklaeren Sie die Formel. Plotten Sie die inverse document frequency fuer jeden
    # Term ueber dem Brown Corpus.
    #
    # Implementieren und verwenden Sie die Klasse RelativeInverseDocumentWordFrequecies
    # im features Modul, in der Sie ein tfidf Gewichtungsschema umsetzen.
    # Ermitteln Sie die Gesamt- und klassenspezifischen Fehlerraten mit der Kreuzvalidierung.
    # Vergleichen Sie das Ergebnis mit der absolten und relativen Gewichtung.
    # Erklaeren Sie die Unterschiede in den klassenspezifischen Fehlerraten. Schauen Sie
    # sich dazu die Verteilungen der Anzahl Woerter und Dokumente je Kategorie aus aufgabe1
    # an. In wie weit ist eine Interpretation moeglich?

    tfidf = RelativeInverseDocumentWordFrequecies(vocabulary, cat_word_dict)
    rel_category_bow_dict = {
        cat: tfidf.weighting(category_bow_dict[cat])
        for cat in category_bow_dict
    }

    cross_validator = CrossValidation(category_bow_dict=rel_category_bow_dict,
                                      n_folds=n_folds)
    crossval_overall_result, crossval_class_results = cross_validator.validate(
        classifier)
    print("ran cross validation for {}-nearest neighbour (relative-inverse)".
          format(n_neighbours))
    print(crossval_overall_result)
    print(crossval_class_results)

    # Evaluieren Sie die beste Klassifikationsleistung
    #
    # Ermitteln Sie nun die Parameter fuer die beste Klassifikationsleistung des
    # k-naechste-Nachbarn Klassifikators auf dem Brown Corpus mit der Kreuzvalidierung.
    # Dabei wird gleichzeitig immer nur ein Parameter veraendert. Man hat eine lokal
    # optimale Parameterkonfiguration gefunden, wenn jede Aenderung eines Parameters
    # zu einer Verschlechterung der Fehlerrate fuehrt.
    #
    # Erlaeutern Sie warum eine solche Parameterkonfiguration lokal optimal ist.
    #
    # Testen Sie mindestens die angegebenen Werte fuer die folgenden Parameter:
    # 1. Groesse des Vokabulars typischer Woerter (100, 500, 1000, 2000)
    # 2. Gewichtung der Bag-of-Words Histogramme (absolute, relative, relative with inverse document frequency)
    # 3. Distanzfunktion fuer die Bestimmung der naechsten Nachbarn (Cityblock, Euclidean, Cosine)
    # 4. Anzahl der betrachteten naechsten Nachbarn (1, 2, 3, 4, 5, 6)
    #
    # Erklaeren Sie den Effekt aller Parameter.
    #
    # Erklaeren Sie den Effekt zwischen Gewichtungsschema und Distanzfunktion.

    # vocabulary sizes as specified
    voc_sizes = (100, 500, 1000, 2000)
    # weightings as specified
    weightings = ('abs', 'rel', 'tfidf')
    # metrices as specified
    dists = ("cityblock", "euclidean", "cosine")
    # numbers of neighbours as specified
    neighbours = (1, 2, 3, 4, 5, 6)

    # indices of best options, to keep track
    idx_vs = 0
    idx_wghts = 0
    idx_dsts = 0
    idx_nn = 0

    normalizer = WordListNormalizer()
    cat_word_dict = normalizer.category_wordlists_dict(corpus=brown)
    # Flatten the category word lists for computing overall word frequencies
    # The * operator expands the list/iterator to function arguments
    # itertools.chain concatenates all its parameters to a single list
    print 'Building Bag-of-Words vocabulary...'
    wordlists = itertools.chain(*(cat_word_dict.itervalues()))
    normalized_words = itertools.chain(*wordlists)
    vocabulary = BagOfWords.most_freq_words(normalized_words)

    #     print("normalizing")
    #     # normalizing
    #     normalizer = WordListNormalizer()
    #     # normalized words
    #     normalized_words = normalizer.normalize_words(brown.words())[1]
    #     print("normalized words")
    #     # normalized wordlists
    #     cat_word_dict = normalizer.category_wordlists_dict(brown)
    #     print("normalized wordlists")
    #
    #     # initializing the vocabulary at maximum size and then taking slices
    #     vocabulary = BagOfWords.most_freq_words(normalized_words, 2000)

    #     print('initializing configuration')
    #
    #     exp_config = ExpConfigurations(vocabulary ,cat_word_dict,
    #                                    voc_sizes, weightings, dists, neighbours)
    #     print(exp_config.run())
    #     for row in exp_config.__log:
    #         print(row)

    config = Configuration(brown, cat_word_dict, vocabulary, voc_sizes[idx_vs],
                           dists[idx_dsts], neighbours[idx_nn],
                           weightings[idx_wghts])
    config.fit()
    err_overall = config.eval()[0]

    # list of configuration results
    config_table = [['voc_sizes', 'metric', 'neighbours', 'weightings', 'err'],
                    [
                        voc_sizes[idx_vs], dists[idx_dsts], neighbours[idx_nn],
                        weightings[idx_wghts], err_overall
                    ]]

    print('calculating size')
    # local optimum for vocabulary size
    for idx, _ in enumerate(voc_sizes[1:]):
        tmp_config = Configuration(brown, cat_word_dict, vocabulary,
                                   voc_sizes[idx + 1], dists[idx_dsts],
                                   neighbours[idx_nn], weightings[idx_wghts])
        tmp_config.fit()
        tmp_err = tmp_config.eval()[0]
        if err_overall > tmp_err:
            err_overall = deepcopy(tmp_err)
            idx_vs = deepcopy(idx + 1)

        config_table.append([
            voc_sizes[idx + 1], dists[idx_dsts], neighbours[idx_nn],
            weightings[idx_wghts], tmp_err
        ])
    print(voc_sizes[idx_vs])

    print('calculating weights')
    # local optimum for weighting
    for idx, _ in enumerate(weightings[1:]):
        tmp_config = Configuration(brown, cat_word_dict, vocabulary,
                                   voc_sizes[idx_vs], dists[idx_dsts],
                                   neighbours[idx_nn], weightings[idx + 1])
        tmp_config.fit()
        tmp_err = tmp_config.eval()[0]
        if err_overall > tmp_err:
            err_overall = deepcopy(tmp_err)
            idx_wghts = deepcopy(idx + 1)

        config_table.append([
            voc_sizes[idx_vs], dists[idx_dsts], neighbours[idx_nn],
            weightings[idx + 1], tmp_err
        ])
    print(weightings[idx_wghts])

    print('calculating metric')
    # local optimum for metric
    for idx, _ in enumerate(dists[1:]):
        tmp_config = Configuration(brown, cat_word_dict, vocabulary,
                                   voc_sizes[idx_vs], dists[idx + 1],
                                   neighbours[idx_nn], weightings[idx_wghts])
        tmp_config.fit()
        tmp_err = tmp_config.eval()[0]
        if err_overall > tmp_err:
            err_overall = deepcopy(tmp_err)
            idx_dsts = deepcopy(idx + 1)

        config_table.append([
            voc_sizes[idx_vs], dists[idx + 1], neighbours[idx_nn],
            weightings[idx_wghts], tmp_err
        ])
    print(dists[idx_dsts])

    print('calculating neighbours')
    # local optimum for neighbours
    for idx, _ in enumerate(neighbours[1:]):
        tmp_config = Configuration(brown, cat_word_dict, vocabulary,
                                   voc_sizes[idx_vs], dists[idx_dsts],
                                   neighbours[idx + 1], weightings[idx_wghts])
        tmp_config.fit()
        tmp_err = tmp_config.eval()[0]
        if err_overall > tmp_err:
            err_overall = deepcopy(tmp_err)
            idx_nn = deepcopy(idx + 1)

        config_table.append([
            voc_sizes[idx_vs], dists[idx_dsts], neighbours[idx + 1],
            weightings[idx_wghts], tmp_err
        ])
    print(neighbours[idx_nn])

    # ideal should be 2000, relative, cityblock, 4
    print('local optimum at\nsize: {}\nweight: {}\nmetric: {}\nneighbours: {}'.
          format(voc_sizes[idx_vs], weightings[idx_wghts], dists[idx_dsts],
                 neighbours[idx_nn]))
    for row in config_table:
        print(row)
def aufgabe4():

    #
    # Mit dem Naechster Nachbar Klassifikator wurde ein Dokumente zu einer Klassen zugeordnet,
    # indem zunaechst aehnliche Dokumente aus einer Trainingsdatenbank ermittelt wurden.
    # Ueber die Klassenzugehoerigkeit dieser aehnlichen Dokumente aus dem Training
    # wurde dann das unbekannte Dokument einer Klasse zugeordnet.
    # Dabei wurden aber noch keine Zusammenhaenge zwischen einzelnen Woertern analysiert
    # und beruecksichtigt. Daher geht es nun um Topic Modelle. Topic Modelle beschreiben
    # diese Zusammenhaenge durch einen mathematischen Unterraum. Die Vektoren, die
    # diesen Unterraum aufspannen, sind die Topics, die jeweils fuer typische Wort-
    # konfigurationen stehen. Dokumente werden nun nicht mehr durch Frequenzen von
    # Woertern repraesentiert, sondern als Linearkombination von Topics im Topic
    # Vektorraum. Es ist zu beachten, dass fuer die Topic-Modellierung keine Informationen
    # ueber die Dokumentenkategorien benoetigt wird.
    #
    # Um ein besseres Verstaendnis fuer diese mathematischen Unterraeume zu entwickeln,
    # schauen wir uns zunaechst die Hauptkomponentenanalyse an.
    #

    # Ein 3D Beispieldatensatz wird aus einer Normalverteilung generiert.
    # Diese ist durch einen Mittelwert und eine Kovarianzmatrix definiert
    mean = np.array([10, 10, 10])
    cov = np.array([[3, .2, .9], [.2, 5, .4], [.9, .4, 9]])
    n_samples = 1000
    limits_samples = ((0, 20), (0, 20), (0, 20))
    samples = np.random.multivariate_normal(mean, cov, n_samples)
    # Plotten der Beispieldaten
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    PCAExample.plot_sample_data(samples, ax=ax)
    PCAExample.set_axis_limits(ax, limits=limits_samples)

    # In der Klasse PCAExample wird ein Unterraum mittels Hauptkomponentenanalyse
    # statistisch geschaetzt. Der Vektorraum wird beispielhaft visualisiert.
    pca_example = PCAExample(samples, target_dim=3)
    pca_example.plot_subspace(limits=limits_samples,
                              color='r',
                              linewidth=0.05,
                              alpha=0.3)
    #plt.show()

    # Nun wird die Dimension des Unterraums reduziert.
    # Implementieren Sie die Reduktion im Konstruktor von PCAExample. Der neue
    # Vektorraum wird wieder visualisiert.
    pca_example_2d = PCAExample(samples, target_dim=2)
    pca_example_2d.plot_subspace(limits=limits_samples,
                                 color='b',
                                 linewidth=0.01,
                                 alpha=0.3)

    # Transformieren Sie nun die 3D Beispieldaten in den 2D Unterraum.
    # Implementieren Sie dazu die Methode transform_samples. Die Daten werden
    # dann in einem 2D Plot dargestellt.
    #
    # Optional: Verwenden Sie Unterraeume mit Dimensionen 3, 2 und 1. Transformieren
    # und plotten Sie die Daten.

    #pca_example_1d = PCAExample(samples, target_dim=1)
    #pca_example_1d.plot_subspace(limits=limits_samples, color='g', linewidth=0.01, alpha=0.3, ellipsoid=False)
    #samples_1d = pca_example_1d.transform_samples(samples)
    #fig = plt.figure()
    #ax = fig.add_subplot(111)
    #PCAExample.plot_sample_data(samples_1d, ax=ax)
    #PCAExample.set_axis_limits(ax, limits=((-10, 10), (-10, 10)))

    # Optional: Generieren und transformieren Sie weitere 3D Beispieldaten. Benutzen Sie
    # dabei auch andere Parameter fuer die Normalverteilung.
    #
    # Optional: Visualisieren Sie die transformierten 2D Daten auch in dem vorherigen
    # 3D Plot.
    samples_2d = pca_example_2d.transform_samples(samples)
    fig = plt.figure()
    ax = fig.add_subplot(111)
    PCAExample.plot_sample_data(samples_2d, ax=ax)
    PCAExample.set_axis_limits(ax, limits=((-10, 10), (-10, 10)))

    #plt.show()

    # Berechnen Sie nun die Kovarianzmatrix der transformierten Daten.
    # Welche Eigenschaften hat diese Matrix? (Dimension, etc.)
    # In welcher Groessenordnung liegen die einzelnen Eintraege? Erklaeren Sie das
    # anhand des vorherigen 2D Plots.
    # Vergleichen Sie das Ergebnis mit der Kovarianzmatrix, die oben zur Generierung
    # der Daten verwendet wurde.
    # Was erwarten Sie fuer den Mittelwert der transformierten Daten (noetig fuer
    # die Berechnung der Kovarianzmatrix) ?
    #
    # Verwenden Sie bei der Berechnung nicht die eingebaute numpy.cov Funktion
    # (hoechstens zur Kontrolle, achten Sie dabei auf den "bias" Parameter)
    # Verwenden Sie bei der Berechnung keine Schleifen, sondern nur Matrixoperationen.
    # Erklaeren Sie die Vorgehensweise.

    samples_mean = np.sum(samples_2d, axis=0) / n_samples
    X = samples_2d - samples_mean
    samples_2d_cov = np.dot(X.T, X) / n_samples
    print(samples_2d_cov)

    #
    # Latent Semantic Indexing
    #
    # Im folgenden soll ein Topic-Raum mittels Latent Semantic Indexing verwendet
    # werden. Das Prinzip geht unmittelbar auf die Hauptkomponentenanalyse zurueck.
    # Siehe: http://lsa.colorado.edu/papers/JASIS.lsi.90.pdf (Seite 12)
    # Grundsaetzlicher Unterschied ist, dass der Unterraum nicht durch eine Eigenewert-
    # analyse der Kovarianzmatrix bestimmt wird. Stattdessen ergibt sich der Unterraum
    # aus einer Zerlegung der Term-Dokument (!) Matrix mit einer Singulaerwertzerlegung.
    # Man kann zeigen, dass diese Singulaerwertzerlegung implizit einer Eigenwert-
    # analyse einer Termkorrelationsmatrix entspricht. Deren Berechnung unterscheidet
    # sich von der Berechnung der Kovarianzmatrix insbesondere darin, dass die Daten
    # nicht vom Mittelwert befreit werden.
    # Sei t die Anzahl der Terms (Groesse des Vokabulars), d die Anzahl der Dokumente,
    # m der Rang von X (Maximale Anzahl von Topics, die sich aus X bestimmen lassen).
    # D' ist die Transponierte von D.
    #
    #   X    =    T    *    S    *    D'
    # t x d     t x m     m x m     m x d
    #
    # In Analogie zur Hauptkomponentenanalyse findet man nun die Vektoren, die
    # den Unterraum aufspannen, in den Spalten von T. Die Matrix S hat nur Eintraege
    # auf der Diagonalen und enthaelt die Singulaerwerte zu den Spaltenvektoren in
    # T. (T und D enthalten die linken respektive rechten Singulaervektoren.)
    # Die Singulaerwerte entsprechen den Eigenwerten in der Hauptkomponentenanalyse.
    # Sie sind ein Mass fuer die Variabilitaet in den einzelnen Topics. Bei D handelt
    # es sich um die Koeffizienten der d Dokumente im Topic Raum (Ergebnis der
    # Transformation von den Bag-of-Words Repraesentationen aus X in den Topic Raum.)
    #
    #
    # Aus der Singulaerwertzerlegung (Formel oben) ergibt sich, wie man einen Topic-
    # Raum statistisch aus Beispieldaten schaetzt. Um den Topic-Raum aber mit unbekannten Daten
    # zu verwenden, muessen diese in den Topic-Raum transformiert werden.
    # Stellen Sie dazu die obige Formel nach D um.
    #
    #   D    =    X'    *    T    *   S^-1
    # d x m     d x t     t x m     m x m
    #
    # Die zu transformierenden Bag-of-Words
    # Repaesentationen koennen dann fuer X eingesetzt werden. Dabei ist wichtig zu
    # beachten:
    # Die Spaltenvektoren in T sind orthonormal (zueinander) T' * T = I
    # Die Spaltenvektoren in D sind orthonormal (zueinander) D' * D = I
    # Dabei ist I die Einheitsmatrix, T' und D' sind die Transponierten in T und D.
    # Fuer Matrizen A und B gilt: (A * B)' = B' * A'
    #
    # Ueberlegen Sie wie die Transponierte einer Matrix gebildet wird und was das
    # fuer eine Matrix bedeutet, deren Eintraege nur auf der Hauptdiagonalen von
    #
    # Antwort: Die Matrix wird an der Hauptdiagonalen gespiegelt, bzw. die Zeilen und Spalten werden
    #          vertauscht. Das bedeutet für die Hauptdiagonale, dass sie bei jeder Transponierung gleich bleibt
    #
    # Erlaeutern Sie die Funktion der einzelnen Matrizen in der sich ergebenden
    # Transformationsvorschrift.
    #

    # Das Schaetzen eines Topic-Raums soll an folgendem einfachen Beispiel veranschaulicht
    # werden. Sei dazu bow_train eine Dokument-Term Matrix mit 9 Dokumenten und 3 Terms.
    # Welcher Zusammenhang zwischen den Terms faellt Ihnen auf?
    #
    # Antwort: Der zweite Term scheint nicht besonders topic bezogen und kommt in jedem Dokument mind. 1 mal vor
    # die anderen beiden Terms sind dagegen eher topic bezogen, vor allem das dritte
    bow_train = np.array([[2, 5, 0], [4, 1, 0], [3, 3, 1], [9, 8,
                                                            2], [1, 5, 3],
                          [0, 7, 9], [2, 9, 6], [0, 2, 3], [5, 3, 3]])

    # Zerlegung der Dokument-Term Matrix mit der Singulaerwertzerlegung
    T, S_arr, D_ = np.linalg.svd(bow_train.T, full_matrices=False)
    S = np.diag(S_arr)
    print('Matrix T, Spaltenvektoren definieren Topic Raum')
    print(T)
    print('Matrix S, Singulaerwerte zu den Vektoren des Topic Raums')
    print(S)
    print('Matrix D, Koeffizienten der Termvektoren in bof Topic Raum')
    print(D_.T)

    # Transformieren Sie nun die folgenden Termvektoren in den Topic Raum
    # Was erwarten Sie fuer die Topic Zugehoerigkeiten?

    bow_test = np.array([[5, 0, 0], [0, 5, 0], [0, 0, 5], [5, 5, 0], [0, 5,
                                                                      5]])
    coefficients = np.dot(np.dot(bow_test, T), np.linalg.inv(S))
    print(coefficients)

    #
    # Warum lassen sich die Koeffizienten der Termvektoren so schwer interpretieren?
    # Antwort: extrem kleine Werte -> besser: visualisieren
    #
    # Um eine bessere Vorstellung von der Bedeutung der einzelnen Topics zu bekommen,
    # plotten Sie die Bag-of-Words Repraesentationen sowie die Topic-Koeffizienten der
    # Trainingsdaten (bow_train) und der Testdaten (bow_test) in verschiedenen Farben.
    # Erstellen Sie dazu jeweils einen Plot fuer Bag-of-Words Repraesentationen und einen
    # Plot fuer Topic-Koeffizienten. Achten Sie auf eine geeignete Skalierung der Axen.
    # Um die Datenpunkte in den beiden Plots besser einander zuordnen zu koennen, plotten
    # Sie zusaetzlich die Termfrequenzen neben jeden Datenpunkt (als Annotation).
    # Mehrere Daten (Trainings-, Testdaten, Annotationen) lassen sich in einem gemeinsamen
    # Plot darzustellen indem sie nacheinander zu dem gleichen Axis Objekt hinzugefuegt
    # werden. Zum Erstellen der Plots orientieren Sie sich an den entsprechenden
    # Funktionen aus dem Beispiel zur Hauptkomponentenanalyse (oben). Schauen Sie sich
    # auch deren weitere Parameter (und zusaetzlich vorhandene Hilfsfunktionen) an.

    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    annotations_D_test = PCAExample.samples_coordinate_annotations(bow_test)
    annotations_D_train = PCAExample.samples_coordinate_annotations(bow_train)
    PCAExample.plot_sample_data(D_.T,
                                ax=ax,
                                annotations=annotations_D_train,
                                color='r')
    PCAExample.plot_sample_data(coefficients,
                                ax=ax,
                                annotations=annotations_D_test,
                                color='b')
    PCAExample.set_axis_limits(ax, limits=((-1, 1), (-1, 1), (-1, 1)))

    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    PCAExample.plot_sample_data(bow_train,
                                ax=ax,
                                color='b',
                                annotations=annotations_D_train)
    PCAExample.plot_sample_data(bow_test,
                                ax=ax,
                                color='r',
                                annotations=annotations_D_test)
    PCAExample.set_axis_limits(ax, limits=((0, 10), (0, 10), (0, 10)))

    #
    # Fuehren Sie nun eine Dimensionsreduktion der Trainings und Testdaten auf zwei
    # Dimensionen durch und plotten Sie die Topic-Koeffizienten (inkl. Bag-of-Words
    # Annotationen). Vergleichen Sie alle drei Plots miteinander. Welchen Effekt hat
    # die Topic Modellierung im Bezug auf typische Termkonfigurationen?
    #
    # Optional: Transformieren Sie die Daten in einen Topic-Raum mit Dimension Eins
    # und plotten Sie die Koeffizienten inkl. deren Bag-of-Words Annotationen.
    #

    T_2d = T[:, :2]
    S_2d = S[:2, :2]
    D_2d_test = np.dot(np.dot(bow_test, T_2d), np.linalg.inv(S_2d))
    D_2d_train = np.dot(np.dot(bow_train, T_2d), np.linalg.inv(S_2d))
    ax = fig.add_subplot(111)
    annotations_D_test = PCAExample.samples_coordinate_annotations(bow_test)
    annotations_D_train = PCAExample.samples_coordinate_annotations(bow_train)
    PCAExample.plot_sample_data(D_2d_train,
                                ax=ax,
                                annotations=annotations_D_train,
                                color='r')
    PCAExample.plot_sample_data(D_2d_test,
                                ax=ax,
                                annotations=annotations_D_test,
                                color='b')
    PCAExample.set_axis_limits(ax, limits=((-0.75, 0.75), (-0.75, 0.75)))
    plt.show()

    #
    # Integrieren Sie nun die Topic-Raum Modellierung mittels Singulaerwertzerlegung
    # in die Kreuzvalidierung auf dem Brown Corpus. Berechnen Sie dabei fuer
    # jede Aufteilung von Training und Test einen neuen Topic-Raum. Transformieren
    # Sie die Bag-of-Words Repraesentationen und fuehren Sie die Klassifikation
    # wie zuvor mit dem Naechster-Nachbar-Klassifikator durch. Verwenden Sie dabei
    # verschiedene Distanzmasse und evaluieren Sie die Klassifikationsfehlerrate
    # fuer verschiedene Dimensionalitaeten des Topic-Raums. Die anderen Parameter
    # waehlen Sie gemaess der besten bisherigen Ergebnisse.
    #
    # Implementieren Sie die Klasse TopicFeatureTransform im features Modul
    # und verwenden Sie sie mit der CrossValidation Klasse (evaluation Modul).
    #
    # Optional: Fuehren Sie eine automatische Gridsuche ueber den kompletten Paramterraum
    # durch. Legen Sie sinnvolle Wertebereiche und Schrittweiten fuer die einzelnen
    # Parameter fest. Wie lassen sich diese bestimmen?
    #
    # Optional: Passen Sie das Suchgrid dynamisch gemaess der Ergebnisse in den einzelnen
    # Wertebereichen an.

    CorpusLoader.load()
    brown = CorpusLoader.brown_corpus()
    brown_categories = brown.categories()
    brown_words = brown.words()

    words_normalizer = WordListNormalizer()
    filtered_list, stemmed_list = words_normalizer.normalize_words(brown_words)
    vocabulary = BagOfWords.most_freq_words(stemmed_list, 500)

    cat_word_dict = {}
    for category in brown_categories:
        words_of_all_documents = []
        for document in brown.fileids(category):
            filtered_list, stemmed_list = words_normalizer.normalize_words(
                brown.words(fileids=document))
            words_of_all_documents.append(stemmed_list)
        cat_word_dict[category] = words_of_all_documents
    bag_of_words = BagOfWords(vocabulary)
    category_bow_dict = bag_of_words.category_bow_dict(cat_word_dict)

    cross_validation = CrossValidation(category_bow_dict, 5)

    knn_classifier = KNNClassifier(k_neighbors=4, metric='cosine')

    topic_feature_transform = TopicFeatureTransform(topic_dim=25)

    overall_result, class_result = cross_validation.validate(
        knn_classifier, feature_transform=topic_feature_transform)

    print(overall_result)
    print(class_result)