Txt Dateien als List aus Verzeichnis lesen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
C#17
User
Beiträge: 19
Registriert: Montag 22. Mai 2017, 12:19

Hallo liebes Forum,

ich bin Python-Neuling und stehe vor einem Problem....

Ich habe in einem Verzeichnis 5 txt dateien (welche im folgenden als Posts bezeichne) mit den Namen 01.txt bis 05.txt.

Diese Dateien haben den folgenden Inhalt:
01.txt : This is a toy post about machine learning. Actually, it contains not much interesting stuff.
02.txt: Imaging databases provide storage capabilities.
03.txt: Most imaging databases save images permanently.
04.txt: Imaging databases store data.
05.txt: Imaging databases store data. Imaging databases store data. Imaging databases store data.


Diese 5 Dateien möchte ich nun in Python laden und den Porter Stemmer des nltk drüber laufen lassen bevor ich sie dann weiter verarbeite.
Mit folgendem Code:

Code: Alles auswählen

import os
import numpy as np
import string
from nltk.tokenize import word_tokenize
from nltk.stem import PorterStemmer

token=[]
stem_post=[]

#Laden der Dateien

post= [open(os.path.join("C:\\Users\Tobias\\PycharmProjects\\MachineLearing\\toy\\",f)).read()for f in os.listdir("C:\\Users\Tobias\\PycharmProjects\\MachineLearing\\toy")]

#Entfernen der Punctuation

for i in range(0,len(post)):
    post[i] = "".join(j for j in post[i] if j not in string.punctuation)


#Tokenize the post

for l in range(len(post)):
    token.append(word_tokenize(post[l]))
print(token)

#Stem the code

porter=PorterStemmer()

for w in token:
    for k in w:
        stem_post.append(porter.stem(k))
print(stem_post)
Schaffe ich es die Dateien einzulesen, die punctuation zu entfernen und auch jeden der Posts in seine Worte zu zerlegen. Hierbei befinden sich die Worte eines jeden Posts aber immernoch in einer eigenen Liste wie: "print(token)" zeigt.
print(token)
>>> [['This', 'is', 'a', 'toy', 'post', 'about', 'machine', 'learning', 'Actually', 'it', 'contains', 'not', 'much', 'interesting', 'stuff'], ['Imaging', 'databases', 'provide', 'storage', 'capabilities'], ['Most', 'imaging', 'databases', 'save', 'images', 'permanently'], ['Imaging', 'databases', 'store', 'data'], ['Imaging', 'databases', 'store', 'data', 'Imaging', 'databases', 'store', 'data', 'Imaging', 'databases', 'store', 'data']]

Nun möchte ich über eben diese Elemente den PorterStemmer drüber laufen lassen. Tue ich das mit dem oben beschriebenen Code und lasse mir das Ergebnis ausgeben erhalte ich:
print(stem_post)
>>>['thi', 'is', 'a', 'toy', 'post', 'about', 'machin', 'learn', 'actual', 'it', 'contain', 'not', 'much', 'interest', 'stuff', 'imag', 'databas', 'provid', 'storag', 'capabl', 'most', 'imag', 'databas', 'save', 'imag', 'perman', 'imag', 'databas', 'store', 'data', 'imag', 'databas', 'store', 'data', 'imag', 'databas', 'store', 'data', 'imag', 'databas', 'store', 'data']

Soooo. Inhaltlich, d.h. von den Wortstämmen her ist alles i.O. ABER: Ich habe nun alle Worte in EINER Liste und nicht die Worte einer jeden Datei in einer eigenen Liste welche dann zusammen eine 2D Liste ergeben wie sie bei dem Befehl print(token) ausgegeben wurde.

Für mich ist es aber essentiell, dass ich die einzelnen Posts auseinander halten kann (jeden in einer eigenen Liste habe).


Wie kann ich das so einfach wie möglich bewerkstelligen? Bitte wenn möglich mit "Anleitung" da ich ein Anfänger bin aber gerne lernen möchte.

Für Eure Antworten bedanke ich mich im Voraus!
Zuletzt geändert von Anonymous am Montag 22. Mai 2017, 13:47, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@C#17: Wenn ich mir Deinen Quelltext so anschaue und auch die Frage, dann wäre die Anleitung von der Du am meisten profitierst, wohl ein Grundlagentutorial in Python. In der Python-Dokumentation befindet sich beispielsweise eines.

