Hintergrund meiner Frage ist auch diesmal wieder (viewtopic.php?f=1&t=53315)
Ich verlagere es in einen eigenen Thread, da sich die "neue" von mir erdachte Aufgabenstellung ein wenig von dem unterscheidet, was dort mal von mir beschrieben wurde.
Beim Lernen, wie das NLP-Paket "spaCy" und die darin enthaltenen Sematik-Vergleichsmöglichkeit funktioniert, kam mir die Idee:
Würde man herausfinden können, ob es auch ähnliche Sprichwörter gibt?
Spoiler (ja, gibt es und funktioniert tatsächlich ganz gut)
Also die Frage ist schon beantwortet - allein der Weg gefällt mir nicht, denn er scheint mir recht umständlich /unperformant zu sein. Denn es wäre z.B. praktisch an der "Ähnlichkeitsschwelle" herumzujustieren und zu gucken, ab wo man ein wirklich gutes Ergebnis erhält. Leider dauert jeder Durchlauf mit ca 1000 Sprüchen mehr als eine halbe Stunde.
Code: Alles auswählen
import logging
from pathlib import Path
import spacy
nlp = spacy.load("de_core_news_lg")
logger = logging.getLogger(__name__)
BASE_PATH = Path("Daten", "WortspielGenerator")
IDENTITY = 1.0
THRESHOLD_SIMILAR = 0.86
def initialize_logging():
logs_path = BASE_PATH / "logs"
logs_path.mkdir(parents=True, exist_ok=True)
log_file_path = logs_path / "Sprueche_ausgemustert.txt"
logging.basicConfig(filename=str(log_file_path), level=logging.ERROR)
def read_sayings(filename):
#
# TODO Verbesserung mittels wiki-api - Texte direkt aus Wikipedia
# "Sprichwörter" auslesen?
#
# Tutorials:
# - https://wdqs-tutorial.toolforge.org/index.php/simple-queries/the-simplest-query/basic-sparql-query/
# - https://docs.python.org/3/howto/logging.html#logging-basic-tutorial
# - https://course.spacy.io/en/chapter1
#
with open(filename, "r", encoding="UTF-8") as file:
for line_number, line in enumerate(file, 1):
if line.startswith('"'):
saying, seperator, _ = line[1:].partition('"')
if seperator:
yield saying
else:
logging.error(
"Ende von Spruch in Zeile %d nicht gefunden: %r",
line_number,
line,
)
else:
line = line.rstrip()
if not (len(line) == 1 and line.isalpha()):
logging.error(
"Unerwartete(s) Zeichen in Zeile %d: %r",
line_number,
line,
)
def compare_sayings(filename):
compared_sayings = {}
for saying_a in read_sayings(filename):
for saying_b in read_sayings(filename):
if saying_b + saying_a not in compared_sayings:
doc_a = nlp(saying_a)
doc_b = nlp(saying_b)
similar = doc_a.similarity(doc_b)
compared_sayings[saying_a+saying_b] = (saying_a, saying_b, similar)
return compared_sayings
def find_similar_sayings(compared_sayings):
for key, (saying_a, saying_b, similar) in list(compared_sayings.items()):
if similar == IDENTITY or similar < THRESHOLD_SIMILAR:
compared_sayings.pop(key)
return compared_sayings
def show_sayings(sayings):
for element, (saying_a, saying_b, similar) in sayings.items():
print(saying_a)
print(saying_b)
print(similar)
print('----------------------------')
def main():
initialize_logging()
sayings_file_path = BASE_PATH / "Sprueche_A.txt"
compared = compare_sayings(sayings_file_path)
similar = find_similar_sayings(compared)
show_sayings(similar)
if __name__ == "__main__":
main()
Ich hab mich daher auf die Suche gemacht und mal geschaut, wie man ggf. die Performance noch verbessern kann:
Die Performance der eigentlichen Vergleichsfunktion kann ich erst mal nicht weiter beeinflussen.
Folgendes würde mir einfallen:
1) Jeden Spruch mit jedem anderen zu vergleichen, macht keinen Sinn. Es reicht nur eine Richtung zu vergleichen, da es ja symmetrisch ist. f(A,B) = f(B,A)
Dies ist im obigen Code schon berücksichtigt
2) Einen Spruch mit sich selbst zu vergleichen, ist auch Quatsch, da dies ja immer zu Identität führt. (Dies ist in compare_sayings nicht berücksichtigt und wird nur umständlich später wieder "herausgeporkelt"

Ich habe das Problem mal versucht isoliert zu betrachten und habe auch mal die Performance gemessen:
Code: Alles auswählen
texts = ['Adel', 'Sargnagel', 'Baum', 'Raum', 'Berg', 'Zwerg', 'Esel', 'Sessel', 'Flügel', 'Kleiderbügel', 'Geld', 'Held']
def compare(text_a, text_b):
# _eigentlich_ kommt hier ein zeitlich teurer Vergleich im Hinblick auf die Semantik
# aus Gründen der Lesbarkeit "banalisiert":
return text_a == text_b
def compare_texts1():
compared_texts = {}
for text_a in texts:
for text_b in texts:
# wg. Symmetrie reicht der Vergleich in eine Richtung
if text_b + text_a not in compared_texts:
compared_texts[text_a+text_b] = (text_a, text_b, compare(text_a, text_b))
return compared_texts
def compare_texts2():
compared_texts = {}
for i in range(len(texts)):
for j in range(i, len(texts)):
text_a = texts[i]
text_b = texts[j]
compared_texts[text_a+text_b] = (text_a, text_b, compare(text_a, text_b))
return compared_texts
def compare_texts3():
compared_texts = []
for i in range(len(texts)):
for j in range(i, len(texts)):
text_a = texts[i]
text_b = texts[j]
compared_texts.append((text_a, text_b, compare(text_a, text_b)))
return compared_texts
def compare_texts4():
compared_texts = []
for i in range(len(texts)):
for j in range(i + 1, len(texts)):
text_a = texts[i]
text_b = texts[j]
compared_texts.append((text_a, text_b, compare(text_a, text_b)))
return compared_texts
%timeit compare_texts1()
%timeit compare_texts2()
%timeit compare_texts3()
%timeit compare_texts4()

Code: Alles auswählen
46.9 µs ± 813 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
36.8 µs ± 5.37 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
32.8 µs ± 2.51 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
27.5 µs ± 2.67 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
"read_sayings(filename)" ist ja ein Generator - wenn ich das richtig verstehe.
D.h. ich habe keinen Index, wie oben im Experimental-Code.
Ich hatte dann die Idee "compare_sayings" selbst in einen Generator umzubauen, aber wie erkenne ich, welche Pärchen ich schon verglichen habe?
Ich fürchte, dass hier gerade voll auf der Leitung stehe....

Ich hatte auch schon überlegt, die Werte als CSV zu persistieren, so dass man leichter den Threshold ändern könnte, um zu sehen, wie gut ein höherer Wert funktioniert, indem man einmal die Vergleichsmatrix berechnet und diese dann immer wieder lädt, falls sie noch nicht vorhanden ist. (Was meint ihr dazu?)
Nun wie dem auch sei, ich freue mich, ob ihr vielleicht noch Ideen habt.
(Wenn mein Text zu lang ist, dann bitte auch gerne sagen/schreiben.)
Edit: Link korrigiert