Dieses Tool nutzt moderne NLP-Technologien (Natural Language Processing) und eine benutzerfreundliche GUI basierend auf PyQt6. In diesem Blogbeitrag stelle ich Ihnen den Code vor, erkläre die Installation und zeige Ihnen, wie Sie das Tool in der Praxis einsetzen können.

Was macht der Frage-Antwort-Extraktor?

Der Frage-Antwort-Extraktor ist ein Python-Programm, das:

  • Texte aus verschiedenen Dateiformaten einliest (PDF, TXT, DOCX, PPTX),
  • Fragen aus einer Textdatei verarbeitet,
  • mithilfe eines vortrainierten NLP-Modells (hier: deepset/gelectra-base-germanquad) Antworten findet,
  • Ergebnisse in einer GUI anzeigt und Berichte in verschiedenen Formaten (CSV, Excel, JSON, PDF) generiert.

Dieses Tool eignet sich perfekt für Anwendungsfälle wie:

  • Recherche: Schnelles Finden von Antworten in umfangreichen Dokumenten.
  • Bildung: Automatische Auswertung von Lehrmaterialien.
  • Dokumentenanalyse: Extraktion relevanter Informationen aus Berichten oder Präsentationen.

Der Code

Hier ist der vollständige Code für den Frage-Antwort-Extraktor:

python

import os
os.environ["OMP_NUM_THREADS"] = "1"  # Begrenzt OpenMP-Threads
os.environ["MKL_NUM_THREADS"] = "1"  # Begrenzt MKL-Threads

import fitz  # PyMuPDF
from transformers import pipeline, AutoModelForQuestionAnswering, AutoTokenizer
import re
import pandas as pd
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
from io import BytesIO
import docx
import pptx
import mimetypes
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, 
                             QLabel, QFileDialog, QProgressBar, QTextEdit, QMessageBox)
from PyQt6.QtCore import Qt
import torch
torch.set_num_threads(1)  # Begrenzt PyTorch-Threads

class QuestionAnswerApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Frage-Antwort Extraktor")
        self.setGeometry(100, 100, 800, 600)
        self.init_ui()

    def init_ui(self):
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        layout = QVBoxLayout(main_widget)

        # Dateiauswahl
        self.context_label = QLabel("Keine Kontextdatei ausgewählt")
        layout.addWidget(self.context_label)
        self.select_context_button = QPushButton("Kontextdatei auswählen")
        self.select_context_button.clicked.connect(self.select_context)
        layout.addWidget(self.select_context_button)

        self.questions_label = QLabel("Keine Fragedatei ausgewählt")
        layout.addWidget(self.questions_label)
        self.select_questions_button = QPushButton("Fragedatei auswählen")
        self.select_questions_button.clicked.connect(self.select_questions)
        layout.addWidget(self.select_questions_button)

        # Start-Button
        self.start_button = QPushButton("Analyse starten")
        self.start_button.clicked.connect(self.start_analysis)
        self.start_button.setEnabled(False)
        layout.addWidget(self.start_button)

        # Fortschrittsbalken
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        layout.addWidget(self.progress_bar)

        # Ausgabebereich
        self.output_text = QTextEdit()
        self.output_text.setReadOnly(True)
        layout.addWidget(self.output_text)

        # Export-Button
        self.export_button = QPushButton("Als TXT exportieren")
        self.export_button.clicked.connect(self.export_to_txt)
        self.export_button.setEnabled(False)
        layout.addWidget(self.export_button)

    def select_context(self):
        file_path = QFileDialog.getOpenFileName(self, "Kontextdatei auswählen", "", 
                                                "Unterstützte Dateien (*.pdf *.txt *.docx *.pptx)")[0]
        if file_path:
            self.context_path = file_path
            self.context_label.setText(f"Kontext: {os.path.basename(file_path)}")
            self.check_start_enabled()

    def select_questions(self):
        file_path = QFileDialog.getOpenFileName(self, "Fragedatei auswählen", "", "Text Dateien (*.txt)")[0]
        if file_path:
            self.questions_path = file_path
            self.questions_label.setText(f"Fragen: {os.path.basename(file_path)}")
            self.check_start_enabled()

    def check_start_enabled(self):
        self.start_button.setEnabled(hasattr(self, "context_path") and hasattr(self, "questions_path"))

    def start_analysis(self):
        self.output_text.clear()
        self.progress_bar.setVisible(True)
        self.start_button.setEnabled(False)
        self.export_button.setEnabled(False)

        # Ordner für Berichte erstellen
        reporting_folder = "reporting"
        base_filename = os.path.splitext(os.path.basename(self.context_path))[0]
        self.report_folder = os.path.join(reporting_folder, base_filename)
        os.makedirs(self.report_folder, exist_ok=True)

        # Text und Fragen einlesen
        contexts = read_text_from_file(self.context_path)
        if not contexts:
            QMessageBox.critical(self, "Fehler", "Konnte Kontext nicht einlesen.")
            self.reset_ui()
            return

        questions = read_questions_from_file(self.questions_path)
        if not questions:
            QMessageBox.critical(self, "Fehler", "Konnte Fragen nicht einlesen.")
            self.reset_ui()
            return

        # Modell laden
        model_name = "deepset/gelectra-base-germanquad"
        nlp = pipeline("question-answering", model=model_name, tokenizer=model_name)

        # Fragen beantworten
        answers = process_questions(questions, contexts, nlp, self.update_progress)
        if not answers:
            self.output_text.setText("Keine Antworten mit ausreichendem Score gefunden.")
        else:
            self.display_results(answers)
            generate_reports(answers, self.report_folder)
            self.export_button.setEnabled(True)

        self.reset_ui()

    def update_progress(self, value, total):
        self.progress_bar.setMaximum(total)
        self.progress_bar.setValue(value)
        QApplication.processEvents()

    def display_results(self, answers):
        output = "=== Ergebnisse ===\n"
        for answer in answers[:5]:  # Erste 5 als Vorschau
            output += (f"Frage: {answer[0]}\nAntwort: {answer[1]}\nSeite: {answer[2]}\n"
                       f"Score: {answer[3]:.2f}\nTextstelle: {answer[4]}\n\n")
        self.output_text.setText(output)
        self.answers = answers

    def export_to_txt(self):
        txt_path = QFileDialog.getSaveFileName(self, "Als TXT speichern", "", "Text Dateien (*.txt)")[0]
        if txt_path:
            with open(txt_path, "w", encoding="utf-8") as f:
                f.write(self.output_text.toPlainText())
            QMessageBox.information(self, "Erfolg", f"Ergebnisse in '{txt_path}' exportiert.")

    def reset_ui(self):
        self.progress_bar.setVisible(False)
        self.start_button.setEnabled(True)

