Skip to content
Snippets Groups Projects
Commit ab4ea6bf authored by Sonja Huber's avatar Sonja Huber
Browse files

rewrite topic_modeling.py entirely, increase readability with modularity and...

rewrite topic_modeling.py entirely, increase readability with modularity and type hints. Change output format from .txt to .csv for easier further processing. also update README.
parent b25d12d7
No related branches found
No related tags found
No related merge requests found
......@@ -24,8 +24,7 @@ ihre Zusammensetzung (oder ein Teil davon) ausgegeben, und zu jedem Dokument,
zu wie vielen Teilen es aus welchen Topics besteht.
Weil es immer ganz auf den Korpus und die Analyse ankommt, welche Anzahl an Topics
sinnvoll ist und aussagekräftige Resultate liefern kann, werden im LDA-Skript standardmässig Modelle zu zwei verschiedenen Topic-Anzahlen berechnet, welche
dann in der Analyse verglichen werden können. Das top2vec-Verfahren funktioniert etwas anders, mehr dazu findest du im Readme im top2vec-Ordner.
sinnvoll ist und aussagekräftige Resultate liefern kann, werden im LDA-Skript standardmässig Modelle zu zwei verschiedenen Topic-Anzahlen berechnet, welche dann in der Analyse verglichen werden können. Das top2vec-Verfahren funktioniert etwas anders, mehr dazu findest du im Readme im top2vec-Ordner.
Ebenfalls ist nicht normiert, wie "streng" der Algorithmus bei der Auswahl von
Stopwörtern sein soll. Stopwörter sind die Wortformen, welche aufgrund hohem Vorkommen
......@@ -67,26 +66,26 @@ in erster Linie Wort- und Lemma-Spalte sein.
Dieses Skript erstellt im Default vier verschiedene Topic Modellings und gibt dir zu jedem
ein .txt-File mit den Topics und der Verteilung pro Dokument und ein .html-File
mit einer Visualisierung zurück.
Als Input gibst du ein File an, in welchem auf jeder Zeile ein lemmatisiertes
Dokument steht, wie du es mit Promethia und der Flag _-s gensim_ erzeugen kannst.
Als Input gibst du ein File an, in welchem auf jeder Zeile ein
Dokument steht, wie du es mit Promethia und dem Skript _vrt2docperline.py_ erzeugen kannst.
`python3 topic_modeling.py inputfile.txt`
`python3 topic_modeling.py inputfile.txt`
Willst du, wie oben beschrieben, eine eigene Anzahl an Topics und obere Limits für
die Stopwords definieren, verwendest du folgenden Befehl:
`python3 topic_modeling.py inputfile.txt -nt I -ul F`
`python3 topic_modeling.py inputfile.txt -nt I -ul F.F`
Hierbei steht `-nt` für _number of topics_, `I` steht für eine _integer_,
eine ganze Zahl du kannst auch mehrere Zahlen angeben, wenn du mehrere Modelle erstellen willst.
`-ut` steht für _upper limit_, `F.F` steht für eine _float_, eine Dezimalzahl,
in unserem Fall ausserdem eine zwischen 0 und 1. Auch hier kannst du mehrere Zahlen angeben.
**Achtung:** Wenn du mehrere Zahlen für number of topics oder floats für
**Achtung:** Wenn du mehrere ints für number of topics oder mehrere floats für
upper limit angeben willst, um mehrere Modelle vergleichen zu können,
müssen die einzelnen Zahlen **nur durch einen Abstand** getrennt werden.
### mögliche Anpassungen im Skript
- Die Anzahl der Worte, die pro Topic angegeben werden, ist defaultmässig auf 10 gesetzt.
- Die Anzahl der Worte, die pro Topic angegeben werden, ist im Skript auf 20 gesetzt.
Du kannst diese aber auch verändern. Die Variabel dazu heisst **num_words**.
......@@ -2,15 +2,16 @@
Skript um Topic-Modeling auf Dokumenten auszuführen und eine html-Visualisierung zum Modell zu erstellen.
Input:
- Datei, welche ein lemmatisiertes Dokument pro Zeile enthält
- Datei, welche ein Dokument pro Zeile enthält
optionaler Input:
- beliebig viele ganze Zahlen, die der Anzahl berechneten Topics entsprechen. Getrennt durch einen Abstand.
- beliebig viele Werte zwischen 0 und 1, welche dem oberen Limit an Auftreten in den Dokumenten entsprechen,
ab wann ein Wort zum Stopword wird. Bsp. 0.8: Sobald ein Wort in mehr als 0.8 der Dokumente vorkommt,
wird es ignoriert. Die drei Zahlen sind mit Abstand dazwischen einzugeben, also z.B. 0.1 0.5 0.85
wird es ignoriert. Die Werte sind mit Abstand dazwischen einzugeben, also z.B. 0.1 0.5 0.85
Output:
- .txt Dateien, die den verschiedenen Models entsprechen
- .csv Dateien, die den verschiedenen Models nach Angabe der Parameter entsprechen,
einmal mit den Wörtern pro Topic, einmal mit den Topic-Distributionen pro Dokument
- .html Dateien, die eine Visualisierung der Topics enthalten und sich im Browser öffnen lassen
......@@ -28,79 +29,186 @@ import pyLDAvis
import pyLDAvis.gensim_models
import nltk
def topic_modeling(infile, number_topics, upper_limits):
data_lemmatized = []
from typing import List, TextIO, Generator
def parse_arguments():
my_parser = argparse.ArgumentParser(
description="Erstellt ein Topic Modeling, entweder mit default Werten oder mit eigenen Anzahl Topics und Stopwords-limit"
)
my_parser.add_argument(
# TODO: see if type can be changed to argparse.FileType
"in_file",
type=argparse.FileType("r", encoding="utf-8"),
help="Input-Datei: Lemmatisierte Dokumente",
)
my_parser.add_argument(
"-nt",
"--number_topics",
type=int,
nargs="+",
default=[7, 15],
help="optionale Angabe verschiedener Anzahlen von Topics, default ist 7 und 15",
)
my_parser.add_argument(
"-ul",
"--upper_limits",
type=float,
nargs="+",
default=[0.5, 0.8],
help="optionale Angabe mehrerer Limiten für die Anzahl Dokumente, "
"in denen ein Wort vorkommen kann, bevor es zum stopword wird."
"wobei 1 = jedes Dokument, 0 = kein Dokument. Defaultwerte sind 0.5 und 0.8",
)
return my_parser.parse_args()
def lines_generator(infile: TextIO) -> Generator[List[str], None, None]:
"""Generiert eine Liste mit dem Inhalt der einzelnen Zeilen/Dokumente der Input-Datei.
Args:
infile (TextIO): Input-Datei mit lemmatisierten Dokumenten
Yields:
List[str]: Liste mit tokens eines Dokuments
"""
for line in infile:
word_list = line.strip().split(" ")
yield word_list
def html_visualisation(
lda_model: LdaModel, corpus, Dict_id_words: Dictionary, nr: int, limit: float
) -> None:
"""
Erstellt eine html-Visualisierung des Topic Models und speichert sie als html-Datei ab.
"""
visualisation = pyLDAvis.gensim_models.prepare(
lda_model, corpus, Dict_id_words, mds="mmds", sort_topics=False
)
pyLDAvis.save_html(
visualisation,
"tm_vis_topics" + str(nr) + "_upperlimit" + str(limit) + ".html",
)
print(
"Das file tm_vis_topics"
+ str(nr)
+ "_upperlimit"
+ str(limit)
+ ".html wurde ausgegeben"
)
def produce_outfiles(
topics: list, lda_model: LdaModel, corpus, nr: int, limit: float
) -> None:
"""
Erstellt zwei .csv Dateien mit den Wörtern pro Topic und den Topic-Distributionen pro Dokument.
"""
with open(
"tm_topic_content_topics" + str(nr) + "upperlimit" + str(limit) + ".csv",
"w",
) as open_out_file1:
open_out_file1.write("Topic, Words, \n")
# schreibt pro Topic eine Zeile mit den num_words wichtigsten Wörtern in ein csv-file
for curr_topic in topics:
curr_words_in_topic = curr_topic[1].split("+")
outline_for_topic = "Topic " + str(curr_topic[0]) + ", "
for word in curr_words_in_topic:
outline_for_topic += word.split('"')[1] + " "
open_out_file1.write(outline_for_topic + "\n")
with open(
"tm_topics_per_doc_topics" + str(nr) + "upperlimit" + str(limit) + ".csv", "w"
) as open_out_file2:
# erstellt eine Liste mit den Topic-Distributionen pro Dokument
topic_per_doc = lda_model.get_document_topics(corpus)
open_out_file2.write("Document Number, Topic distribution, \n")
# schreibt für jedes Dokument die Topic-Distribution in eine Zeile
for index, topics_per_doc in enumerate(topic_per_doc):
outline_per_doc = "Document " + str(index) + ", "
for topic in topics_per_doc:
outline_per_doc += str(topic) + " "
open_out_file2.write(f"{outline_per_doc}, \n")
print(
"Das file tm_topics_per_doc_topics"
+ str(nr)
+ "upperlimit"
+ str(limit)
+ ".csv wurde ausgegeben"
)
def topic_modeling(
infile: TextIO, number_topics: int, upper_limits: List[float]
) -> None:
"""Berechnet Topic Modeling mit den angegebenen Parametern.
Args:
infile (TextIO): Input-Datei mit einem Dokument pro Zeile
number_topics (int): Anzahl Topics, die berechnet werden sollen
upper_limits (List[float]): Liste mit den oberen Limits für die
Anzahl Dokumente, in denen ein Wort vorkommen darf
Returns:
None
"""
stop_word_upper_limits = upper_limits
nr_of_topics = number_topics
with open(infile, "r", encoding="utf8") as in_file_open:
# liest die einzelnen Zeilen ein
lines = in_file_open.readlines()
for line in lines:
word_list = line.strip().split(" ")
data_lemmatized.append(word_list)
print("read input")
# Create Dictionary
Dict_id_words = Dictionary(data_lemmatized)
for limit in stop_word_upper_limits:
Dict_id_words.filter_extremes(no_below=1, no_above=limit, keep_n=None, keep_tokens=None)
corpus = [Dict_id_words.doc2bow(text) for text in data_lemmatized]
print(limit)
print("Building model ....")
for nr in nr_of_topics:
# Build LDA model
lda_model = LdaModel(corpus=corpus, id2word=Dict_id_words, num_topics=nr, random_state=100,
iterations=100, update_every=1, chunksize=100, passes=10, alpha='auto',
per_word_topics=True)
# Berechnet und speichert die html-Ansicht der Topics
visualisation = pyLDAvis.gensim_models.prepare(lda_model, corpus, Dict_id_words, mds='mmds',
sort_topics=False)
pyLDAvis.save_html(visualisation,
'Topic_Modeling_vis_#Topics' + str(nr) + '#Upperlimit' + str(limit) + '.html')
print("Das file Topic_Modeling_vis_#Topics" + str(nr) + "#Upperlimit" + str(
limit) + ".html wurde ausgegeben")
# berechnet nr_topics Topics mit den num_words ersten token der Topics
topics = lda_model.show_topics(num_topics=nr, num_words=10)
with open('topic_modeling_#topics' + str(nr) + '#upperlimit' + str(limit) + '.txt',
"w") as open_out_file:
# schreibt für jedes Topic die num_words token ins out-file
for ele in topics:
curr_list = ele[1].split("+")
open_out_file.write("Topic " + str(ele[0] + 1) + ": \n")
for word in curr_list:
open_out_file.write(word + "\n")
open_out_file.write("\n")
# berechnet die Topic-Distribution
topic_per_doc = lda_model.get_document_topics(corpus)
doc_counter = 1
open_out_file.write("\n")
# schreibt für jedes Dokument die enthaltenen Topics ins out-file
for topic in topic_per_doc:
open_out_file.write("Topic distribution in Document: " + str(doc_counter) + "\n")
open_out_file.write(str(topic) + "\n")
open_out_file.write("\n")
doc_counter += 1
print("Das file topic_modeling_#topics" + str(nr) + "#upperlimit" + str(
limit) + ".txt wurde ausgegeben")
Dict_id_words = Dictionary(data_lemmatized)
Dict_id_words = {}
all_documents = []
documents = lines_generator(infile)
print("reading input...")
for doc in documents:
all_documents.append(doc)
Dict_id_words = Dictionary(all_documents)
for limit in stop_word_upper_limits:
# entfernt Wörter, die in mehr als limit der Dokumente vorkommen
Dict_id_words.filter_extremes(
no_below=1, no_above=limit, keep_n=None, keep_tokens=None
)
# erstellt ein corpus mit den relevanten Dokumenten
corpus = [Dict_id_words.doc2bow(text) for text in all_documents]
print(limit)
print("Building model ....")
for nr in nr_of_topics:
# erstellt ein LDA model für die Anzahl Topics
lda_model = LdaModel(
corpus=corpus,
id2word=Dict_id_words,
num_topics=nr,
random_state=100,
iterations=100,
update_every=1,
chunksize=100,
passes=10,
alpha="auto",
per_word_topics=True,
)
html_visualisation(lda_model, corpus, Dict_id_words, nr, limit)
# erstellt eine liste von (nr_topics) Topics mit den num_words wichtigsten Wörtern pro Topic aus
topics = lda_model.show_topics(num_topics=nr, num_words=20)
produce_outfiles(topics, lda_model, corpus, nr, limit)
# füllt den Dictionary wieder auf, damit die nächste Iteration
# mit dem ursprünglichen Dictionary beginnt
Dict_id_words = Dictionary(all_documents)
def main():
args = parse_arguments()
topic_modeling(args.in_file, args.number_topics, args.upper_limits)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Erstellt ein Topic Modeling, entweder mit default Werten oder mit eigenen Anzahl Topics und Stopwords-limit")
parser.add_argument("in_file", type=str, help="Input-Datei: Lemmatisierte Dokumente")
parser.add_argument('-nt', '--number_topics', type=int, nargs='+', default=[7, 15],
help="optionales Angabe drei Anzahlen von Topics")
parser.add_argument('-ul', '--upper_limits', type=float, nargs='+', default=[0.5, 0.8],
help="optionales Angabe dreier oberer Limiten für die Anzahl Dokumente, "
"in denen ein Wort erscheinen kann, bevor es zum Stopword wird",
)
args = parser.parse_args()
topic_modeling(args.in_file, args.number_topics, args.upper_limits)
main()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment