Die Mehrdeutigkeit von Suchbegriffen kann die Korpusbildung erschweren, da wichtige Suchbegriffe auch zu einer großen Anzahl von irrelevanten Dokumenten für das eigene Forschungsprojekt führen können. Das deutsche Wort “Krebs” zum Beispiel, beschreibt eine Krankheit, ist aber auch ein Tier, ein Sternzeichen sowie ein weit verbreiteter Nachname. Um die Bedeutung von Krebs zu bestimmen, braucht es Kontext. Mit Text Mining Methoden kann der Kontext von Suchwörtern mitberücksichtigt und somit die Unterscheidung zwischen relevanten und irrelevanten Dokumenten erleichtert werden. Nachfolgendes Notebook zeigt, wie die Methoden des Topic Modeling (Gensim Library) in Kombination mit der Jensen-Shannon (JS) Distanz genutzt werden können, um semantisch ähnlich Inhalte zu automatisch zu erkennen und zu gruppieren.
Word Sense Disambiguation (WSD) aus dem Bereich Natural Language Processing (NLP) umfasst Methoden, die polysemische Suchbegriffe disambiguieren sollen. WSD kann als Aufgabe beschrieben werden, die richtige Bedeutung mit einem Wort in einem gegebenen Kontext zu assoziieren" (Pasini und Navigli, 2020). WSD-Techniken können wissensbasiert (z. B. auf der Grundlage von Wörterbüchern), überwacht (mit Hilfe von maschinellem Lernen aus manuell annotierten Daten) oder unüberwacht sein. Unüberwachte WSD-Methoden gehen davon aus, dass ähnliche Bedeutungen in ähnlichen Kontexten auftreten (Pal und Saha, 2015; Navigli 2009)
Laden Sie das Coverbild. Klicken Sie dafür aud die nächste Zelle mit dem Code und dann auf "Run", wie im Bild gelb hervorgehoben.
from IPython.display import Image
display(Image("Bilder/cover.png"))
This work has been supported by the European Union Horizon 2020 research and innovation programme under grant 770299 (NewsEye).
Fügen Sie die CSV Datei "export_krebs_krankheit_12_05_2020_10_52" ein. Dafür geben Sie in der nächsten Zelle den Dateiname zwischen die zwei leeren Anführungszeichen bei "df_all = pd.read_csv('')" ein und fügen dem Dateinamen ".csv" hinzu.
import pandas as pd
import re
import re, numpy as np, pandas as pd
import csv
from pprint import pprint
from IPython.display import display
get_ipython().magic(u'matplotlib inline')
#import data
df_all = pd.read_csv('')
print('Tabelle 1: Annotierter Korpus mit Metadaten.')
df_all.head()
Klicken Sie in der nachfolgenden Zeilen erneut auf "Run". Für das weitere Notebook werden wir uns auf den Text und die Relevanzlabels konzentrieren. Die Ziffer 0 wurde an nicht relevante Texte vergeben, die Ziffer 3 an relevante Texte.
df = pd.read_csv('export_krebs_krankheit_12_05_2020_10_52.csv', usecols = ['text','relevancy'])
caption_content = 'Tabelle 2: Text mit Relevanzlables (3 = relevant; 0 = irrelevant).'
display(df[22:24].style.set_caption(caption_content).hide_index())
Visualisieren Sie die Verteilung der Relevanzlabels und der Tageszeitungen in Ihrem Korpus, indem Sie den nachfolgenden Code ausführen.
%matplotlib inline
import matplotlib.pyplot as plt
df_newspaper = pd.read_csv('export_krebs_krankheit_12_05_2020_10_52.csv')
fig = df_newspaper.groupby(['relevancy','newspaper_id']).size().unstack().plot(kind='bar',stacked=True)
plt.title('Abbildung 1: Manuell annotierte Zeitungsartikel zum Thema "Krebs" (0 = irrelevant, 3 = relevant).')
plt.show()
Bevor Textmining Methoden angewendet werden können, muss der Text bereinigt(Interpunktion wird entfernt, alle Wörter werden kleingeschrieben), und tokenisiert (der Text wird in einzelne sprachliche Einheiten zerlegt) werden. Außerdem werden Stoppwörter entfernt und die Token gestemmt (flektierte Wörter auf ihren Wortstamm reduziert). Das Pre-Processing ist wichtig, da Interpunktion oder Sonderzeichen für die weitere Analyse in der Regel nicht benötigt werden. Das Gleiche gilt für Wörter wie und, oder, mit und ähnliche, die nicht als wichtiger Kontext betrachtet werden können. Aus diesem Grund werden wir im nächsten Schritt nur jene Wörter beibehalten, die eine Unterschiedung von relevanten und nicht-relevanten Kontexten ermöglichen.
Ein weiterer Pre-Processing Schritt, der angewendet werden könnte, ist die Lemmatisierung (Umwandlung von Wörtern in ihre Lemmaform/Lexeme). In diesem Notebook wird jedoch ein deutschsprachigen Stemmer verwendet, da dieser weniger Verarbeitungsaufwand erfordert (kein Part-of-Speech-Tagging erforderlich). Wir haben jedoch die Liste der deutschen Stoppwörter aus dem NLTK-Paket erweitert. Eine längere Liste mit deutschen Stoppwörtern wurde von https://countwordsfree.com/stopwords/german abgerufen und der bestehenden Datei hinzugefügt.
Pre-Processing Methoden sind sprachenabhängig. Da wir mit deutschen Texten arbeiten, ist es notwendig, auf Modelle zurückzugreifen, die auf die deutsche Sprache abgestimmt sind. Fügen Sie jeweils dort, wo zwei Punkte zwischen Anführungszeichen zu finden sind, die Sprache für die einzelnen Pre-Processing Schritte hinzu. Für Deutsch verwenden Sie "german". Erweitern Sie ebenfalls die Liste der manuell hinzugefügten Stoppwörtern:
"a","ab","aber","ach","acht","achte","achten","achter","achtes","ag","alle","allein","allem","allen","aller","allerdings","alles","allgemeinen","als","also","am","an","andere","anderen","andern","anders","au","auch","auf","aus","ausser","außer","ausserdem","außerdem",
import numpy as np
import nltk
from nltk.corpus import stopwords
from nltk.stem.snowball import SnowballStemmer
from nltk import FreqDist
#functions to clean and tokenize the data
def initial_clean(text):
text = re.sub(r'[^\w\s]','',text)
text = text.lower()
text = nltk.word_tokenize(text, language = '..')
return text
#remove stop words
nltk.download('stopwords')
nltk.download('punkt')
stop_words = stopwords.words('..')
#add stop words manually
stop_words.extend(["a","ab","aber","ach","acht","achte","achten","achter","achtes","ag","alle","allein","allem","allen","aller","allerdings","alles","allgemeinen","als","also","am","an","andere","anderen","andern","anders","au","auch","auf","aus","ausser","außer","ausserdem","außerdem","b","bald","bei","beide","beiden","beim","beispiel","bekannt","bereits","besonders","besser","besten","bin","bis","bisher","bist","c","d","da","dabei","dadurch","dafür","dagegen","daher","dahin","dahinter","damals","damit","danach","daneben","dank","dann","daran","darauf","daraus","darf","darfst","darin","darüber","darum","darunter","das","dasein","daselbst","dass","daß","dasselbe","davon","davor","dazu","dazwischen","dein","deine","deinem","deiner","dem","dementsprechend","demgegenüber","demgemäss","demgemäß","demselben","demzufolge","den","denen","denn","denselben","der","deren","derjenige","derjenigen","dermassen","dermaßen","derselbe","derselben","des","deshalb","desselben","dessen","deswegen","d.h","dich","die","diejenige","diejenigen","dies","diese","dieselbe","dieselben","diesem","diesen","dieser","dieses","dir","doch","dort","drei","drin","dritte","dritten","dritter","drittes","du","durch","durchaus","dürfen","dürft","durfte","durften","e","eben","ebenso","ehrlich","ei","ei,","eigen","eigene","eigenen","eigener","eigenes","ein","einander","eine","einem","einen","einer","eines","einige","einigen","einiger","einiges","einmal","eins","elf","en","ende","endlich","entweder","er","Ernst","erst","erste","ersten","erster","erstes","es","etwa","etwas","euch","f","früher","fünf","fünfte","fünften","fünfter","fünftes","für","g","gab","ganz","ganze","ganzen","ganzer","ganzes","gar","gedurft","gegen","gegenüber","gehabt","gehen","geht","gekannt","gekonnt","gemacht","gemocht","gemusst","genug","gerade","gern","gesagt","geschweige","gewesen","gewollt","geworden","gibt","ging","gleich","gott","gross","groß","grosse","große","grossen","großen","grosser","großer","grosses","großes","gut","gute","guter","gutes","h","habe","haben","habt","hast","hat","hatte","hätte","hatten","hätten","heisst","her","heute","hier","hin","hinter","hoch","i","ich","ihm","ihn","ihnen","ihr","ihre","ihrem","ihren","ihrer","ihres","im","immer","in","indem","infolgedessen","ins","irgend","ist","j","ja","jahr","jahre","jahren","je","jede","jedem","jeden","jeder","jedermann","jedermanns","jedoch","jemand","jemandem","jemanden","jene","jenem","jenen","jener","jenes","jetzt","k","kam","kann","kannst","kaum","kein","keine","keinem","keinen","keiner","kleine","kleinen","kleiner","kleines","kommen","kommt","können","könnt","konnte","könnte","konnten","kurz","l","lang","lange","leicht","leide","lieber","los","m","machen","macht","machte","mag","magst","mahn","man","manche","manchem","manchen","mancher","manches","mann","mehr","mein","meine","meinem","meinen","meiner","meines","mensch","menschen","mich","mir","mit","mittel","mochte","möchte","mochten","mögen","möglich","mögt","morgen","muss","muß","müssen","musst","müsst","musste","mussten","n","na","nach","nachdem","nahm","natürlich","neben","nein","neue","neuen","neun","neunte","neunten","neunter","neuntes","nicht","nichts","nie","niemand","niemandem","niemanden","noch","nun","nur","o","ob","oben","oder","offen","oft","ohne","Ordnung","p","q","r","recht","rechte","rechten","rechter","rechtes","richtig","rund","s","sa","sache","sagt","sagte","sah","satt","schlecht","Schluss","schon","sechs","sechste","sechsten","sechster","sechstes","sehr","sei","seid","seien","sein","seine","seinem","seinen","seiner","seines","seit","seitdem","selbst","sich","sie","sieben","siebente","siebenten","siebenter","siebentes","sind","so","solang","solche","solchem","solchen","solcher","solches","soll","sollen","sollte","sollten","sondern","sonst","sowie","später","statt","t","tag","tage","tagen","tat","teil","tel","tritt","trotzdem","tun","u","über","überhaupt","übrigens","uhr","um","und","und?","uns","unser","unsere","unserer","unter","v","vergangenen","viel","viele","vielem","vielen","vielleicht","vier","vierte","vierten","vierter","viertes","vom","von","vor","w","wahr?","während","währenddem","währenddessen","wann","war","wäre","waren","wart","warum","was","wegen","weil","weit","weiter","weitere","weiteren","weiteres","welche","welchem","welchen","welcher","welches","wem","wen","wenig","wenige","weniger","weniges","wenigstens","wenn","wer","werde","werden","werdet","wessen","wie","wieder","will","willst","wir","wird","wirklich","wirst","wo","wohl","wollen","wollt","wollte","wollten","worden","wurde","würde","wurden","würden","x","y","z","z.b","zehn","zehnte","zehnten","zehnter","zehntes","zeit","zu","zuerst","zugleich","zum","zunächst","zur","zurück","zusammen","zwanzig","zwar","zwei","zweite","zweiten","zweiter","zweites","zwischen","zwölf","euer","eure","hattest","hattet","jedes","mußt","müßt","sollst","sollt","soweit","weshalb","wieso","woher","wohin"])
def remove_stop_words(text):
return [word for word in text if word not in stop_words]
#stemming
stemmer = SnowballStemmer('..')
def stem_words(text):
try:
text = [stemmer.stem(word) for word in text]
text = [word for word in text if len(word) > 1]
except IndexError:
pass
return text
In einem nächsten Schritt müssen die einzelnen Funktionen angewendet werden. Um den dafür notwendigen Code zu erstellen, müssen Sie eine neue Code-Zelle einfügen. Dafür müssen Sie sich zunächst in jener Zelle befinden, auf die eine neue Zelle folgen soll (zum Beispiel in der Zelle dieses Textes). Anschließend klicken Sie in der Menüleiste auf das "plus" Symbol:
Fügen Sie nun die folgenden Codezeilen in die neue Zelle ein. Kopieren Sie den Text im Bearbeitungsmodus dieser Zelle. Dafür müssen Sie mit einem Doppelklick auf diese Zelle klicken.
def apply_all(text): return stem_words(remove_stop_words(initial_clean(text)))
df['tokenized'] = df['text'].apply(apply_all)
caption_content='Tabelle 2: Relevanz, Originaltext und Tokens.' display(df[12:14].style.set_caption(caption_content).hide_index())
In einem weiteren Schritt wird die Sammlung in ein Trainings- und Testkorpus zu unterteilt. Damit erhalten wir eine Reihe von Dokumenten (Trainingskorpus), um den Algorithmus zu trainieren, und eine Reihe von Dokumenten (Testkorpus), um die Effizienz der gewählten Methoden zu testen.
Um die Sammlung in ein Trainings- und ein Testkorpus aufzuteilen, wird die Funktion numpy.random.rand() verwendet, um ein Array mit einer bestimmten Form zu erstellen und es mit Zufallswerten zu füllen. Dies ermöglichte es, eine gute Mischung aus relevanten und nicht relevanten Artikeln in jedem der Korpora zu erhalten (Abbildung 2). Da diese Funktion auf dem Zufallsprinzip beruht und die Aufteilung der Korpora bei jedem Aufruf variieren kann, wurde ein Zufallswert gesetzt, um reproduzierbare Aufrufe zu erzeugen. Folglich sind alle Zufallszahlen, die nach dem Setzen des Seeds erzeugt werden, auf jeder Maschine gleich.
#create testing and training corpus
np.random.seed(1)
msk = np.random.rand(len(df)) < 0.599
train_df = df[msk]
train_df.reset_index(drop=True,inplace=True)
test_df = df[~msk]
test_df.reset_index(drop=True,inplace=True)
#plot the result
my_colors = [(0.20,0.200,0.50), (0.100, 0.75, 0.200)] #set colors
fig, axes = plt.subplots(1,2,figsize=(10,3))
test_df.relevancy.value_counts().plot(kind='bar', color = (0.100, 0.75, 0.20), ax=axes[1])
test_df.relevancy.value_counts().plot(kind='bar', color = my_colors, ax=axes[1])
train_df.relevancy.value_counts().plot(kind='bar', color = (0.100, 0.75, 0.20), ax=axes[0])
train_df.relevancy.value_counts().plot(kind='bar', color = my_colors, ax=axes[0])
axes[1].legend(['Non_Relevant', 'Relevant'])
axes[0].legend(['Non_Relevant', 'Relevant'])
axes[1].title.set_text('figure 2a: Test Corpus.')
axes[0].title.set_text('figure 2b: Training Corpus.')
print(f"Der Trainingskorpus enthält {len(train_df)} Artikel, {train_df.relevancy.value_counts()[3]} sind relevant und {train_df.relevancy.value_counts()[0]} irrelevant.")
print(f"Der Testkoprus enthält {len(test_df)} Artikel, {test_df.relevancy.value_counts()[3]} sind relevant und {test_df.relevancy.value_counts()[0]} irrelevant.")
Um Wörter und ähnliche Ausdrücke zu gruppieren, die relevante oder irrelevante Dokumente am besten charakterisieren, und um jeden Artikel mit Informationen über seine Themenverteilung zu versehen, wird ein Topic Modeling Altgorithmus (LDA) trainiert. Die Jensen-Shannon Distanz hingegen wird verwendet, um die Ähnlichkeit zwischen der Themenverteilung der Dokumente zu messen. Die Kombination von LDA und JSD hat Vorteile gegenüber Textklassifikatoren, da auch für hochkomplexe Sammlung gute Ergebnisse erziehlt werden können. Die Grenzen zwischen relevanten und nicht relevanten Artikeln ist bei vielen mehrdeutigen Begriffen sehr wage. Mit LDA in Kombination mit JSD kann diese Komplexität bewältigt werden, indem der Dateninput für die endgültige Klassifizierung eines ungesehenen Artikels auf die 10 ähnlichsten Artikel eingrenzen. Dies bedeutet, dass nur die ähnlichsten Artikel als Grundlage für die Klassifizierung eines ungesehenen Artikels herangezogen werden.
Topic Modelle beruhen auf der Annahme, dass Texten in natürlicher Sprache eine relativ kleine Menge latenter oder verborgener Themen zugrunde liegt, wobei ein Wort zu mehreren Themen gehören kann. Themenmodelle verwenden die so genannte Bag-of-Words-Annahme innerhalb eines Dokuments bzw. sie gruppieren statistisch signifikante Wörter innerhalb eines bestimmten Korpus. Wie von (Blei, Ng und Jordan 2003) beschrieben, können Dokumente "als zufällige Mischungen über latente Themen dargestellt werden, wobei jedes Thema durch eine Verteilung über Wörter charakterisiert ist". Die Themenmodellierung wird für verschiedene Zwecke eingesetzt: um einen Koprpus besser zu verstehen (Zosa et al. 2020), um Diskursdynamik zu erfassen (Marjanen et al. 2020), um einen besseren Einblick in die Art oder das Genre von Dokumenten in einem Korpus zu erhalten (Oberbichler 2021), um die Entwicklung von Themen und Trends in mehrsprachigen Sammlungen zu erfassen (Zosa und Granroth-Wilding 2019) oder um verschiedene Korpora zu vergleichen (Lu, Henchion und Namee 2019).
Jeder dieser Anwendungsbereiche benötigt unterschiedliche Parameter. Während Methoden zur automatischen Bestimmung der Themenanzahl in einigen Fällen hilfreich sein können (Zhao et al. 2015) (O'Callaghan et al. 2015), ist das genaue Lesen einer beträchtlichen Menge von Dokumenten nach wie vor am zuverlässigsten.
Wir verwenden hier die Python-Library Gensim, um die Topic Modelle zu trainieren. Gensim ist eine Bibliothek, die für die Themenmodellierung, die Dokumentenindexierung als auch die Ähnlichkeitssuche verwendet wird. Alle Algorithmen sind speicher- und sprachunabhängig sowie unüberwacht, was bedeutet, dass keine menschliche Eingabe erforderlich ist.
Die Gensim Library beinhaltet diverse Parameter, die je nach Anwendung abgestimmt werden müssen. Passen Sie die Parameter auf folgende Werte an:
import gensim
from gensim.models import LdaModel
from gensim import parsing, corpora, matutils, interfaces, models, utils
import gensim.corpora as corpora
import gensim, spacy, logging, warnings
from gensim.models import CoherenceModel
from matplotlib import pyplot as plt
from wordcloud import WordCloud
#function to train the topic model
def train_lda(data):
num_topics =
dictionary = corpora.Dictionary(data['tokenized'])
corpus = [dictionary.doc2bow(doc) for doc in data['tokenized']]
lda = LdaModel(corpus=corpus, num_topics=num_topics, id2word=dictionary,
alpha=0.2e-2, eta=1e-2, minimum_probability=0.0, passes=, iterations=, update_every=1, random_state =1)
return dictionary,corpus,lda
#apply function and check results
dictionary,corpus,lda = train_lda(train_df)
Sehen Sie sich neben dem Topic 27 noch weitere Topics an. Ändern Sie dafür die Zahl 27 in der nachfolgenden Zeile.
# plot word cloud
plt.figure(dpi=100)
plt.imshow(WordCloud(max_words=30).fit_words(dict(lda.show_topic(27, 200))))
plt.axis("off")
plt.title("Abbildung 3: Topic Nummer 27.")
plt.show()
Nach dem Training des Modells sollte geprüft werden, wie gut diese Themen in relevante und nicht relevante Artikel unterteilt werden konnten. Nicht alle Themen haben die gleiche Dominanz innerhalb eines Textes. Um zu verstehen, welches Thema in einem Dokument am dominantesten ist, werden die Themen mit der höchsten Gewichtung für ein Dokument ermittel.
def format_topics_texts(ldamodel=None, corpus=corpus, relevancy=df['relevancy']):
sent_topics_df = pd.DataFrame()
#get main topic in each document
for i, row_list in enumerate(ldamodel[corpus]):
row = row_list[0] if ldamodel.per_word_topics else row_list
row = sorted(row, key=lambda x: (x[1]), reverse=True)
for s, (topic_num, prop_topic) in enumerate(row):
if s == 0: # => dominant topic
wp = ldamodel.show_topic(topic_num)
topic_keywords = ", ".join([word for word, prop in wp])
sent_topics_df = sent_topics_df.append(pd.Series([int(topic_num), round(prop_topic,4), topic_keywords]), ignore_index=True)
else:
break
sent_topics_df.columns = ['Dominant_Topic', 'Perc_Contribution', 'Topic_Keywords']
#add relevancy to the end of the output
contents = pd.Series(relevancy)
sent_topics_df = pd.concat([sent_topics_df, contents], axis=1)
return(sent_topics_df)
df_topic_sents_keywords = format_topics_texts(ldamodel=lda, corpus=corpus, relevancy=df['relevancy'])
#format
df_dominant_topic = df_topic_sents_keywords.reset_index()
df_dominant_topic.columns = ['Document_No', 'Dominant_Topic', 'Topic_Perc_Contrib', 'Keywords', 'Relevancy']
caption_content= 'Tabelle 2: Dominante Topics eines jeden Artikels, topic Keyörter und die Relevanz der Artikel.'
display(df_dominant_topic[23:28].style.set_table_attributes("style='display:block'").set_caption(caption_content).hide_index())
Wie in Tabelle 3 zu sehen ist, stellt das Topic 27 (Abbildung 3) eindeutig diesen Artikel dar.
Sehen Sie sich auch jenen Text an, im dem Topic 1 dominiert. Dafür müssen Sie nicht nach Artikel 23, sondern nach Artikel 26 (wie aus Tabelle 2 hervorgeht) suchen.
article_df_1 = train_df['text'][23]
article_df_1 = pd.DataFrame(np.column_stack([article_df_1]),
columns=['Der Text, der überwiegend von Topic 27 repräsentiert wird'])
article_df_1 = article_df_1.apply(lambda x: x[:600])
caption_content = "Tabelle 3: Der Text, der überwiegend von Topic 27 repräsentiert wird"
display(article_df_1.style.set_caption(caption_content).hide_index())
Um zu sehen, wie gut die dominanten Themen auf relevante (3) und nicht relevante (0) Artikel verteilt sind, erstellen wir eine Netzwerkvisualisierung mit dem Python-Paket NetworkX. NetworkX wird hauptsächlich für die Erstellung, Manipulation und Untersuchung der Struktur, Dynamik und Funktionen komplexer Netzwerke verwendet. Anhand dieser Visualisierung lässt sich erkennen, wie effektiv das Modell trainiert wurde. Für das Netzwerk werden das dominanteste Thema sowie das Relevanzlabel für jeden Zeitungsausschnitt miteinander in Verbindung gebracht.
import networkx as nx
import seaborn as sns
import sys
#create a list with topics and the relevancy
df_dominant_topic.to_csv('topic_relevancy.csv')
import csv
with open('topic_relevancy.csv', encoding="utf8") as infile:
reader = csv.reader(infile)
csv_data = list(reader)
df_dominant_topics= pd.read_csv('topic_relevancy.csv', usecols = ['Dominant_Topic', 'Relevancy'])
list_topic = []
for key in csv_data:
list_topic.append(key[2])
topic = list_topic[1:]
list_relevancy = []
for key in csv_data:
list_relevancy.append(key[5])
relevance = list_relevancy[1:]
#build a dataframe with 4 connections
df = pd.DataFrame({ 'from': relevance, 'to': topic})
#build the graph
plt.figure(figsize=(6.5,6.5))
G = nx.from_pandas_edgelist(df, 'from', 'to')
color_map = []
for node in G:
if node == "3":
color_map.append('#b85399')
if node == "0":
color_map.append('#b85399')
else:
color_map.append('#2d95b3')
#plot it
nx.draw(G, with_labels=True, node_color=color_map, node_size=500)
plt.legend(('Relevancy labels (0= irrelevant, 3= relevant)', 'Relationship between entities'),
loc='upper left')
plt.title('Abbildung 4: Dieses Diagramm zeigt, wie gut die dominierenden Themen zwischen relevanten und irrelevanten Zeitungsausschnitten getrennt sind')
plt.show()
Können Sie Topic 27 finden?
Im nächsten Schritt wird die Jensen-Shannon-Distanz (JS) angewendet, um die Ähnlichkeit zwischen der Themenverteilung der Dokumente aus dem Trainingskorpus und jenen aus dem Testkorpus zu messen. Es wird davon ausgegangen, dass Artikel mit ähnlichen Inhalten auch als solche erkannt werden.
from scipy.stats import entropy
#JS distance functions
def jensen_shannon(query, matrix):
p = query[None,:].T
q = matrix.T
m = 0.5*(p + q)
return np.sqrt(0.5*(entropy(p,m) + entropy(q,m)))
def get_most_similar_documents(query,matrix,k=10):
sims = jensen_shannon(query,matrix)
return sims.argsort()[:k]
#most similar articles
bow = dictionary.doc2bow(test_df.iloc[11,2])
doc_distribution = np.array([tup[1] for tup in lda.get_document_topics(bow=bow)])
doc_topic_dist = np.stack([np.array([tup[1] for tup in lst]) for lst in lda[corpus]])
doc_topic_dist.shape
sim_ids = get_most_similar_documents(doc_distribution,doc_topic_dist)
similar_df = train_df[train_df.index.isin(sim_ids)]
Mit der Hilfe der JS-Distanz wird die Themenverteilung jedes neuen Artikels (aus dem Testkorpus) mit der Themenverteilung jedes Artikels im Trainingkorpus verglichen. Auf diese Weise werden die 10 ähnlichsten Artikel aus dem Trainingskorpus abgerufen.
Sehen wir uns ein Beispiel aus dem Testkorpus an:
from IPython.display import Image, display
display(Image("Bilder/artikel.png"), 'Abbildung 5: Neue Freie Presse, 02.07.1871')
Tabelle 4 zeigt die zehn ähnlichsten Artikel für den Zeitungsartikel aus der "Neue Freie Presse" vom 02.07.1871.
Lesen Sie den Artikel aus Abbildung 5. Dann sehen Sie sich die Ausschnitte der autmatisch gefundenen 10 ähnlichsten Artikel an (dafür müssen Sie den Code der nächsten Zeile ausführen). Finden Sie Ähnlichkeiten?
similar_df = similar_df.drop(['tokenized'], axis =1)
similar_df['text'] = similar_df['text'].apply(lambda x: x[:300])
similar_df.rename(columns={'text': 'Die zehn ähnlichsten Artikel aus dem Trainingskorpus für den ungesehenen Artikel aus Abbildung 5'}, inplace=True)
caption_content= 'Tabelle 4: Die zehn ähnlichsten Artikel aus dem Trainingskorpus für den ungesehenen Artikel aus Abbildung 5'
display(similar_df.style.set_caption(caption_content).hide_index())
Nun wählen Sie selbst einen bisher ungesehenen Artikel aus dem Testkorpus aus, indem Sie eine Zahl zwischen 1 und 50 überall dort einfügen, wo derzeit zwei Punkte vorhanden sind.
display(test_df['text'][..])
#most similar articles
bow = dictionary.doc2bow(test_df.iloc[.., 2])
doc_distribution = np.array([tup[1] for tup in lda.get_document_topics(bow=bow)])
doc_topic_dist = np.stack([np.array([tup[1] for tup in lst]) for lst in lda[corpus]])
doc_topic_dist.shape
sim_ids = get_most_similar_documents(doc_distribution,doc_topic_dist)
similar_df = train_df[train_df.index.isin(sim_ids)]
similar_df = similar_df.drop(['tokenized'], axis =1)
similar_df['text'] = similar_df['text'].apply(lambda x: x[:300])
similar_df.rename(columns={'text': 'Die zehn ähnlichsten Artikel aus dem Trainingskorpus für den ungesehenen Artikel'}, inplace=True)
caption_content= 'Tabelle 5: Die zehn ähnlichsten Artikel aus dem Trainingskorpus für den ungesehenen Artikel'
display(similar_df.style.set_caption(caption_content).hide_index())
Nun durchläuft jeder Artikel aus dem Testkorpus eine Schleife, in der er mit allen Artikeln aus dem Trainingskoprus verglichen wird. Wenn 60 Prozent der ähnlichsten Artikel ursprünglich als relevant eingestuft wurden, wird auch der neue Artikel als relevant eingestuft. Andernfalls wird er als irrelevant eingestuft. In diesem Fall wurde der ungesehene Artikel aus Abbildung 5 als relevant eingestuft (erste Spalte, dritte Zeile), weil mehr als 60 Prozent der ähnlichsten Artikel (Tabelle 6) ursprünglich als relevant annotiert wurden (= 3).
Dieser Code bräucht etwas länger, bis er ausgeführt wird.
#lists for the output
text_relevant = []
number_relevant = []
text_non_relevant = []
number_non_relevant = []
index = 0
#loop for the classification of each article
while index < len(test_df) -1:
index +=1
new_bow = dictionary.doc2bow(test_df.iloc[index,2])
new_doc_distribution = np.array([tup[1] for tup in lda.get_document_topics(bow=new_bow)])
doc_topic_dist = np.stack([np.array([tup[1] for tup in lst]) for lst in lda[corpus]])
doc_topic_dist.shape
most_sim_ids = get_most_similar_documents(new_doc_distribution,doc_topic_dist)
most_similar_df = train_df[train_df.index.isin(most_sim_ids)]
relevant = []
if sum(most_similar_df['relevancy']) > 17:
text_relevant.append(test_df.iloc[index,1])
number_relevant.append(test_df.iloc[index,0])
else:
text_non_relevant.append(test_df.iloc[index,1])
number_non_relevant.append(test_df.iloc[index,0])
#dataframe with results
df_relevant = pd.DataFrame(np.column_stack([text_relevant, number_relevant]),
columns=['Relevant_Text', 'Real_Relevancy'])
df_non_relevant = pd.DataFrame(np.column_stack([text_non_relevant, number_non_relevant]),
columns=['Unrelevant_Text', 'Real_Relevancy'])
df_results = pd.concat([df_relevant,df_non_relevant], ignore_index=True, axis=1)
df_results.columns=['Diese Texte wurden als relevant klassifiziert', '3','Diese Texte wurden als irrelevant klassifiziert', '0']
df_results['Diese Texte wurden als relevant klassifiziert'][0:20] = df_results['Diese Texte wurden als relevant klassifiziert'][0:20].apply(lambda x: x[:50])
df_results['Diese Texte wurden als irrelevant klassifiziert'][0:20] = df_results['Diese Texte wurden als irrelevant klassifiziert'][0:20].apply(lambda x: x[:50])
caption_content= 'Tabelle 6: Die Artikel werden automatisch in relevante und nicht relevante Artikel gruppiert. Die manuell vergebenen Annotierungen zeigen, ob die Artikel korrekt klassifiziert wurden.'
display(df_results[0:20].style.set_caption(caption_content).hide_index())
#calculation of correct or incorrect classified results
rev_3 = []
for key in df_results['3']:
if key == '3':
rev_3.append(key)
rev_0 = []
for key in df_results['3']:
if key == '0':
rev_0.append(key)
non_rev_3 = []
for key in df_results['0']:
if key == '3':
non_rev_3.append(key)
non_rev_0 = []
for key in df_results['0']:
if key == '0':
non_rev_0.append(key)
result_right = len(non_rev_0) + len(rev_3)
result_wrng = len(non_rev_3) + len(rev_0)
relevant = len(rev_3) / (len(rev_0) + len(rev_3))
irrelevant = len(non_rev_0) / (len(non_rev_0) + len(non_rev_3))
all_ = len(non_rev_3) + len(rev_0) + len(non_rev_0) + len(rev_3)
score = result_right / all_
df_score = pd.DataFrame(np.column_stack([relevant, irrelevant, score]),
columns=['Correct classified relevant articles', 'Correct classified irrelevant articles', 'Total score'])
caption_content= 'table 7: Evaluation of the classification of the articles from the testing corpus.'
display(df_score.style.set_caption(caption_content).hide_index())
Diese Ergebnisse werden als gut genug für die weitere Verarbeitung angesehen. Der letzte Schritt betrifft den gesamten Korpus, der disambiguiert werden soll.
df_all = pd.read_csv('export_krebs_all_25_05_2020_20_00.csv', usecols = ['id','language','date','newspaper_id','iiif_url','text'])
print('Tabelle 8: Gesamter Korpus')
df_all.head()
df_all['tokenized'] = df_all['text'].apply(apply_all)
# first get a list of all words
all_words = [word for item in list(df_all['tokenized']) for word in item]
# use nltk fdist to get a frequency distribution of all words
fdist = FreqDist(all_words)
f"Die Anzahl individueller Worter ist: {len(fdist)}"
#document length
df_all['doc_len'] = df_all['tokenized'].apply(lambda x: len(x))
doc_lengths = list(df_all['doc_len'])
df_all.drop(labels='doc_len', axis=1, inplace=True)
print(f"Die Sammlung enthält {max(doc_lengths)} Artikel")
df_all = df_all[df_all['tokenized'].map(len) >= 30]
df_all = df_all[df_all['tokenized'].map(type) == list]
df_all.reset_index(drop=True,inplace=True)
print("Die Sammlung enthält nun", len(df_all), "Artikel")
def jensen_shannon(query, matrix):
p = query[None,:].T
q = matrix.T
m = 0.5*(p + q)
return np.sqrt(0.5*(entropy(p,m) + entropy(q,m)))
def get_most_similar_documents(query,matrix,k=10):
sims = jensen_shannon(query,matrix) # list of jensen shannon distances
return sims.argsort()[:k] # the top k positional index of the smallest Jensen Shannon distances
Die Klassifikation dauert, wenn im Browser ausgeführt, etwas länger.
#create lists for your output
text_relevant = []
number_relevant = []
date_relevant = []
text_non_relevant = []
number_non_relevant = []
language_relevant = []
newspaper_id_relevant = []
iiif_url_relevant = []
id_relevant = []
#find most similar articles and select between relevant and non-relevant
index = 0
while index < len(df_all) -1:
index +=1
new_bow = dictionary.doc2bow(df_all.iloc[index,6])
new_doc_distribution = np.array([tup[1] for tup in lda.get_document_topics(bow=new_bow)])
doc_topic_dist = np.stack([np.array([tup[1] for tup in lst]) for lst in lda[corpus]])
doc_topic_dist.shape
most_sim_ids = get_most_similar_documents(new_doc_distribution,doc_topic_dist)
most_similar_df = train_df[train_df.index.isin(most_sim_ids)]
# Calculate
if sum(most_similar_df['relevancy']) > 17:
text_relevant.append(df_all.iloc[index,5])
date_relevant.append(df_all.iloc[index,2])
language_relevant.append(df_all.iloc[index,1])
newspaper_id_relevant.append(df_all.iloc[index,3])
iiif_url_relevant.append(df_all.iloc[index,4])
id_relevant.append(df_all.iloc[index,0])
else:
text_non_relevant.append(df_all.iloc[index,5])
#transform your lists into a dataframe
df_relevant = pd.DataFrame(np.column_stack([text_relevant]),
columns=['Relevanter_Text'])
df_non_relevant = pd.DataFrame(np.column_stack([text_non_relevant]),
columns=['Unrelevanter_Text'])
df_results = pd.concat([df_relevant,df_non_relevant], ignore_index=True, axis=1)
df_results.columns=['Diese Texte wurden als relevant klassifiziert', 'Diese Texte wurden als irrelevant klassifiziert']
df_results['Diese Texte wurden als relevant klassifiziert'][0:5] = df_results['Diese Texte wurden als relevant klassifiziert'][0:5].apply(lambda x: x[:400])
df_results['Diese Texte wurden als irrelevant klassifiziert'][0:5] = df_results['Diese Texte wurden als irrelevant klassifiziert'][0:5].apply(lambda x: x[:400])
caption_content= 'Tabelle 5: Die Artikel werden automatisch in relevante und nicht relevante Artikel gruppiert. Die manuell vergebenen Annotierungen zeigen, ob die Artikel korrekt klassifiziert wurden.'
display(df_results[0:5].style.set_caption(caption_content).hide_index())
df_final = pd.DataFrame(np.column_stack([id_relevant, language_relevant, newspaper_id_relevant, date_relevant, iiif_url_relevant, text_relevant]),
columns=['id', 'language', 'date', 'newspaper_id', 'iiif_url', 'text'])
df_new = pd.concat([df_final], ignore_index=True, axis=1)
df_new.columns=['id','language', 'date', 'newspaper_id', 'iiif_url', 'text']
Schauen Sie ich das Excel File "Collection_relevant" an, das automatisch in Ihrem Ordner generiert wurde.
from openpyxl import Workbook
df_new.to_csv('Collection_relevant.csv')
df_new.to_excel('Collection_relevant.xlsx')