Auf der einen Seite schreibst Du nämlich Code der nicht ”pythonisch” ist, weil Grundlagenkenntnisse fehlen, zum anderen ist die Antwort auf die Frage letztlich einfach nur mal darüber nachzudenken wie eine Schleife funktioniert, und was man machen muss damit das Ergebnis so aussieht wie Du es gerne haben möchtest. Das ist eigentlich sehr einfach, Du musst nur darüber nachdenken was der jetzige Code macht, also nicht nur das Ergebnis sondern wie es durch den Code zustande kommt, und dann überlegen wie sich das vom gewünschten Ergebnis unterscheidet, und was demnach am aktuellen Code an welcher Stelle anders/zusätzlich gemacht werden muss.

Anmerkung zum Quelltext:

Führe keine Namen an Stellen ein wo sie noch gar nicht gebraucht werden. Das macht Programme unübersichtlicher, fehleranfälliger, erhöht die Wahrscheinlichkeit das man am Ende Namen/Werte hat, die nach Programmänderungen überhaupt nicht mehr verwendet werden, und es macht es schwieriger den Code nachträglich sinnvoll auf Funktionen aufzuteilen, wenn alles was zu einer Funktion gehört, über den Quelltext verteilt ist.

Listen/Sequenzen bekommen üblicherweise einen Namen in Mehrzahl von dem wie man ein einzelnes Element nennen würde. Beispielsweise `tokens` oder `posts` für eine Liste mit Token oder Posts. Das ist nicht nur genauer, man bekommt auch keine Probleme wenn man dann in Schleifen einzelnen Elementen einen Namen geben muss, was ja `token` oder `post` wäre.

Und Diese Namen braucht man auch, denn ``for i in range(len(sequence)):`` um dann `i` als Index in `sequence` zu verwenden ist kein idiomatisches Python, das ist sogar ein „anti pattern“. In Python kann man direkt über die Elemente von Listen und anderen Sequenzen iterieren, da braucht man keinen Umweg über einen Index. Ebenfalls ist es unüblich Listen zu verändern. Man erstellt eher eine neue Liste mit den gewünschten Elementen. Also statt:

Code: Alles auswählen

for i in range(0, len(post)):
    post[i] = "".join(j for j in post[i] if j not in string.punctuation)

# <=>

posts = [
  ''.join(c for c in post if c not in string.punctuation) for post in posts
]
Oder statt:

Code: Alles auswählen

for l in range(len(post)):
    token.append(word_tokenize(post[l]))

# <=>

tokens = [word_tokenize(post) for post in posts]

# <=>

tokens = list(map(word_tokenize, posts))
Auch sind einbuchstabige Namen selten gute Namen, insbesondere wenn sie nicht auf einen Ausdruck beschränkt sind oder typische Indexname aus der Mathematik sind (`i`, `j`, `k`). Im letzteren Fall sollte es sich dann aber auch tatsächlich um ganze Zahlen handeln, und nicht um Buchstaben oder Worte. Das ist verwirrend.

Der Code zum Einlesen der Dateien ist übrigens nicht gerade robust. Oft liegen in Verzeichnissen noch andere Dateien, zum Beispiel legen viele Editoren Sicherungskopien an wenn man Dateien verändert.
C#17
User
Beiträge: 19
Registriert: Montag 22. Mai 2017, 12:19

Hallo BlackJack, vielen Dank für deine Hilfe. Du hast sicherlich recht, dass meine Art und Weise noch recht holprig ist aber deshalb bin ich ja auch noch blutiger Anfänger und muss die Sprache noch lernen... aber ich bin dabei. Anhängend mein angepasster und nun funktionierender Code!
Bei dem Schritt in welchem die Worte auf Ihren Wortstamm zurückgeführt werden wusste ich nicht wie ich es mit reiner List-Abstraction hin bekomme und habe deshalb für die einzelnen sentences eine Scheife eingebaut.
Wenn es auch hier einen schnelleren "pythonischeren" Weg gibt wäre ich für eine Korrektur sehr Dankbar!!!

Code: Alles auswählen

#Load the posts

posts= [open(os.path.join("C:\\Users\Tobias\\PycharmProjects\\MachineLearing\\toy\\",f)).read()for f in os.listdir("C:\\Users\Tobias\\PycharmProjects\\MachineLearing\\toy")]

#remove punctuation

posts= ["".join(char for char in post if char not in string.punctuation) for post in posts]

#Tokenize the post

tokens=[word_tokenize(post) for post in posts]

#Stem the post

snowball= SnowballStemmer("english")
stems=[]
for sentences in tokens:
    stems.append([snowball.stem(token) for token in sentences])