# Funktionen zum Einlesen von Text
def read_text_from_pdf(file_path):
    try:
        document = fitz.open(file_path)
        pages = [(page.get_text(), i + 1) for i, page in enumerate(document)]
        document.close()
        return pages
    except Exception as e:
        print(f"Fehler beim Lesen der PDF: {e}")
        return []

def read_text_from_txt(file_path):
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            return [(file.read(), 1)]
    except Exception as e:
        print(f"Fehler beim Lesen der TXT: {e}")
        return []

def read_text_from_docx(file_path):
    try:
        document = docx.Document(file_path)
        return [('\n'.join(para.text for para in document.paragraphs), 1)]
    except Exception as e:
        print(f"Fehler beim Lesen der DOCX: {e}")
        return []

def read_text_from_pptx(file_path):
    try:
        presentation = pptx.Presentation(file_path)
        return [('\n'.join(shape.text for shape in slide.shapes if hasattr(shape, "text")), i + 1)
                for i, slide in enumerate(presentation.slides)]
    except Exception as e:
        print(f"Fehler beim Lesen der PPTX: {e}")
        return []

def read_text_from_file(file_path):
    mime_type, _ = mimetypes.guess_type(file_path)
    if mime_type == 'application/pdf':
        return read_text_from_pdf(file_path)
    elif mime_type == 'text/plain':
        return read_text_from_txt(file_path)
    elif mime_type == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
        return read_text_from_docx(file_path)
    elif mime_type == 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
        return read_text_from_pptx(file_path)
    else:
        print(f"Unsupported file type: {mime_type}")
        return []

def read_questions_from_file(file_path):
    try:
        with open(file_path, 'r', encoding='utf-8') as file:
            return [q.strip() for q in file.readlines() if q.strip()]
    except Exception as e:
        print(f"Fehler beim Lesen der Fragen: {e}")
        return []

def clean_number_format(text):
    text = re.sub(r'(\d)(?=(\d{3})+(\.|\b))', r'\1.', text)
    text = re.sub(r'(?<!\d)(\d+)(?=\.\d+)', r'\1.', text)
    return text

def find_relevant_text(context, answer_start, answer_end):
    start = max(answer_start - 100, 0)
    end = min(answer_end + 100, len(context))
    return context[start:end]

def process_questions(questions, contexts, nlp, progress_callback):
    answers = []
    total_steps = len(questions) * len(contexts)
    step = 0
    for question in questions:
        for context, page_num in contexts:
            cleaned_context = clean_number_format(context)
            result = nlp(question=question, context=cleaned_context, max_answer_len=100)
            if result['score'] > 0.5:
                relevant_text = find_relevant_text(cleaned_context, result['start'], result['end'])
                answers.append((question, result['answer'], page_num, result['score'], relevant_text))
            step += 1
            progress_callback(step, total_steps)
    return answers

