Ich lese und markiere gerne Dinge (ich benutze einen Kindle). Ich habe das Gefühl, dass ich beim Lesen nicht mehr als 10 % der Informationen, die ich konsumiere, behalten kann, aber erst durch das erneute Lesen der Highlights oder das Zusammenfassen des Buches anhand dieser Informationen verstehe ich tatsächlich, was ich lese.

Das Drawback ist, dass ich manchmal viel hervorhebe.

Und mit viel meine ich VIEL. Wir können sie nicht einmal „Schlüsselnotizen“ nennen.

In solchen Fällen verschwende ich nach der Lektüre des Buches entweder viel Zeit mit der Zusammenfassung oder höre einfach damit auf (Letzteres kommt häufiger vor).

Ich habe kürzlich ein Buch gelesen, das mir sehr intestine gefallen hat, und ich möchte das, was mich am meisten beeindruckt hat, vollständig behalten. Aber es battle wiederum eines dieser Bücher, die ich übermäßig hervorgehoben habe.

Und ich wollte nicht viel von meiner knappen Freizeit dafür verwenden. Deshalb habe ich beschlossen, den Prozess zu automatisieren und meine technischen/Datenkenntnisse zu nutzen. Da ich mit dem Ergebnis zufrieden bin, dachte ich, ich würde es teilen, damit auch alle Interessierten von diesem Device profitieren können.

Haftungsausschluss: Mein Kindle ist ziemlich alt, daher sollte dies auch auf neuen funktionieren. Tatsächlich gibt es einen etwas besseren Ansatz für neue Kindle-Versionen (wird auch in diesem Beitrag erklärt).


Das Projekt

Definieren wir das Ziel: Erstellen Sie eine Zusammenfassung unserer Kindle-Highlights.

Als ich darüber nachdachte, stellte ich mir die folgende einfache Pipeline für ein einzelnes Buch vor:

  • Holen Sie sich die Buch-Highlights
  • Erstellen Sie ein RAG oder ähnliches
  • Exportieren Sie die Zusammenfassung

Das Ergebnis unterscheidet sich im ersten Teil, was jedoch auf die erforderliche Vorverarbeitung unter Berücksichtigung der Datenstruktur zurückzuführen ist.

Deshalb werde ich diesen Beitrag in zwei Hauptabschnitte strukturieren:

  • Datenabruf und -verarbeitung
  • KI-Modell und Ausgabe

1. Datenerhebung und -verarbeitung

Meine Instinct sagte mir, dass es eine Möglichkeit gibt, Highlights aus meinem Kindle zu extrahieren. Am Ende werden sie dort gelagert, additionally brauche ich nur eine Möglichkeit, sie herauszuholen.

Es gibt mehrere Möglichkeiten, dies zu tun, aber ich wollte einen Ansatz, der sowohl mit Büchern funktioniert, die im offiziellen Kindle-Store gekauft wurden, als auch mit PDFs oder Dateien, die ich von meinem Laptop computer gesendet habe.

Und ich habe auch beschlossen, keine vorhandene Software program zum Extrahieren der Daten zu verwenden. Nur mein E-E-book und mein Laptop computer (und ein USB-Anschluss, der beide verbindet).

Glücklicherweise ist kein Jailbreak erforderlich und es gibt zwei Möglichkeiten, dies zu tun, abhängig von Ihrer Kindle-Model:

  1. Alle Kindles haben (vermutlich) eine Datei im Dokumentenordner mit dem Namen Meine Clippings.txt. Es enthält buchstäblich alle Ausschnitte, die Sie zu irgendeinem Zeitpunkt und in jedem Buch gemacht haben.
  2. Neue Kindles verfügen außerdem über eine SQLite-Datei im Systemverzeichnis mit dem Namen annotations.db. Dadurch werden Ihre Highlights strukturierter dargestellt.

In diesem Beitrag verwende ich Methode 1 (My Clippings.txt), hauptsächlich weil mein Kindle nicht über die Datenbank annotations.db verfügt. Aber wenn Sie das Glück haben, über die Datenbank zu verfügen, verwenden Sie sie, da sie einfacher und qualitativ hochwertiger ist (der größte Teil der Vorverarbeitung, die wir als Nächstes sehen werden, wird wahrscheinlich nicht erforderlich sein).

Das Abrufen der Ausschnitte ist additionally so einfach wie das Lesen des TXT. Hier sind einige wichtige Aspekte und Probleme, auf die ich bei der Verwendung dieser Methode gestoßen bin:

  • Alle Bücher befinden sich in derselben Datei.
  • Ich bin mir über die genaue „Clipping“-Definition auf Amazon nicht sicher, aber ich habe sie so gesehen: Alles, was Sie zu irgendeinem Zeitpunkt hervorheben. Auch wenn Sie es löschen oder erweitern, bleibt das Authentic im TXT. Ich gehe davon aus, dass das so ist, weil wir tatsächlich mit einer TXT-Datei arbeiten und es sehr schwierig ist, Dinge zu löschen, die in keiner Weise indiziert sind.
  • Für das Clipping gibt es eine Grenze: Ich kenne den genauen Schwellenwert nicht, aber sobald wir ihn überschreiten, können wir keine weiteren Clippings mehr abrufen. Dies geschieht, weil sonst jemand das gesamte Buch markieren, es extrahieren und unlawful weitergeben könnte.

Und das ist die Anatomie eines Ausschnitts:

==========
E-book Title (Creator Title)
- Your Spotlight on web page 145 | Location 2212-2212 | Added on Sunday, August 30, 2020 11:25:29 PM

transparency downside results in the identical place as
==========

Der erste Schritt besteht additionally darin, die Highlights zu analysieren, und hier beginnen wir, Python-Code zu sehen:

def parse_clippings(file_path):

    uncooked = Path(file_path).read_text(encoding="utf-8")
    entries = uncooked.break up("==========")

    highlights = ()

    for entry in entries:

        traces = (l.strip() for l in entry.strip().break up("n") if l.strip())

        if len(traces) < 3:
            proceed

        e book = traces(0)

        if "Spotlight" not in traces(1):
            proceed

        location_match = re.search(r"Location (d+)", traces(1))
        if not location_match:
            proceed

        location = int(location_match.group(1))

        textual content = " ".be part of(traces(2:)).strip()

        highlights.append(
            {
                "e book": e book,
                "location": location,
                "textual content": textual content
            }
        )

    return highlights

Angesichts des Pfads der Ausschnittsdatei teilt diese Funktion den Textual content lediglich in die verschiedenen Einträge auf und durchläuft sie dann in einer Schleife. Für jeden Eintrag werden der Titelname, der Ort und der hervorgehobene Textual content extrahiert.

Diese endgültige Struktur (eine Liste von Wörterbüchern) erleichtert das Filtern nach Buch:

(
    h for h in highlights
    if book_name.decrease() in h("e book").decrease()
)

Nach dem Filtern müssen wir die Highlights ordnen. Da Ausschnitte an die TXT-Datei angehängt werden, basiert die Reihenfolge darauf, wann wir sie hervorheben, und nicht auf der Place des Textes.

Und ich persönlich möchte, dass meine Ergebnisse so aussehen, wie sie im Buch erscheinen, daher ist eine Bestellung erforderlich:

sorted(highlights, key=lambda x: x("location"))

Wenn Sie nun Ihre Ausschnittsdatei überprüfen, finden Sie möglicherweise doppelte Ausschnitte (oder doppelte Unterausschnitte). Dies liegt daran, dass jedes Mal, wenn Sie eine Hervorhebung bearbeiten (weil Sie beispielsweise nicht alle gewünschten Wörter eingefügt haben), diese als neue Hervorhebung gewertet wird. Es wird additionally zwei sehr ähnliche Ausschnitte im TXT geben. Oder sogar noch mehr, wenn Sie es häufig bearbeiten.

Wir müssen dies in den Griff bekommen, indem wir irgendwie eine Deduplizierung anwenden. Es ist einfacher als erwartet:

def deduplicate(highlights):

    clear = ()
    for h in highlights:

        textual content = h("textual content")
        duplicate = False

        for c in clear:

            if textual content == c("textual content"):
                duplicate = True
                break
            if textual content in c("textual content"):
                duplicate = True
                break
            if c("textual content") in textual content:
                c("textual content") = textual content
                duplicate = True
                break

        if not duplicate:
            clear.append(h)

    return clear

Es ist sehr einfach und könnte perfektioniert werden, aber wir prüfen grundsätzlich, ob es aufeinanderfolgende Ausschnitte mit demselben Textual content (oder einem Teil davon) gibt, und behalten den längsten Teil bei.

Im Second haben wir die Buch-Highlights richtig sortiert und können die Vorverarbeitung hier beenden. Aber das kann ich nicht. Ich magazine es, Titel jedes Mal hervorzuheben, weil ich bei der Zusammenfassung jeder Hervorhebung einen Abschnitt richtig zuordnen kann.

Aber unser Code ist nicht in der Lage, zwischen einem echten Spotlight und einem Abschnittstitel zu unterscheiden … Noch nicht. Siehe unten:

def is_probable_title(textual content):

    textual content = textual content.strip()
    if len(textual content) > 120:
        return False
    if textual content.endswith("."):
        return False

    phrases = textual content.break up()
    if len(phrases) > 12:
        return False

    # chapter type prefix
    if has_chapter_prefix(textual content):
        return True

    # capitalization ratio
    capitalized = sum(
        1 for w in phrases if w(0).isupper()
    )

    cap_ratio = capitalized / len(phrases)

    # stopword ratio
    stopword_count = sum(
        1 for w in phrases if w.decrease() in STOPWORDS
    )

    stop_ratio = stopword_count / len(phrases)

    rating = 0
    if cap_ratio > 0.6:
        rating += 1
    if stop_ratio < 0.3:
        rating += 1
    if len(phrases) <= 6:
        rating += 1

    return rating >= 2

Es kann ziemlich willkürlich erscheinen und ist nicht die beste Lösung für dieses Drawback, aber es funktioniert ganz intestine. Es verwendet eine Heuristik, die auf Großschreibung, Länge, Stoppwörtern und Präfixen basiert.

Diese Funktion wird innerhalb einer Schleife durch alle Hervorhebungen aufgerufen, wie wir in früheren Funktionen gesehen haben, um zu prüfen, ob es sich bei einer Hervorhebung um einen Titel handelt oder nicht. Das Ergebnis ist eine „Abschnitts“-Liste von Wörterbüchern, wobei das Wörterbuch zwei Schlüssel hat:

  • Titel: der Abschnittstitel.
  • Highlights: Die Highlights des Abschnitts.

Im Second sind wir bereit für eine Zusammenfassung.

KI-Modell und Ausgabe

Ich wollte, dass dies ein kostenloses Projekt ist, additionally brauchen wir ein KI-Modell, das Open Supply ist.

Ich dachte, dass Ollama (1) eine der besten Möglichkeiten sei, ein Projekt wie dieses durchzuführen (zumindest lokal). Außerdem bleiben unsere Daten immer bei uns und wir können die Modelle offline betreiben.

Nach der Set up battle der Code einfach. Ich bin kein schneller Ingenieur, additionally würde jeder mit dem entsprechenden Know-how noch bessere Ergebnisse erzielen, aber das hier ist, was für mich funktioniert:

def summarize_with_ollama(textual content, mannequin):

    immediate = f"""
    You're summarizing a e book from reader highlights.

    Produce a structured abstract with:

    - Primary thesis
    - Temporary abstract
    - Key concepts
    - Essential ideas
    - Sensible takeaways

    Highlights:

    {textual content}
    """

    end result = subprocess.run(
        ("ollama", "run", mannequin),
        enter=immediate,
        textual content=True,
        capture_output=True
    )

    return end result.stdout

Ganz einfach, ich weiß. Aber es funktioniert zum Teil, weil die Datenvorverarbeitung intensiv battle, aber auch, weil wir bereits die vorhandenen Modelle nutzen.

Aber was machen wir mit der Zusammenfassung? Ich verwende gerne Obsidian (2), daher ist der Export einer Markdown-Datei sinnvoller. Hier haben Sie es:

def export_markdown(e book, sections, abstract, output):

    md = f"# {e book}nn"
    for part in sections:
        md += f"## {part('title')}nn"
        for h in part("highlights"):
            md += f"- {h}n"
        md += "n"

    md += "n---nn"
    md += "## E-book Summarynn"
    md += abstract

    output_path = Path(output)
    output_path.mother or father.mkdir(dad and mom=True, exist_ok=True)
    output_path.write_text(md, encoding="utf-8")
    print(f"nSaved to {output_path}")

Et voilà.

Und so komme ich von Highlights zu einer vollständigen Markdown-Zusammenfassung (direkt zu Obsidian, wenn ich möchte) mit weniger als 300 Zeilen Python-Code!

Vollständiger Code und Check

Hier ist der vollständige Code, für den Fall, dass Sie ihn kopieren und einfügen möchten. Es enthält das, was wir gesehen haben, sowie einige Hilfsfunktionen und Argumentanalyse:

import re
import argparse
from pathlib import Path
import subprocess


# ---------- PARSE CLIPPINGS ----------

def parse_clippings(file_path):

    uncooked = Path(file_path).read_text(encoding="utf-8")
    entries = uncooked.break up("==========")

    highlights = ()

    for entry in entries:

        traces = (l.strip() for l in entry.strip().break up("n") if l.strip())

        if len(traces) < 3:
            proceed

        e book = traces(0)

        if "Spotlight" not in traces(1):
            proceed

        location_match = re.search(r"Location (d+)", traces(1))
        if not location_match:
            proceed

        location = int(location_match.group(1))

        textual content = " ".be part of(traces(2:)).strip()

        highlights.append(
            {
                "e book": e book,
                "location": location,
                "textual content": textual content
            }
        )

    return highlights


# ---------- FILTER BOOK ----------

def filter_book(highlights, book_name):

    return (
        h for h in highlights
        if book_name.decrease() in h("e book").decrease()
    )


# ---------- SORT ----------

def sort_by_location(highlights):

    return sorted(highlights, key=lambda x: x("location"))


# ---------- DEDUPLICATE ----------

def deduplicate(highlights):

    clear = ()

    for h in highlights:

        textual content = h("textual content")
        duplicate = False

        for c in clear:

            if textual content == c("textual content"):
                duplicate = True
                break

            if textual content in c("textual content"):
                duplicate = True
                break

            if c("textual content") in textual content:
                c("textual content") = textual content
                duplicate = True
                break

        if not duplicate:
            clear.append(h)

    return clear


# ---------- TITLE DETECTION ----------

STOPWORDS = {
    "the","and","or","however","of","in","on","at","for","to",
    "is","are","was","had been","be","been","being",
    "that","this","with","as","by","from"
}


def has_chapter_prefix(textual content):

    return bool(
        re.match(
            r"^(chapter|half|part)s+d+|^d+(.))|^(ivxlcdm)+.",
            textual content.decrease()
        )
    )


def is_probable_title(textual content):

    textual content = textual content.strip()
    if len(textual content) > 120:
        return False
    if textual content.endswith("."):
        return False

    phrases = textual content.break up()

    if len(phrases) > 12:
        return False
    # chapter type prefix
    if has_chapter_prefix(textual content):
        return True

    # capitalization ratio
    capitalized = sum(
        1 for w in phrases if w(0).isupper()
    )
    cap_ratio = capitalized / len(phrases)

    # stopword ratio
    stopword_count = sum(
        1 for w in phrases if w.decrease() in STOPWORDS
    )
    stop_ratio = stopword_count / len(phrases)

    rating = 0
    if cap_ratio > 0.6:
        rating += 1
    if stop_ratio < 0.3:
        rating += 1
    if len(phrases) <= 6:
        rating += 1

    return rating >= 2


# ---------- GROUP SECTIONS ----------

def group_by_sections(highlights):

    sections = ()
    present = {
        "title": "Introduction",
        "highlights": ()
    }

    for h in highlights:
        textual content = h("textual content")

        if is_probable_title(textual content):
            sections.append(present)
            present = {
                "title": textual content,
                "highlights": ()
            }
        else:
            present("highlights").append(textual content)
    sections.append(present)
    return sections


# ---------- SUMMARY ----------




# ---------- EXPORT MARKDOWN ----------

def export_markdown(e book, sections, abstract, output):

    md = f"# {e book}nn"
    for part in sections:
        md += f"## {part('title')}nn"
        for h in part("highlights"):
            md += f"- {h}n"
        md += "n"

    md += "n---nn"
    md += "## E-book Summarynn"
    md += abstract

    output_path = Path(output)
    output_path.mother or father.mkdir(dad and mom=True, exist_ok=True)
    output_path.write_text(md, encoding="utf-8")
    print(f"nSaved to {output_path}")


# ---------- MAIN ----------

def major():

    parser = argparse.ArgumentParser()

    parser.add_argument("--book", required=True)
    parser.add_argument("--output", required=False, default=None)
    parser.add_argument(
        "--clippings",
        default="Information/My Clippings.txt"
    )
    parser.add_argument(
        "--model",
        default="mistral"
    )

    args = parser.parse_args()

    highlights = parse_clippings(args.clippings)
    highlights = filter_book(highlights, args.e book)
    highlights = sort_by_location(highlights)
    highlights = deduplicate(highlights)
    sections = group_by_sections(highlights)

    all_text = "n".be part of(
        h("textual content") for h in highlights
    )

    abstract = summarize_with_ollama(all_text, args.mannequin)

    if args.output:
        export_markdown(
            args.e book,
            sections,
            abstract,
            args.output
        )
    else:
        print("n---- HIGHLIGHTS ----n")
        for h in highlights:
            print(f"{h('textual content')}n")

        print("n---- SUMMARY ----n")
        print(abstract)


if __name__ == "__main__":
    major()

Aber mal sehen, wie es funktioniert! Der Code selbst ist nützlich, aber ich wette, Sie sind bereit, die Ergebnisse zu sehen. Da es sehr lang ist, habe ich beschlossen, den ersten Teil zu löschen, da er lediglich die Hervorhebungen kopiert und einfügt.

Ich habe zufällig ein Buch ausgewählt, das ich vor etwa 6 Jahren (2020) gelesen habe und das „Speaking to Strangers“ von Malcolm Gladwell heißt (ein Bestseller, eine ziemlich unterhaltsame Lektüre). Sehen Sie sich die gedruckte Ausgabe des Modells an (nicht den Markdown):

$ python3 kindle_summary.py --book "Speaking to Strangers"

---- HIGHLIGHTS ----

...


---- SUMMARY ----

 Title: Speaking to Strangers: What We Ought to Know About Human Interplay

Primary Thesis: The e book explores the complexities and paradoxes of human 
interplay, notably in conversations with strangers, and emphasizes 
the significance of warning, humility, and understanding the context in 
which these interactions happen.

Temporary Abstract: The writer delves into the misconceptions and shortcomings 
in our dealings with strangers, specializing in how we regularly make incorrect 
assumptions about others based mostly on restricted info or preconceived 
notions. The e book presents insights into why this occurs, its penalties, 
and methods for bettering our skill to know and talk 
successfully with folks we do not know.

Key Concepts:
1. The transparency downside and the default-to-truth downside: Individuals usually 
assume that others are open books, sharing their true feelings and 
intentions, when in actuality this isn't at all times the case.
2. Coupling: Behaviors are strongly linked to particular circumstances and 
situations, making it important to know the context during which a 
stranger operates.
3. Limitations of understanding strangers: There isn't a good mechanism 
for peering into the minds of these we have no idea, emphasizing the necessity 
for restraint and humility when interacting with strangers.

Essential Ideas:
1. Emotional responses falling outdoors expectations
2. Defaulting to fact
3. Transparency as an phantasm
4. Contextual understanding in coping with strangers
5. The paradox of speaking to strangers (want versus terribleness)
6. The phenomenon of coupling and its affect on habits
7. Blaming the stranger when issues go awry

Sensible Takeaways:
1. Acknowledge that folks could not at all times seem as they appear, each 
emotionally and behaviorally.
2. Perceive the significance of context in deciphering strangers' 
behaviors and intentions.
3. Be cautious and humble when interacting with strangers, acknowledging 
our limitations in understanding them totally.
4. Keep away from leaping to conclusions about strangers based mostly on restricted 
info or preconceived notions.
5. Settle for that there'll at all times be a point of ambiguity and 
complexity in coping with strangers.
6. Keep away from penalizing others for defaulting to fact as a protection mechanism.
7. When interactions with strangers go awry, contemplate the position one would possibly 
have performed in contributing to the scenario reasonably than solely blaming 
the stranger.

Und das alles innerhalb weniger Sekunden. Ganz cool meiner Meinung nach.

Abschluss

Und das ist im Grunde der Grund, warum ich jetzt eine Menge Freizeit spare (die ich nutzen kann, um Beiträge wie diesen zu schreiben), indem ich meine Datenkenntnisse und meine KI nutze.

Ich hoffe, Ihnen hat die Lektüre gefallen und Sie waren motiviert, es auszuprobieren! Es wird nicht besser sein als die Zusammenfassung, die Sie aufgrund Ihrer eigenen Wahrnehmung des Buches schreiben würden … Aber davon wird es nicht weit entfernt sein!

Vielen Dank für Ihre Aufmerksamkeit. Wenn Sie Ideen oder Vorschläge haben, können Sie gerne einen Kommentar abgeben!


Ressourcen

(1) Ollama. (nd). Ollama. https://ollama.com

(2) Obsidian. (nd). Obsidian. https://obsidian.md

Von admin

Schreibe einen Kommentar

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