print(stems) gibt dann:
>>>[['this', 'is', 'a', 'toy', 'post', 'about', 'machin', 'learn', 'actual', 'it', 'contain', 'not', 'much', 'interest', 'stuff'], ['imag', 'databas', 'provid', 'storag', 'capabl'], ['most', 'imag', 'databas', 'save', 'imag', 'perman'], ['imag', 'databas', 'store', 'data'], ['imag', 'databas', 'store', 'data', 'imag', 'databas', 'store', 'data', 'imag', 'databas', 'store', 'data']]

was genau das ist was ich brauche!!!
Zuletzt geändert von Anonymous am Dienstag 23. Mai 2017, 11:04, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@C#17: Eine Schleife die eine Liste aufbaut in eine „list comprehension“ (LC)umzuwandeln geht immer nach den gleichen Schema. Das der Ausdruck der ein einzelnes Element erzeugt in diesem Fall selbst eine LC ist, macht keinen Unterschied.

Was nicht gut bis falsch ist: Die Dateien werden zwar geöffnet, aber nicht wieder explizit geschlossen. Für das Laden würde man also keine LC oder Generatorausdruck verwenden, weil man dafür mehr als einen einfachen Ausdruck braucht. Ausserdem fehlt beim `open()` eine explizite Angabe der Kodierung. Selbst wenn die Dateien nur ASCII enthalten, sollte man das auch sagen.

Zudem würde ich wie gesagt die Dateien auf ein bestimmtes Muster beschränken.

Die Namen `tokens` und `stems` treffen es nicht so genau denn die Objekte enthalten ja nicht direkt die Token oder Stämme sondern Sequenzen von Token beiehungsweise Stämmen.

Zwischenstand:

Code: Alles auswählen

import string
from glob import glob

from somewhere import SnowballStemmer, word_tokenize

POST_GLOB_PATTERN = r'C:\Users\Tobias\PycharmProjects\MachineLearing\toy\*.txt'


def main():
    # 
    # Load the posts.
    # 
    posts = list()
    for filename in glob(POST_GLOB_PATTERN):
        # 
        # FIXME Use explicit encoding.
        # 
        with open(filename) as post_file:
            posts.append(post_file.read())
    # 
    # Remove punctuation.
    # 
    posts = (
        ''.join(char for char in post if char not in string.punctuation)
        for post in posts
    )

    tokenized_posts = map(word_tokenize, posts)
    
    snowball = SnowballStemmer('english')
    stemmed_posts = [
        list(map(snowball.stem, sentences)) for sentences in tokenized_posts
    ]
    print(stemmed_posts)


if __name__ == '__main__':
    main()
Die letzten beiden Kommentare waren überflüssig, weil sehr offensichtlich. Der erste Kommentar ist von diesem Standpunkt aus grenzwertig. Und der mit den Satzzeichen ist hauptsächlich nötig weil der Code das nicht so knapp rüber bringt. Den könnte man aber weglassen wenn man eine Funktion `remove_punctuation()` einführt. Bei so einer Funktion kann man dann später auch überlegen ob man die nicht eventuell anders implementieren kann oder sogar muss. Denn `string.punctuation` enthält nicht alles was in Unicode als Satzzeichen durchgeht. Und einige Zeichen einfach so durch nichts zu ersetzen kann auch falsch sein. Der '/' trennt ja beispielsweise manchmal Worte wie in 'true/false' und daraus würde dann *ein* ”Wort” 'truefalse'. Punkte in Domains, '@' in E-Mail-Adressen, …, da gibt es sicher noch mehr Beispiele.

Das Laden könnte man in eine Generatorfunktion heraus ziehen, dann braucht man nicht erst alle Posts/Dateien in den Speicher laden.
C#17
User
Beiträge: 19
Registriert: Montag 22. Mai 2017, 12:19

@ BlackJack,

vielen Dank für deine Hilfe! Ich werde versuchen deine Ratschläge in Zukunft zu beherzigen...
Mit dem von dir gelieferten Code bekomme ich jedenfalls genau das was ich mir vorstelle und kann nun darauf aufbauen...
BlackJack

@C#17: Noch was was ich vergessen hatte zu erwähnen: Falls Dir die Reihenfolge wichtig ist, in der die Dateien verarbeitet werden, dann musst Du selbst dafür sorgen, denn sowohl `listdir()` als auch die Funktionen im `glob`-Modul machen keine Garantien in welcher Reihenfolge die Dateinamen geliefert werden. Das hängt vom Betriebssystem und damit dann letztendlich vom Dateisystemtreiber ab.
Antworten