def generate_reports(answers, report_folder):
    df = pd.DataFrame(answers, columns=["Frage", "Antwort", "Seite", "Score", "Textstelle"])
    df.to_csv(os.path.join(report_folder, "report.csv"), index=False, encoding='utf-8')
    df.to_excel(os.path.join(report_folder, "report.xlsx"), index=False, engine='openpyxl')
    df.to_json(os.path.join(report_folder, "report.json"), orient='records', lines=True, force_ascii=False)
    
    # PDF-Bericht
    buffer = BytesIO()
    c = canvas.Canvas(buffer, pagesize=letter)
    text = c.beginText(40, letter[1] - 40)
    text.setFont("Helvetica", 10)
    for _, row in df.iterrows():
        text.textLines([f"Frage: {row['Frage']}", f"Antwort: {row['Antwort']}", 
                        f"Seite: {row['Seite']}", f"Score: {row['Score']:.2f}", 
                        f"Textstelle: {row['Textstelle']}", ""])
    c.drawText(text)
    c.showPage()
    c.save()
    with open(os.path.join(report_folder, "report.pdf"), 'wb') as f:
        f.write(buffer.getvalue())

def main():
    app = QApplication([])
    window = QuestionAnswerApp()
    window.show()
    app.exec()

if __name__ == "__main__":
    main()

Installationsanweisungen

Um den Frage-Antwort-Extraktor zu nutzen, müssen Sie die folgenden Schritte ausführen:

1. Python installieren

Laden Sie die neueste Version von Python (mindestens 3.8) herunter und installieren Sie sie.

2. Virtuelle Umgebung erstellen (optional, aber empfohlen)

bash

python -m venv qa_env
source qa_env/bin/activate  # Linux/Mac
qa_env\Scripts\activate     # Windows

3. Abhängigkeiten installieren

Installieren Sie die erforderlichen Python-Bibliotheken mit pip:

bash

pip install PyMuPDF transformers torch pandas reportlab python-docx python-pptx PyQt6 openpyxl mimetypes
  • PyMuPDF: Für die Verarbeitung von PDF-Dateien.
  • Transformers: Für das NLP-Modell von Hugging Face.
  • PyTorch: Als Backend für das Modell.
  • Pandas: Für die Datenverarbeitung und Berichtgenerierung.
  • ReportLab: Für die PDF-Berichtserstellung.
  • python-docx: Zum Lesen von DOCX-Dateien.
  • python-pptx: Zum Lesen von PPTX-Dateien.
  • PyQt6: Für die grafische Benutzeroberfläche.
  • openpyxl: Für die Excel-Datei-Generierung.

4. Code ausführen

Speichern Sie den Code in einer Datei (z. B. qa_extractor.py) und führen Sie ihn aus:

bash

python qa_extractor.py

Einsatzfelder

Dieses Tool ist vielseitig einsetzbar. Hier sind einige Beispiele:

1. Wissenschaftliche Recherche

Forscher können große PDF-Dokumente hochladen (z. B. Studien oder Berichte) und gezielte Fragen stellen, um relevante Informationen schnell zu finden, ohne das gesamte Dokument manuell durchsuchen zu müssen.

2. Bildung und Training

Lehrer können Lehrmaterialien (z. B. Präsentationen oder Handouts) analysieren und automatisch Antworten auf Prüfungsfragen generieren lassen.

3. Unternehmensdokumentation

Unternehmen können interne Berichte oder Protokolle analysieren, um wichtige Details wie Projektergebnisse oder KPI-Daten zu extrahieren.

4. Juristische Analyse

Anwälte könnten Schriftsätze oder Verträge hochladen und spezifische Fragen stellen, um Klauseln oder Bedingungen zu identifizieren.


Funktionsweise im Detail

  1. Dateiauswahl: Über die GUI wählen Sie eine Kontextdatei (PDF, TXT, DOCX, PPTX) und eine TXT-Datei mit Fragen aus.
  2. Textverarbeitung: Das Tool liest den Text aus der Kontextdatei Seite für Seite (bei PDFs/PPTX) oder als Ganzes (TXT/DOCX).
  3. NLP-Analyse: Das Modell deepset/gelectra-base-germanquad (optimiert für deutsche Texte) sucht nach Antworten mit einem Score > 0.5.
  4. Ergebnisse: Die Antworten werden in der GUI angezeigt und als Berichte in einem reporting-Ordner gespeichert.

Fazit

Der Frage-Antwort-Extraktor ist ein mächtiges Werkzeug für alle, die schnell und effizient Informationen aus Textdokumenten extrahieren möchten. Mit der Unterstützung mehrerer Dateiformate und der Integration eines modernen NLP-Modells ist es sowohl für den privaten als auch den professionellen Einsatz geeignet.


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert