7.2. Feature Extraction#
Das Modul sklearn.feature_extraction kann verwendet werden, um Features in einem Format zu extrahieren, das von Machine-Learning-Algorithmen unterstützt wird, aus Datensätzen, die Formate wie Text und Bild umfassen.
Hinweis
Feature Extraction unterscheidet sich stark von der Feature Selection: Erstere besteht darin, beliebige Daten, wie Text oder Bilder, in numerische Features zu transformieren, die für maschinelles Lernen verwendbar sind. Letzteres ist eine maschinelle Lernt technik, die auf diese Features angewendet wird.
7.2.1. Laden von Features aus Dictionaries#
Die Klasse DictVectorizer kann verwendet werden, um Feature-Arrays, die als Listen von Standard-Python-dict-Objekten repräsentiert sind, in die von scikit-learn-Schätzern verwendete NumPy/SciPy-Repräsentation umzuwandeln.
Obwohl die Verarbeitung von Python-dicts nicht besonders schnell ist, bieten sie die Vorteile, praktisch in der Anwendung, dünn besetzt (fehlende Features müssen nicht gespeichert werden) zu sein und zusätzlich zu Werten auch Feature-Namen zu speichern.
DictVectorizer implementiert die sogenannte One-of-K- oder „One-Hot“-Kodierung für kategoriale (auch nominale, diskrete) Features. Kategoriale Features sind „Attribut-Wert“-Paare, bei denen der Wert auf eine Liste diskreter, ungeordneter Möglichkeiten beschränkt ist (z. B. Themenidentifikatoren, Objekttypen, Tags, Namen…).
Im Folgenden ist „city“ ein kategoriales Attribut, während „temperature“ ein traditionelles numerisches Feature ist.
>>> measurements = [
... {'city': 'Dubai', 'temperature': 33.},
... {'city': 'London', 'temperature': 12.},
... {'city': 'San Francisco', 'temperature': 18.},
... ]
>>> from sklearn.feature_extraction import DictVectorizer
>>> vec = DictVectorizer()
>>> vec.fit_transform(measurements).toarray()
array([[ 1., 0., 0., 33.],
[ 0., 1., 0., 12.],
[ 0., 0., 1., 18.]])
>>> vec.get_feature_names_out()
array(['city=Dubai', 'city=London', 'city=San Francisco', 'temperature'], ...)
DictVectorizer akzeptiert mehrere Zeichenkettenwerte für ein Feature, wie z. B. mehrere Kategorien für einen Film.
Angenommen, eine Datenbank klassifiziert jeden Film anhand einiger Kategorien (nicht zwingend erforderlich) und seines Erscheinungsjahres.
>>> movie_entry = [{'category': ['thriller', 'drama'], 'year': 2003},
... {'category': ['animation', 'family'], 'year': 2011},
... {'year': 1974}]
>>> vec.fit_transform(movie_entry).toarray()
array([[0.000e+00, 1.000e+00, 0.000e+00, 1.000e+00, 2.003e+03],
[1.000e+00, 0.000e+00, 1.000e+00, 0.000e+00, 2.011e+03],
[0.000e+00, 0.000e+00, 0.000e+00, 0.000e+00, 1.974e+03]])
>>> vec.get_feature_names_out()
array(['category=animation', 'category=drama', 'category=family',
'category=thriller', 'year'], ...)
>>> vec.transform({'category': ['thriller'],
... 'unseen_feature': '3'}).toarray()
array([[0., 0., 0., 1., 0.]])
DictVectorizer ist auch eine nützliche Transformation zur Repräsentation für das Training von Sequenzklassifikatoren in Modellen der natürlichen Sprachverarbeitung, die typischerweise durch das Extrahieren von Feature-Fenstern um ein bestimmtes Wort von Interesse arbeiten.
Angenommen, wir haben beispielsweise einen ersten Algorithmus, der Part-of-Speech (PoS)-Tags extrahiert, die wir als ergänzende Tags für das Training eines Sequenzklassifikators (z. B. eines Chunkers) verwenden möchten. Das folgende Dictionary könnte ein solches Feature-Fenster sein, das um das Wort „sat“ im Satz „The cat sat on the mat.“ extrahiert wurde.
>>> pos_window = [
... {
... 'word-2': 'the',
... 'pos-2': 'DT',
... 'word-1': 'cat',
... 'pos-1': 'NN',
... 'word+1': 'on',
... 'pos+1': 'PP',
... },
... # in a real application one would extract many such dictionaries
... ]
Diese Beschreibung kann in eine dünn besetzte zweidimensionale Matrix vektorisiert werden, die für die Eingabe in einen Klassifikator geeignet ist (möglicherweise nach dem Durchleiten durch einen TfidfTransformer zur Normalisierung)
>>> vec = DictVectorizer()
>>> pos_vectorized = vec.fit_transform(pos_window)
>>> pos_vectorized
<Compressed Sparse...dtype 'float64'
with 6 stored elements and shape (1, 6)>
>>> pos_vectorized.toarray()
array([[1., 1., 1., 1., 1., 1.]])
>>> vec.get_feature_names_out()
array(['pos+1=PP', 'pos-1=NN', 'pos-2=DT', 'word+1=on', 'word-1=cat',
'word-2=the'], ...)
Wie Sie sich vorstellen können, wird die resultierende Matrix sehr breit (viele One-Hot-Features), wenn man einen solchen Kontext um jedes einzelne Wort eines Korpus von Dokumenten extrahiert, wobei die meisten davon die meiste Zeit den Wert Null haben. Um die resultierende Datenstruktur speicherbar zu machen, verwendet die Klasse DictVectorizer standardmäßig eine scipy.sparse-Matrix anstelle eines numpy.ndarray.
7.2.2. Feature Hashing#
Die Klasse FeatureHasher ist ein schneller Vektorisierer mit geringem Speicherverbrauch, der eine Technik namens Feature Hashing oder der „Hashing Trick“ verwendet. Anstatt eine Hash-Tabelle der im Training angetroffenen Features zu erstellen, wie es die Vektorisierer tun, wenden Instanzen von FeatureHasher eine Hash-Funktion auf die Features an, um deren Spaltenindex direkt in Stichprobenmatrizen zu bestimmen. Dies führt zu erhöhter Geschwindigkeit und geringerem Speicherverbrauch, auf Kosten der Inspektierbarkeit; der Hasher merkt sich nicht, wie die Eingabe-Features aussahen und verfügt über keine inverse_transform-Methode.
Da die Hash-Funktion zu Kollisionen zwischen (unverbundenen) Features führen kann, wird eine vorzeichenbehaftete Hash-Funktion verwendet und das Vorzeichen des Hash-Werts bestimmt das Vorzeichen des Werts, der in der Ausgabematrix für ein Feature gespeichert wird. Auf diese Weise heben sich Kollisionen eher auf, anstatt Fehler anzuhäufen, und der erwartete Mittelwert des Werts eines beliebigen Ausgabe-Features ist Null. Dieser Mechanismus ist standardmäßig mit alternate_sign=True aktiviert und ist besonders nützlich für kleine Hash-Tabellengrößen (n_features < 10000). Für große Hash-Tabellengrößen kann er deaktiviert werden, um die Ausgabe an Estimators wie MultinomialNB oder chi2 Feature-Selektoren weiterzugeben, die nicht-negative Eingaben erwarten.
FeatureHasher akzeptiert entweder Abbildungen (wie die dict von Python und deren Varianten im Modul collections), (feature, value)-Paare oder Zeichenketten, abhängig vom Konstruktorparameter input_type. Abbildungen werden als Listen von (feature, value)-Paaren behandelt, während einzelne Zeichenketten einen impliziten Wert von 1 haben, sodass ['feat1', 'feat2', 'feat3'] als [('feat1', 1), ('feat2', 1), ('feat3', 1)] interpretiert wird. Wenn ein einzelnes Feature mehrmals in einer Stichprobe vorkommt, werden die zugehörigen Werte summiert (sodass ('feat', 2) und ('feat', 3.5) zu ('feat', 5.5) werden). Die Ausgabe von FeatureHasher ist immer eine scipy.sparse-Matrix im CSR-Format.
Feature Hashing kann bei der Dokumentenklassifizierung eingesetzt werden, aber im Gegensatz zu CountVectorizer führt FeatureHasher außer der Unicode-zu-UTF-8-Kodierung keine Worttrennung oder andere Vorverarbeitung durch; siehe Vektorisierung eines großen Textkorpus mit dem Hashing Trick unten für einen kombinierten Tokenizer/Hasher.
Betrachten wir als Beispiel eine auf Wörterbene durchgeführte Aufgabe der natürlichen Sprachverarbeitung, die Features aus (token, part_of_speech)-Paaren extrahieren muss. Man könnte eine Python-Generatorfunktion zur Extraktion von Features verwenden.
def token_features(token, part_of_speech):
if token.isdigit():
yield "numeric"
else:
yield "token={}".format(token.lower())
yield "token,pos={},{}".format(token, part_of_speech)
if token[0].isupper():
yield "uppercase_initial"
if token.isupper():
yield "all_uppercase"
yield "pos={}".format(part_of_speech)
Dann kann das raw_X, das an FeatureHasher.transform übergeben werden soll, mithilfe von erstellt werden
raw_X = (token_features(tok, pos_tagger(tok)) for tok in corpus)
und an einen Hasher übergeben mit
hasher = FeatureHasher(input_type='string')
X = hasher.transform(raw_X)
um eine scipy.sparse-Matrix X zu erhalten.
Beachten Sie die Verwendung einer Generator-Comprehension, die eine Latenz in die Feature-Extraktion einführt: Tokens werden nur bei Bedarf vom Hasher verarbeitet.
Implementierungsdetails#
FeatureHasher verwendet die vorzeichenbehaftete 32-Bit-Variante von MurmurHash3. Infolgedessen (und aufgrund von Einschränkungen in scipy.sparse) beträgt die maximal unterstützte Anzahl von Features derzeit \(2^{31} - 1\).
Die ursprüngliche Formulierung des Hashing-Tricks von Weinberger et al. verwendete zwei separate Hash-Funktionen \(h\) und \(\xi\), um den Spaltenindex und das Vorzeichen eines Features zu bestimmen. Die vorliegende Implementierung geht davon aus, dass das Vorzeichen-Bit von MurmurHash3 von seinen anderen Bits unabhängig ist.
Da zur Umwandlung der Hash-Funktion in einen Spaltenindex ein einfacher Modulo verwendet wird, ist es ratsam, eine Potenz von zwei als Parameter n_features zu verwenden; andernfalls werden die Features nicht gleichmäßig auf die Spalten abgebildet.
Referenzen
Referenzen
Kilian Weinberger, Anirban Dasgupta, John Langford, Alex Smola und Josh Attenberg (2009). Feature hashing for large scale multitask learning. Proc. ICML.
7.2.3. Text Feature Extraction#
7.2.3.1. Die Bag-of-Words-Repräsentation#
Textanalyse ist ein wichtiges Anwendungsgebiet für Algorithmen des maschinellen Lernens. Die Rohdaten, eine Zeichenkette, können jedoch nicht direkt an die Algorithmen selbst übergeben werden, da die meisten von ihnen numerische Feature-Vektoren mit fester Größe und nicht rohe Textdokumente variabler Länge erwarten.
Um dies zu erreichen, stellt scikit-learn Dienstprogramme für die gängigsten Methoden zur Extraktion numerischer Features aus Textinhalten bereit, nämlich
Tokenisieren von Zeichenketten und Zuweisen einer Ganzzahl-ID für jedes mögliche Token, z. B. durch Verwendung von Leerzeichen und Satzzeichen als Token-Separatoren.
Zählen der Vorkommen von Tokens in jedem Dokument.
Normalisieren und Abwerten von Tokens, die in der Mehrheit der Stichproben/Dokumente vorkommen, mit abnehmender Wichtigkeit.
In diesem Schema werden Features und Stichproben wie folgt definiert:
Jede **individuelle Token-Vorkommenshäufigkeit** (normalisiert oder nicht) wird als **Feature** behandelt.
Der Vektor aller Token-Häufigkeiten für ein bestimmtes **Dokument** wird als multivariate **Stichprobe** betrachtet.
Ein Korpus von Dokumenten kann somit durch eine Matrix mit einer Zeile pro Dokument und einer Spalte pro Token (z. B. Wort) dargestellt werden, das im Korpus vorkommt.
Wir bezeichnen den allgemeinen Prozess der Umwandlung einer Sammlung von Textdokumenten in numerische Feature-Vektoren als **Vektorisierung**. Diese spezifische Strategie (Tokenisierung, Zählung und Normalisierung) wird als **Bag of Words** oder „Bag of n-grams“-Repräsentation bezeichnet. Dokumente werden durch Wortvorkommen beschrieben, wobei die relative Positionsinformation der Wörter im Dokument vollständig ignoriert wird.
7.2.3.2. Sparsity (Dünnbesetztheit)#
Da die meisten Dokumente typischerweise nur einen sehr kleinen Teil der im Korpus verwendeten Wörter verwenden, weist die resultierende Matrix viele Nullwerte auf (typischerweise mehr als 99 %).
Zum Beispiel wird eine Sammlung von 10.000 kurzen Textdokumenten (wie E-Mails) ein Vokabular mit einer Größe von etwa 100.000 eindeutigen Wörtern insgesamt verwenden, während jedes Dokument individuell 100 bis 1000 eindeutige Wörter verwenden wird.
Um eine solche Matrix im Speicher speichern zu können, aber auch um algebraische Operationen mit Matrizen/Vektoren zu beschleunigen, werden typischerweise dünn besetzte Darstellungen verwendet, wie die im Paket scipy.sparse verfügbaren Implementierungen.
7.2.3.3. Gängige Vektorisierer-Nutzung#
CountVectorizer implementiert sowohl Tokenisierung als auch Zählungszählung in einer einzigen Klasse.
>>> from sklearn.feature_extraction.text import CountVectorizer
Dieses Modell hat viele Parameter, die Standardwerte sind jedoch recht vernünftig (Details finden Sie in der Referenzdokumentation).
>>> vectorizer = CountVectorizer()
>>> vectorizer
CountVectorizer()
Verwenden wir es, um die Wortvorkommen eines minimalistischen Korpus von Textdokumenten zu tokenisieren und zu zählen.
>>> corpus = [
... 'This is the first document.',
... 'This is the second second document.',
... 'And the third one.',
... 'Is this the first document?',
... ]
>>> X = vectorizer.fit_transform(corpus)
>>> X
<Compressed Sparse...dtype 'int64'
with 19 stored elements and shape (4, 9)>
Die Standardkonfiguration tokenisiert die Zeichenkette, indem sie Wörter mit mindestens 2 Buchstaben extrahiert. Die spezifische Funktion, die diesen Schritt ausführt, kann explizit angefordert werden.
>>> analyze = vectorizer.build_analyzer()
>>> analyze("This is a text document to analyze.") == (
... ['this', 'is', 'text', 'document', 'to', 'analyze'])
True
Jeder vom Analysator während des Fits gefundene Begriff erhält einen eindeutigen ganzzahligen Index, der einer Spalte in der resultierenden Matrix entspricht. Diese Interpretation der Spalten kann wie folgt abgerufen werden.
>>> vectorizer.get_feature_names_out()
array(['and', 'document', 'first', 'is', 'one', 'second', 'the',
'third', 'this'], ...)
>>> X.toarray()
array([[0, 1, 1, 1, 0, 0, 1, 0, 1],
[0, 1, 0, 1, 0, 2, 1, 0, 1],
[1, 0, 0, 0, 1, 0, 1, 1, 0],
[0, 1, 1, 1, 0, 0, 1, 0, 1]]...)
Die umgekehrte Zuordnung von Feature-Name zu Spaltenindex ist im Attribut vocabulary_ des Vektorisierers gespeichert.
>>> vectorizer.vocabulary_.get('document')
1
Daher werden Wörter, die im Trainingskorpus nicht gesehen wurden, in zukünftigen Aufrufen der `transform`-Methode vollständig ignoriert.
>>> vectorizer.transform(['Something completely new.']).toarray()
array([[0, 0, 0, 0, 0, 0, 0, 0, 0]]...)
Beachten Sie, dass im vorherigen Korpus das erste und das letzte Dokument genau die gleichen Wörter haben, daher werden sie in gleiche Vektoren kodiert. Insbesondere verlieren wir die Information, dass das letzte Dokument eine Frageform ist. Um einige lokale Ordnungsinformationen zu bewahren, können wir zusätzlich zu den 1-Grammen (einzelne Wörter) 2-Gramme von Wörtern extrahieren.
>>> bigram_vectorizer = CountVectorizer(ngram_range=(1, 2),
... token_pattern=r'\b\w+\b', min_df=1)
>>> analyze = bigram_vectorizer.build_analyzer()
>>> analyze('Bi-grams are cool!') == (
... ['bi', 'grams', 'are', 'cool', 'bi grams', 'grams are', 'are cool'])
True
Das von diesem Vektorisierer extrahierte Vokabular ist daher viel größer und kann nun Mehrdeutigkeiten auflösen, die in lokalen Positionsmustern kodiert sind.
>>> X_2 = bigram_vectorizer.fit_transform(corpus).toarray()
>>> X_2
array([[0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0],
[0, 0, 1, 0, 0, 1, 1, 0, 0, 2, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0],
[1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1]]...)
Insbesondere ist die Frageform „Is this“ nur im letzten Dokument vorhanden.
>>> feature_index = bigram_vectorizer.vocabulary_.get('is this')
>>> X_2[:, feature_index]
array([0, 0, 0, 1]...)
7.2.3.4. Verwendung von Stoppwörtern#
Stoppwörter sind Wörter wie „und“, „der“, „ihm“, von denen angenommen wird, dass sie den Inhalt eines Textes nicht aussagekräftig darstellen und die entfernt werden können, um zu vermeiden, dass sie als informativ für die Vorhersage angesehen werden. Manchmal sind jedoch ähnliche Wörter für die Vorhersage nützlich, z. B. bei der Klassifizierung von Schreibstil oder Persönlichkeit.
Es gibt mehrere bekannte Probleme mit unserer bereitgestellten „englischen“ Stoppwortliste. Sie soll keine allgemeine „One-Size-Fits-All“-Lösung sein, da einige Aufgaben eine benutzerdefiniertere Lösung erfordern können. Siehe [NQY18] für weitere Details.
Bitte seien Sie vorsichtig bei der Auswahl einer Stoppwortliste. Beliebte Stoppwortlisten können Wörter enthalten, die für einige Aufgaben sehr aussagekräftig sind, wie z. B. computer.
Sie sollten auch sicherstellen, dass auf die Stoppwortliste die gleiche Vorverarbeitung und Tokenisierung angewendet wurde wie auf die im Vektorisierer verwendete. Das Wort we’ve wird von den Standard-Tokenizern von CountVectorizer in we und ve aufgeteilt. Wenn also we’ve in stop_words enthalten ist, aber ve nicht, wird ve aus we’ve im transformierten Text beibehalten. Unsere Vektorisierer versuchen, einige Arten von Inkonsistenzen zu erkennen und davor zu warnen.
Referenzen
J. Nothman, H. Qin und R. Yurchak (2018). „Stop Word Lists in Free Open-source Software Packages“. In Proc. Workshop for NLP Open Source Software.
7.2.3.5. Tf-Idf Termgewichtung#
In einem großen Textkorpus werden einige Wörter sehr häufig vorkommen (z. B. „der“, „ein“, „ist“ im Deutschen), und tragen daher wenig aussagekräftige Informationen über den eigentlichen Inhalt des Dokuments. Wenn wir die direkten Zähldaten direkt an einen Klassifikator übergeben würden, würden diese sehr häufigen Begriffe die Häufigkeit von selteneren, aber interessanteren Begriffen überschatten.
Um die Zähl-Features in Gleitkommazahlen umzugewichten, die für die Verwendung durch einen Klassifikator geeignet sind, wird häufig die Tf-Idf-Transformation verwendet.
Tf bedeutet **Term-Häufigkeit**, während Tf-Idf Term-Häufigkeit mal **Inverse Document-Häufigkeit** bedeutet: \(\text{tf-idf(t,d)}=\text{tf(t,d)} \times \text{idf(t)}\).
Unter Verwendung der Standardeinstellungen von TfidfTransformer, TfidfTransformer(norm='l2', use_idf=True, smooth_idf=True, sublinear_tf=False) wird die Term-Häufigkeit, d. h. die Anzahl, wie oft ein Begriff in einem bestimmten Dokument vorkommt, mit der IDF-Komponente multipliziert, die wie folgt berechnet wird:
\(\text{idf}(t) = \log{\frac{1 + n}{1+\text{df}(t)}} + 1\),
wobei \(n\) die Gesamtzahl der Dokumente im Dokumentensatz ist und \(\text{df}(t)\) die Anzahl der Dokumente im Dokumentensatz ist, die den Begriff \(t\) enthalten. Die resultierenden tf-idf-Vektoren werden dann durch die Euklidische Norm normalisiert.
\(v_{norm} = \frac{v}{||v||_2} = \frac{v}{\sqrt{v{_1}^2 + v{_2}^2 + \dots + v{_n}^2}}\).
Dies war ursprünglich ein Termgewichtungsschema, das für die Informationswiederauffindung (als Ranking-Funktion für Suchergebnisse von Suchmaschinen) entwickelt wurde und sich auch gut in der Dokumentenklassifizierung und -clustering bewährt hat.
Die folgenden Abschnitte enthalten weitere Erklärungen und Beispiele, die veranschaulichen, wie die Tf-Idfs genau berechnet werden und wie die in den scikit-learn-Klassen TfidfTransformer und TfidfVectorizer berechneten Tf-Idfs geringfügig von der Standardlehrbuchnotation abweichen, die das IDF wie folgt definiert:
\(\text{idf}(t) = \log{\frac{n}{1+\text{df}(t)}}.\)
In TfidfTransformer und TfidfVectorizer mit smooth_idf=False wird die „1“ zum IDF addiert, anstatt zum Nenner des IDF.
\(\text{idf}(t) = \log{\frac{n}{\text{df}(t)}} + 1\)
Diese Normalisierung wird durch die Klasse TfidfTransformer implementiert.
>>> from sklearn.feature_extraction.text import TfidfTransformer
>>> transformer = TfidfTransformer(smooth_idf=False)
>>> transformer
TfidfTransformer(smooth_idf=False)
Auch hier finden Sie Details zu allen Parametern in der Referenzdokumentation.
Numerisches Beispiel einer Tf-Idf-Matrix#
Nehmen wir ein Beispiel mit den folgenden Zählungen. Der erste Begriff kommt zu 100 % vor und ist daher nicht sehr interessant. Die beiden anderen Features kommen nur in weniger als 50 % der Fälle vor und sind daher wahrscheinlich repräsentativer für den Inhalt der Dokumente.
>>> counts = [[3, 0, 1],
... [2, 0, 0],
... [3, 0, 0],
... [4, 0, 0],
... [3, 2, 0],
... [3, 0, 2]]
...
>>> tfidf = transformer.fit_transform(counts)
>>> tfidf
<Compressed Sparse...dtype 'float64'
with 9 stored elements and shape (6, 3)>
>>> tfidf.toarray()
array([[0.81940995, 0. , 0.57320793],
[1. , 0. , 0. ],
[1. , 0. , 0. ],
[1. , 0. , 0. ],
[0.47330339, 0.88089948, 0. ],
[0.58149261, 0. , 0.81355169]])
Jede Zeile wird normalisiert, um eine Einheits-Euklid-Norm zu haben.
\(v_{norm} = \frac{v}{||v||_2} = \frac{v}{\sqrt{v{_1}^2 + v{_2}^2 + \dots + v{_n}^2}}\)
Wir können zum Beispiel das Tf-Idf des ersten Begriffs im ersten Dokument im `counts`-Array wie folgt berechnen.
\(n = 6\)
\(\text{df}(t)_{\text{term1}} = 6\)
\(\text{idf}(t)_{\text{term1}} = \log \frac{n}{\text{df}(t)} + 1 = \log(1)+1 = 1\)
\(\text{tf-idf}_{\text{term1}} = \text{tf} \times \text{idf} = 3 \times 1 = 3\)
Wenn wir diese Berechnung für die verbleibenden 2 Begriffe im Dokument wiederholen, erhalten wir:
\(\text{tf-idf}_{\text{term2}} = 0 \times (\log(6/1)+1) = 0\)
\(\text{tf-idf}_{\text{term3}} = 1 \times (\log(6/2)+1) \approx 2.0986\)
und der Vektor der rohen Tf-Idfs
\(\text{tf-idf}_{\text{raw}} = [3, 0, 2.0986].\)
Nach Anwendung der Euklidischen (L2)-Norm erhalten wir die folgenden Tf-Idfs für Dokument 1.
\(\frac{[3, 0, 2.0986]}{\sqrt{\big(3^2 + 0^2 + 2.0986^2\big)}} = [ 0.819, 0, 0.573].\)
Darüber hinaus fügt der Standardparameter smooth_idf=True dem Zähler und Nenner „1“ hinzu, als ob ein zusätzliches Dokument vorhanden wäre, das jeden Begriff in der Sammlung genau einmal enthält, was Null-Divisionen verhindert.
\(\text{idf}(t) = \log{\frac{1 + n}{1+\text{df}(t)}} + 1\)
Mit dieser Modifikation ändert sich das Tf-Idf des dritten Begriffs in Dokument 1 zu 1.8473.
\(\text{tf-idf}_{\text{term3}} = 1 \times \log(7/3)+1 \approx 1.8473\)
Und das L2-normalisierte Tf-Idf ändert sich zu
\(\frac{[3, 0, 1.8473]}{\sqrt{\big(3^2 + 0^2 + 1.8473^2\big)}} = [0.8515, 0, 0.5243]\):
>>> transformer = TfidfTransformer()
>>> transformer.fit_transform(counts).toarray()
array([[0.85151335, 0. , 0.52433293],
[1. , 0. , 0. ],
[1. , 0. , 0. ],
[1. , 0. , 0. ],
[0.55422893, 0.83236428, 0. ],
[0.63035731, 0. , 0.77630514]])
Die vom Methodenaufruf `fit` berechneten Gewichte jedes Features werden in einem Modellattribut gespeichert.
>>> transformer.idf_
array([1., 2.25, 1.84])
Da Tf-Idf sehr oft für Text-Features verwendet wird, gibt es auch eine weitere Klasse namens TfidfVectorizer, die alle Optionen von CountVectorizer und TfidfTransformer in einem einzigen Modell kombiniert.
>>> from sklearn.feature_extraction.text import TfidfVectorizer
>>> vectorizer = TfidfVectorizer()
>>> vectorizer.fit_transform(corpus)
<Compressed Sparse...dtype 'float64'
with 19 stored elements and shape (4, 9)>
Während die Tf-Idf-Normalisierung oft sehr nützlich ist, kann es Fälle geben, in denen die binären Vorkommensmarkierungen bessere Features liefern. Dies kann durch Verwendung des Parameters binary von CountVectorizer erreicht werden. Insbesondere modellieren einige Estimators wie Bernoulli Naive Bayes explizit diskrete boolesche Zufallsvariablen. Außerdem haben sehr kurze Texte wahrscheinlich verrauschte Tf-Idf-Werte, während die binären Vorkommensinformationen stabiler sind.
Wie üblich ist der beste Weg zur Anpassung der Feature-Extraktionsparameter die Verwendung einer Kreuz-validierten Grid-Suche, zum Beispiel durch Verknüpfung des Feature-Extraktors mit einem Klassifikator.
Beispiele
Klassifizierung von Textdokumenten unter Verwendung dünn besetzter Features: Feature-Kodierung mit einer Tf-Idf-gewichteten Dokumenten-Term-Matrix.
Vergleich von FeatureHasher und DictVectorizer: Effizienzvergleich der verschiedenen Feature-Extraktoren.
Clustering von Textdokumenten mit k-Means: Dokumenten-Clustering und Vergleich mit
HashingVectorizer.Beispiel-Pipeline für Text-Feature-Extraktion und -Bewertung: Tuning von Hyperparametern von
TfidfVectorizerals Teil einer Pipeline.
7.2.3.6. Dekodieren von Textdateien#
Text besteht aus Zeichen, aber Dateien bestehen aus Bytes. Diese Bytes stellen Zeichen gemäß einer bestimmten Kodierung dar. Um in Python mit Textdateien zu arbeiten, müssen ihre Bytes in einen Zeichensatz namens Unicode dekodiert werden. Gängige Kodierungen sind ASCII, Latin-1 (Westeuropa), KOI8-R (Russisch) und die universellen Kodierungen UTF-8 und UTF-16. Es gibt viele weitere.
Hinweis
Eine Kodierung kann auch als „Zeichensatz“ bezeichnet werden, aber dieser Begriff ist weniger genau: Für einen einzelnen Zeichensatz kann es mehrere Kodierungen geben.
Die Text-Feature-Extraktoren in scikit-learn wissen, wie sie Textdateien dekodieren können, aber nur, wenn Sie ihnen mitteilen, in welcher Kodierung die Dateien vorliegen. Die Klasse CountVectorizer verwendet zu diesem Zweck einen Parameter encoding. Für moderne Textdateien ist die richtige Kodierung wahrscheinlich UTF-8, weshalb dies der Standard ist (encoding="utf-8").
Wenn der Text, den Sie laden, nicht tatsächlich mit UTF-8 kodiert ist, erhalten Sie jedoch eine UnicodeDecodeError. Die Vektorisierer können so konfiguriert werden, dass sie bei Dekodierungsfehlern stumm bleiben, indem der Parameter decode_error auf "ignore" oder "replace" gesetzt wird. Weitere Details finden Sie in der Dokumentation der Python-Funktion bytes.decode (geben Sie help(bytes.decode) an der Python-Eingabeaufforderung ein).
Fehlerbehebung bei der Dekodierung von Text#
Wenn Sie Probleme mit der Dekodierung von Text haben, versuchen Sie Folgendes:
Finden Sie die tatsächliche Kodierung des Textes heraus. Die Datei enthält möglicherweise eine Kopfzeile oder eine README-Datei, in der die Kodierung angegeben ist, oder es gibt eine Standardkodierung, die Sie basierend auf der Herkunft des Textes annehmen können.
Möglicherweise können Sie mit dem UNIX-Befehl
fileherausfinden, um welche Art von Kodierung es sich handelt. Das Python-Modulchardetenthält ein Skript namenschardetect.py, das die spezifische Kodierung errät, Sie können sich jedoch nicht darauf verlassen, dass die Vermutung korrekt ist.Sie könnten UTF-8 versuchen und die Fehler ignorieren. Sie können Byte-Strings mit
bytes.decode(errors='replace')dekodieren, um alle Dekodierungsfehler durch ein bedeutungsloses Zeichen zu ersetzen, oderdecode_error='replace'im Vektorisierer setzen. Dies kann die Nützlichkeit Ihrer Merkmale beeinträchtigen.Echte Texte können aus einer Vielzahl von Quellen stammen, die möglicherweise unterschiedliche Kodierungen verwendet haben oder sogar schlampig in einer anderen Kodierung als der ursprünglichen kodiert wurden. Dies ist häufig bei Texten der Fall, die aus dem Web abgerufen werden. Das Python-Paket ftfy kann einige Arten von Dekodierungsfehlern automatisch beheben. Sie könnten also versuchen, den unbekannten Text als
latin-1zu dekodieren und dannftfyzur Korrektur von Fehlern zu verwenden.Wenn der Text in einer Mischung von Kodierungen vorliegt, die einfach zu schwer zu sortieren ist (was bei der 20 Newsgroups-Datenmenge der Fall ist), können Sie auf eine einfache Single-Byte-Kodierung wie
latin-1zurückgreifen. Einige Texte werden möglicherweise falsch angezeigt, aber zumindest wird die gleiche Byte-Sequenz immer dasselbe Merkmal darstellen.
Das folgende Snippet verwendet beispielsweise chardet (nicht mit scikit-learn geliefert, muss separat installiert werden), um die Kodierung von drei Texten zu ermitteln. Anschließend werden die Texte vektorisiert und das gelernte Vokabular ausgegeben. Die Ausgabe wird hier nicht gezeigt.
>>> import chardet
>>> text1 = b"Sei mir gegr\xc3\xbc\xc3\x9ft mein Sauerkraut"
>>> text2 = b"holdselig sind deine Ger\xfcche"
>>> text3 = b"\xff\xfeA\x00u\x00f\x00 \x00F\x00l\x00\xfc\x00g\x00e\x00l\x00n\x00 \x00d\x00e\x00s\x00 \x00G\x00e\x00s\x00a\x00n\x00g\x00e\x00s\x00,\x00 \x00H\x00e\x00r\x00z\x00l\x00i\x00e\x00b\x00c\x00h\x00e\x00n\x00,\x00 \x00t\x00r\x00a\x00g\x00 \x00i\x00c\x00h\x00 \x00d\x00i\x00c\x00h\x00 \x00f\x00o\x00r\x00t\x00"
>>> decoded = [x.decode(chardet.detect(x)['encoding'])
... for x in (text1, text2, text3)]
>>> v = CountVectorizer().fit(decoded).vocabulary_
>>> for term in v: print(v)
(Abhängig von der Version von chardet kann die erste Kodierung falsch erkannt werden.)
Eine Einführung in Unicode und Zeichenkodierungen im Allgemeinen finden Sie in Joel Spolskys Absolute Minimum Every Software Developer Must Know About Unicode.
7.2.3.7. Anwendungen und Beispiele#
Die Bag-of-Words-Darstellung ist ziemlich einfach, aber in der Praxis überraschend nützlich.
Insbesondere in einem überwachten Umfeld kann sie erfolgreich mit schnellen und skalierbaren linearen Modellen kombiniert werden, um Dokumentklassifikatoren zu trainieren, zum Beispiel
In einem unüberwachten Umfeld kann sie verwendet werden, um ähnliche Dokumente zu gruppieren, indem Clustering-Algorithmen wie K-Means angewendet werden.
Schließlich ist es möglich, die Hauptthemen eines Korpus zu entdecken, indem die harte Zuweisungsbeschränkung des Clusterings gelockert wird, zum Beispiel durch Verwendung von Nicht-negativer Matrixfaktorisierung (NMF oder NNMF).
7.2.3.8. Grenzen der Bag-of-Words-Darstellung#
Eine Sammlung von Unigrammen (was Bag of Words ist) kann keine Phrasen und mehrwortigen Ausdrücke erfassen und ignoriert effektiv jegliche Wortreihenfolgenabhängigkeit. Zusätzlich berücksichtigt das Bag-of-Words-Modell keine möglichen Rechtschreibfehler oder Wortableitungen.
N-Gramme zur Rettung! Anstatt eine einfache Sammlung von Unigrammen (n=1) zu erstellen, könnte man eine Sammlung von Bigrammen (n=2) bevorzugen, bei der Vorkommen von aufeinanderfolgenden Wortpaaren gezählt werden.
Man könnte alternativ eine Sammlung von Zeichen-N-Grammen in Betracht ziehen, eine Darstellung, die widerstandsfähig gegenüber Rechtschreibfehlern und Wortableitungen ist.
Nehmen wir zum Beispiel an, wir haben einen Korpus von zwei Dokumenten: ['words', 'wprds']. Das zweite Dokument enthält einen Rechtschreibfehler des Wortes „words“. Eine einfache Bag-of-Words-Darstellung würde diese beiden als sehr unterschiedliche Dokumente betrachten, die sich in beiden möglichen Merkmalen unterscheiden. Eine Zeichen-2-Gramm-Darstellung würde jedoch feststellen, dass die Dokumente in 4 von 8 Merkmalen übereinstimmen, was dem bevorzugten Klassifikator helfen kann, besser zu entscheiden.
>>> ngram_vectorizer = CountVectorizer(analyzer='char_wb', ngram_range=(2, 2))
>>> counts = ngram_vectorizer.fit_transform(['words', 'wprds'])
>>> ngram_vectorizer.get_feature_names_out()
array([' w', 'ds', 'or', 'pr', 'rd', 's ', 'wo', 'wp'], ...)
>>> counts.toarray().astype(int)
array([[1, 1, 1, 0, 1, 1, 1, 0],
[1, 1, 0, 1, 1, 1, 0, 1]])
Im obigen Beispiel wird der char_wb-Analysator verwendet, der N-Gramme nur aus Zeichen innerhalb von Wortgrenzen (mit Leerzeichen auf beiden Seiten) erstellt. Der char-Analysator erstellt alternativ N-Gramme, die sich über Wörter erstrecken.
>>> ngram_vectorizer = CountVectorizer(analyzer='char_wb', ngram_range=(5, 5))
>>> ngram_vectorizer.fit_transform(['jumpy fox'])
<Compressed Sparse...dtype 'int64'
with 4 stored elements and shape (1, 4)>
>>> ngram_vectorizer.get_feature_names_out()
array([' fox ', ' jump', 'jumpy', 'umpy '], ...)
>>> ngram_vectorizer = CountVectorizer(analyzer='char', ngram_range=(5, 5))
>>> ngram_vectorizer.fit_transform(['jumpy fox'])
<Compressed Sparse...dtype 'int64'
with 5 stored elements and shape (1, 5)>
>>> ngram_vectorizer.get_feature_names_out()
array(['jumpy', 'mpy f', 'py fo', 'umpy ', 'y fox'], ...)
Die wortgrenzenbewusste Variante char_wb ist besonders interessant für Sprachen, die Leerzeichen zur Worttrennung verwenden, da sie in diesem Fall deutlich weniger verrauschte Merkmale als die reine char-Variante erzeugt. Für solche Sprachen kann sie sowohl die prädiktive Genauigkeit als auch die Konvergenzgeschwindigkeit von Klassifikatoren, die mit solchen Merkmalen trainiert werden, erhöhen, während sie gleichzeitig die Robustheit gegenüber Rechtschreibfehlern und Wortableitungen beibehält.
Während durch die Extraktion von N-Grammen anstelle einzelner Wörter eine gewisse lokale Positionsinformation erhalten bleiben kann, zerstören Bag-of-Words und Bag-of-N-Gramme den größten Teil der inneren Struktur des Dokuments und damit den größten Teil der durch diese innere Struktur getragenen Bedeutung.
Um die umfassendere Aufgabe des Natural Language Understanding anzugehen, sollte die lokale Struktur von Sätzen und Absätzen berücksichtigt werden. Viele solcher Modelle werden daher als Probleme mit „strukturiertem Output“ formuliert, die derzeit außerhalb des Geltungsbereichs von scikit-learn liegen.
7.2.3.9. Vektorisieren eines großen Textkorpus mit dem Hashing-Trick#
Das oben genannte Vektorisierungsschema ist einfach, aber die Tatsache, dass es eine In-Memory-Zuordnung von String-Tokens zu ganzzahligen Merkmalsindizes (das Attribut vocabulary_) speichert, verursacht mehrere Probleme beim Umgang mit großen Datensätzen.
Je größer der Korpus, desto größer wird das Vokabular und damit auch der Speicherverbrauch.
Die Anpassung erfordert die Allokation von Zwischen-Datenstrukturen, deren Größe proportional zur Größe des ursprünglichen Datensatzes ist.
Der Aufbau der Wortzuordnung erfordert einen vollständigen Durchlauf des Datensatzes, daher ist es nicht möglich, Textklassifikatoren streng online anzupassen.
Das Pickling und Unpickling von Vektorisierern mit einem großen
vocabulary_kann sehr langsam sein (typischerweise viel langsamer als das Pickling / Unpickling von flachen Datenstrukturen wie einem NumPy-Array gleicher Größe).Es ist nicht einfach möglich, die Vektorisierungsarbeit in gleichzeitige Unteraufgaben aufzuteilen, da das Attribut
vocabulary_ein gemeinsamer Zustand mit einer feingranularen Synchronisationsbarriere sein müsste: Die Zuordnung von Token-String zu Merkmal-Index hängt von der Reihenfolge des ersten Auftretens jedes Tokens ab und müsste daher geteilt werden, was potenziell die Leistung der gleichzeitigen Worker bis zu dem Punkt beeinträchtigt, an dem sie langsamer als die sequentielle Variante werden.
Diese Einschränkungen können durch die Kombination des „Hashing-Tricks“ (Feature Hashing), implementiert durch die Klasse FeatureHasher, und der Textvorverarbeitungs- und Tokenisierungsfunktionen von CountVectorizer überwunden werden.
Diese Kombination ist in HashingVectorizer implementiert, einer Transformer-Klasse, die weitgehend API-kompatibel mit CountVectorizer ist. HashingVectorizer ist zustandslos, was bedeutet, dass Sie nicht fit darauf aufrufen müssen.
>>> from sklearn.feature_extraction.text import HashingVectorizer
>>> hv = HashingVectorizer(n_features=10)
>>> hv.transform(corpus)
<Compressed Sparse...dtype 'float64'
with 16 stored elements and shape (4, 10)>
Sie sehen, dass im Vektor-Output 16 Nicht-Null-Merkmal-Tokens extrahiert wurden: das sind weniger als die 19 Nicht-Null-Werte, die zuvor von CountVectorizer auf demselben Beispielkorpus extrahiert wurden. Die Diskrepanz ergibt sich aus Kollisionen der Hash-Funktion aufgrund des niedrigen Werts des Parameters n_features.
In einer realen Situation kann der Parameter n_features auf seinem Standardwert von 2 ** 20 (ungefähr eine Million mögliche Merkmale) belassen werden. Wenn der Speicher oder die Größe nachgelagerter Modelle ein Problem darstellt, kann die Auswahl eines niedrigeren Werts wie 2 ** 18 hilfreich sein, ohne zu viele zusätzliche Kollisionen bei typischen Textklassifizierungsaufgaben einzuführen.
Beachten Sie, dass die Dimensionalität die CPU-Trainingszeit von Algorithmen, die auf CSR-Matrizen arbeiten (LinearSVC(dual=True), Perceptron, SGDClassifier), nicht beeinflusst, wohl aber für Algorithmen, die mit CSC-Matrizen arbeiten (LinearSVC(dual=False), Lasso(), etc.).
Versuchen wir es erneut mit der Standardeinstellung.
>>> hv = HashingVectorizer()
>>> hv.transform(corpus)
<Compressed Sparse...dtype 'float64'
with 19 stored elements and shape (4, 1048576)>
Wir erhalten keine Kollisionen mehr, aber das geht auf Kosten einer viel größeren Dimensionalität des Ausgabe Raums. Natürlich können auch andere Terme als die hier verwendeten 19 miteinander kollidieren.
Der HashingVectorizer hat auch folgende Einschränkungen:
Es ist nicht möglich, das Modell zu invertieren (keine Methode
inverse_transform), noch auf die ursprüngliche String-Darstellung der Merkmale zuzugreifen, aufgrund der Einwegnatur der Hash-Funktion, die die Zuordnung durchführt.Er bietet keine IDF-Gewichtung, da dies eine Zustandsabhängigkeit im Modell einführen würde. Ein
TfidfTransformerkann bei Bedarf an eine Pipeline angehängt werden.
Out-of-Core-Skalierung mit HashingVectorizer durchführen#
Eine interessante Entwicklung bei der Verwendung eines HashingVectorizer ist die Möglichkeit, Out-of-Core-Skalierung durchzuführen. Das bedeutet, dass wir aus Daten lernen können, die nicht in den Hauptspeicher des Computers passen.
Eine Strategie zur Implementierung von Out-of-Core-Skalierung ist das Streamen von Daten an den Schätzer in Mini-Batches. Jeder Mini-Batch wird mit HashingVectorizer vektorisiert, um sicherzustellen, dass der Eingangsraum des Schätzers immer die gleiche Dimensionalität hat. Die zu irgendeinem Zeitpunkt verwendete Speichermenge ist somit durch die Größe eines Mini-Batches begrenzt. Obwohl es keine Begrenzung für die Menge der Daten gibt, die mit einem solchen Ansatz aufgenommen werden können, wird die Lernzeit aus praktischer Sicht oft durch die CPU-Zeit begrenzt, die man für die Aufgabe aufwenden möchte.
Ein vollständiges Beispiel für Out-of-Core-Skalierung in einer Textklassifizierungsaufgabe finden Sie unter Out-of-core classification of text documents.
7.2.3.10. Anpassen der Vektorisierer-Klassen#
Das Verhalten kann durch Übergabe eines Aufrufbaren an den Vektorisierer-Konstruktor angepasst werden.
>>> def my_tokenizer(s):
... return s.split()
...
>>> vectorizer = CountVectorizer(tokenizer=my_tokenizer)
>>> vectorizer.build_analyzer()(u"Some... punctuation!") == (
... ['some...', 'punctuation!'])
True
Insbesondere nennen wir
preprocessor: Ein Aufrufbares, das ein gesamtes Dokument als Eingabe (als einzelne Zeichenkette) nimmt und eine möglicherweise transformierte Version des Dokuments zurückgibt, immer noch als einzelne Zeichenkette. Dies kann verwendet werden, um HTML-Tags zu entfernen, das gesamte Dokument in Kleinbuchstaben umzuwandeln usw.tokenizer: Ein Aufrufbares, das die Ausgabe des Preprocessors nimmt und sie in Tokens zerlegt, dann eine Liste davon zurückgibt.analyzer: Ein Aufrufbares, das den Preprocessor und den Tokenizer ersetzt. Die Standard-Analyzer rufen beide den Preprocessor und den Tokenizer auf, benutzerdefinierte Analyzer überspringen dies jedoch. N-Gramm-Extraktion und Stoppwortfilterung erfolgen auf der Ebene des Analyzers, daher muss ein benutzerdefinierter Analyzer möglicherweise diese Schritte reproduzieren.
(Lucene-Benutzer erkennen diese Namen vielleicht wieder, aber seien Sie sich bewusst, dass scikit-learn-Konzepte möglicherweise nicht eins zu eins mit Lucene-Konzepten übereinstimmen.)
Um den Preprocessor, Tokenizer und Analyzer mit den Modellparametern vertraut zu machen, können Sie von der Klasse ableiten und die Fabrikmethode build_preprocessor, build_tokenizer und build_analyzer überschreiben, anstatt benutzerdefinierte Funktionen zu übergeben.
Tipps und Tricks#
Wenn Dokumente von einem externen Paket vor-tokenisiert werden, speichern Sie diese in Dateien (oder Zeichenketten) mit durch Leerzeichen getrennten Tokens und übergeben Sie
analyzer=str.splitAnspruchsvolle Token-Level-Analysen wie Stemming, Lemmatisierung, Zusammensetzungszerlegung, Filterung nach Wortart usw. sind nicht im scikit-learn-Code enthalten, können aber durch Anpassung des Tokenizers oder des Analyzers hinzugefügt werden. Hier ist ein
CountVectorizermit einem Tokenizer und Lemmatizer unter Verwendung von NLTK.>>> from nltk import word_tokenize >>> from nltk.stem import WordNetLemmatizer >>> class LemmaTokenizer: ... def __init__(self): ... self.wnl = WordNetLemmatizer() ... def __call__(self, doc): ... return [self.wnl.lemmatize(t) for t in word_tokenize(doc)] ... >>> vect = CountVectorizer(tokenizer=LemmaTokenizer())
(Beachten Sie, dass dies keine Satzzeichen herausfiltert.)
Das folgende Beispiel wandelt beispielsweise britische Schreibweisen in amerikanische um.
>>> import re >>> def to_british(tokens): ... for t in tokens: ... t = re.sub(r"(...)our$", r"\1or", t) ... t = re.sub(r"([bt])re$", r"\1er", t) ... t = re.sub(r"([iy])s(e$|ing|ation)", r"\1z\2", t) ... t = re.sub(r"ogue$", "og", t) ... yield t ... >>> class CustomVectorizer(CountVectorizer): ... def build_tokenizer(self): ... tokenize = super().build_tokenizer() ... return lambda doc: list(to_british(tokenize(doc))) ... >>> print(CustomVectorizer().build_analyzer()(u"color colour")) [...'color', ...'color']
Für andere Arten der Vorverarbeitung; Beispiele sind Stemming, Lemmatisierung oder die Normalisierung von numerischen Tokens, wobei letzteres in
Die Anpassung des Vektorisierers kann auch beim Umgang mit asiatischen Sprachen nützlich sein, die keinen expliziten Worttrenner wie Leerzeichen verwenden.
7.2.4. Bildmerkmalsextraktion#
7.2.4.1. Patch-Extraktion#
Die Funktion extract_patches_2d extrahiert Patches aus einem Bild, das als zweidimensionales Array oder dreidimensional mit Farbinformationen entlang der dritten Achse gespeichert ist. Zum Wiederaufbau eines Bildes aus all seinen Patches verwenden Sie reconstruct_from_patches_2d. Generieren wir zum Beispiel ein 4x4-Pixel-Bild mit 3 Farbkanälen (z. B. im RGB-Format).
>>> import numpy as np
>>> from sklearn.feature_extraction import image
>>> one_image = np.arange(4 * 4 * 3).reshape((4, 4, 3))
>>> one_image[:, :, 0] # R channel of a fake RGB picture
array([[ 0, 3, 6, 9],
[12, 15, 18, 21],
[24, 27, 30, 33],
[36, 39, 42, 45]])
>>> patches = image.extract_patches_2d(one_image, (2, 2), max_patches=2,
... random_state=0)
>>> patches.shape
(2, 2, 2, 3)
>>> patches[:, :, :, 0]
array([[[ 0, 3],
[12, 15]],
[[15, 18],
[27, 30]]])
>>> patches = image.extract_patches_2d(one_image, (2, 2))
>>> patches.shape
(9, 2, 2, 3)
>>> patches[4, :, :, 0]
array([[15, 18],
[27, 30]])
Versuchen wir nun, das ursprüngliche Bild aus den Patches durch Mittelwertbildung über überlappende Bereiche zu rekonstruieren.
>>> reconstructed = image.reconstruct_from_patches_2d(patches, (4, 4, 3))
>>> np.testing.assert_array_equal(one_image, reconstructed)
Die Klasse PatchExtractor funktioniert genauso wie extract_patches_2d, sie unterstützt nur mehrere Bilder als Eingabe. Sie ist als scikit-learn-Transformer implementiert, kann also in Pipelines verwendet werden. Siehe
>>> five_images = np.arange(5 * 4 * 4 * 3).reshape(5, 4, 4, 3)
>>> patches = image.PatchExtractor(patch_size=(2, 2)).transform(five_images)
>>> patches.shape
(45, 2, 2, 3)
7.2.4.2. Konnektivitätsgraph eines Bildes#
Mehrere Schätzer in scikit-learn können Konnektivitätsinformationen zwischen Merkmalen oder Stichproben verwenden. Beispielsweise kann Ward-Clustering (Hierarchisches Clustering) nur benachbarte Pixel eines Bildes zusammen clustern und so zusammenhängende Patches bilden.
Zu diesem Zweck verwenden die Schätzer eine „Konnektivitätsmatrix“, die angibt, welche Stichproben verbunden sind.
Die Funktion img_to_graph gibt eine solche Matrix aus einem 2D- oder 3D-Bild zurück. Ebenso erstellt grid_to_graph eine Konnektivitätsmatrix für Bilder, gegeben die Form dieser Bilder.
Diese Matrizen können verwendet werden, um Konnektivität in Schätzern zu erzwingen, die Konnektivitätsinformationen verwenden, wie z. B. Ward-Clustering (Hierarchisches Clustering), aber auch zum Erstellen von vorab berechneten Kerneln oder Ähnlichkeitsmatrizen.