pandas Codeoptimierung

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
Benutzeravatar
Felix92
User
Beiträge: 133
Registriert: Mittwoch 7. November 2018, 17:57

Huhu,

hat jemand Ideen wie ich diesen Quellcode noch vereinfachen könnte ?
So ganz zufrieden bin ich damit noch nicht :roll:

Code: Alles auswählen

data = pd.read_csv("/home/felix/Desktop/Document_Scanner/german.csv")

# count various labels
count  = pd.Series(data['label'].str.replace(r'[\[\]\']','').str.split(',').map(Counter).sum())
num_classes = len(set([element.strip() for element in count.index]))

data['label'] = data['label'].str.replace(r'[\[\]\']','').str.replace(" ", '').str.split(',')
#data = data.explode('label')

#initialize MultiLabelBinarizer 
mlb = MultiLabelBinarizer() 

#transform the label column to a series of columns with binary values
binary_labels = pd.DataFrame(mlb.fit_transform(data['label']), columns=mlb.classes_) 
binary_labels = binary_labels.sort_index(axis=1)

#bring data frames together
data = data.merge(binary_labels, how='inner', left_index=True, right_index=True)

Hier noch das erstellen der csv-Datei/en

Code: Alles auswählen

import os
from tqdm import tqdm

import textract
import re
import pandas as pd

from langdetect import detect


FOLDER_PATH = '/home/felix/Desktop/MachineLearning'

german_labels = list()
german_texts = list()

english_labels = list()
english_texts = list()

error_files = list()

def unzip_labels(label):
    labels = re.split('(?=[A-Z])', label)
    label = [label.lower() for label in labels]
    return label

def create_csv():
    for root, dirs, files in os.walk(FOLDER_PATH):
        for file in tqdm(files):
            try:
                file_path = os.path.abspath(os.path.join(root, file)) # filepath
                label = os.path.basename(root) # label 
                label = unzip_labels(label)
                text = str(textract.process(file_path, method='tesseract', encoding='ascii')).replace('\n', ' ') # text   # other: pdfminer , pdftotext
                if detect(text) == 'de':
                    german_labels.append(label)
                    german_texts.append(text)
                elif detect(text) == 'en':
                    english_labels.append(label)
                    english_texts.append(text)
                else:
                    print(f'can not detect de or en in {file}')
                    error_files.append(file)
            except:
                print(f'{file} occurs an error')
                error_files.append(file)

    # german
    df = pd.DataFrame(columns=['label', 'text'])
    df['label'] = german_labels
    df['text'] = german_texts

    df.to_csv('german.csv', index=False, encoding='ascii')

    # english
    df = pd.DataFrame(columns=['label', 'text'])
    df['label'] = english_labels
    df['text'] = english_texts
    df.explode('label')

    df.to_csv('english.csv', index=False, encoding='ascii')

    print(f'unexcepted files : {error_files}')

if __name__ == "__main__":
    create_csv()
Hier die erstellte csv:

Code: Alles auswählen

0      ['china', 'politik']  b'82\n\n    \n\n|\n\nAndreas Dittrich ist\nwis...
1      ['china', 'politik']  b"Das politische System\nder Volksrepublik Chi...
2      ['china', 'politik']  b' \n\n \n\nKonrad-Adenauer-Stiftung Washingto...
3      ['china', 'politik']  b"China Analysis 63\nJuli 2008\nwww.chinapolit...
4      ['china', 'politik']  b'SP Fraktion im\nBundestag\n\nBerlin, 30.06.2...
..                      ...                                                ...
200  ['italien', 'politik']  b' \n\nAlessandro Cavalli\n\n \n\nDie italieni...
201  ['italien', 'politik']  b'3 Das italienische Parteiensystem in histori...
202  ['italien', 'politik']  b'Foto: AP\n\n \n\nWahlkampfer\nBerlusconi:\nw...
203  ['italien', 'politik']  b" \n\nMatteo Renz\n\nvon Heinz Bierbaum\n\nMi...
204  ['italien', 'politik']  b'Deutsch-italienische Renaissance?\n\nMichael...
und nach dem modifizieren:

Code: Alles auswählen

                  label                                               text  boxen  china  corona  italien  politik  python  rezept  sport  typisierung  usa
0      [china, politik]  b'82\n\n    \n\n|\n\nAndreas Dittrich ist\nwis...      0      1       0        0        1       0       0      0            0    0
1      [china, politik]  b"Das politische System\nder Volksrepublik Chi...      0      1       0        0        1       0       0      0            0    0
2      [china, politik]  b' \n\n \n\nKonrad-Adenauer-Stiftung Washingto...      0      1       0        0        1       0       0      0            0    0
3      [china, politik]  b"China Analysis 63\nJuli 2008\nwww.chinapolit...      0      1       0        0        1       0       0      0            0    0
4      [china, politik]  b'SP Fraktion im\nBundestag\n\nBerlin, 30.06.2...      0      1       0        0        1       0       0      0            0    0
..                  ...                                                ...    ...    ...     ...      ...      ...     ...     ...    ...          ...  ...
200  [italien, politik]  b' \n\nAlessandro Cavalli\n\n \n\nDie italieni...      0      0       0        1        1       0       0      0            0    0
201  [italien, politik]  b'3 Das italienische Parteiensystem in histori...      0      0       0        1        1       0       0      0            0    0
202  [italien, politik]  b'Foto: AP\n\n \n\nWahlkampfer\nBerlusconi:\nw...      0      0       0        1        1       0       0      0            0    0
203  [italien, politik]  b" \n\nMatteo Renz\n\nvon Heinz Bierbaum\n\nMi...      0      0       0        1        1       0       0      0            0    0
204  [italien, politik]  b'Deutsch-italienische Renaissance?\n\nMichael...      0      0       0        1        1       0       0      0            0    0
Vielen Dank :)
MfG Felix
Benutzeravatar
__blackjack__
User
Beiträge: 13110
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Felix92: In den CSV-Daten scheinen Zeichenkettenrepräsentationen von Python-Listen und `bytes`-Objekten zu sein. Beides ist falsch. Bevor man etwas optimiert, sollte man es sinnvoll und fehlerfrei machen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
Felix92
User
Beiträge: 133
Registriert: Mittwoch 7. November 2018, 17:57

Oh du hast recht das ist mir bisher noch gar nicht aufgefallen :)
Benutzeravatar
Felix92
User
Beiträge: 133
Registriert: Mittwoch 7. November 2018, 17:57

So jetzt nochmal:
Ausgabe:
(nicht auf den Text achten das erledigt Tesseract noch hatte es zum testen jetzt nur "schnell" mit dem pdfminer extrahiert)
(Die Labels müssen als List oder Dict in der CSV liegen ansonsten bekomme ich Sie nicht in den MultiLabelBinarizer)

Code: Alles auswählen

                label                                               text  italien  politik
0  [italien, politik]  feuiletoneuxcxb zxcxbcrcxcxbr zxcxaitungndiens...        1        1
1  [italien, politik]  ndiskusionspapier nforschungsgrupe euintegrati...        1        1
2  [italien, politik]  ba lysendem ok at ensch en rech ender italieni...        1        1
3  [italien, politik]  bonifaz von canosa markgraf yon tuszien und di...        1        1
4  [italien, politik]  bdeutschitalienische renaisance nmichael kreil...        1        1
5  [italien, politik]  bonifaz von canosa markgraf yon tuszien und di...        1        1
6  [italien, politik]  balesandro cavalindie italienische gewerkschaf...        1        1
7  [italien, politik]  das italienische parteiensystem in historische...        1        1
8  [italien, politik]  bpnan nontnonfndas italienische rxcxatsel xex ...        1        1
9  [italien, politik]  bmeinung xcxb nmateo renzi xex der italienisch...        1        1
                           label                                               text  china  corona  italien  politik  usa
0  [usa, china, corona, politik]  nispsw strategy series focus on defense and in...      1       1        0        1    1
1             [italien, politik]  bkempisgorawantschy uhr seite nwidersprxcxbche...      0       0        1        1    0
Hier der Code:

Code: Alles auswählen

import os
import re
import string

from tqdm import tqdm

import pandas as pd
import numpy as np 

from collections import Counter
from  itertools import chain

import textract
from langdetect import detect
import spacy.lang.de.stop_words
import spacy.lang.en.stop_words

from sklearn.preprocessing import MultiLabelBinarizer
from sklearn.model_selection import train_test_split

PUNCTUATION = string.punctuation
DE_STOP_WORDS = spacy.lang.de.stop_words.STOP_WORDS
EN_STOP_WORDS = spacy.lang.en.stop_words.STOP_WORDS

FOLDER_PATH = '/home/felix/Desktop/test'
CSV_PATH = '/home/felix/Desktop/Document_Scanner/text_classifier/data'

def _unzip_labels(label):
    labels = re.split('(?=[A-Z])', label)
    label = [label.lower() for label in labels]
    return label

def create_csv(path=FOLDER_PATH, prep_label=True, prep_texts=True, save=True):
    german_labels = list()
    german_texts = list()

    english_labels = list()
    english_texts = list()

    error_files = list()

    for root, dirs, files in os.walk(path):
        for file in tqdm(files):
            try:
                file_path = os.path.abspath(os.path.join(root, file)) # filepath
                label = os.path.basename(root) # label 
                label = _unzip_labels(label)
                text = str(textract.process(file_path, method='pdfminer', encoding='utf-8'))# text   # other: pdfminer , pdftotext
                if prep_texts:
                    text = _preprocess_texts(text, filter_stop_words=False)
                if detect(text) == 'de':
                    german_labels.append(str(label))
                    german_texts.append(str(text))
                elif detect(text) == 'en':
                    english_labels.append(str(label))
                    english_texts.append(str(text))
                else:
                    print(f'can not detect de or en in {file}')
                    error_files.append(file)
            except:
                print(f'{file} occurs an error')
                error_files.append(file)

    print(f'unexcepted files : {error_files}')
        
    # german
    df_german = pd.DataFrame(columns=['label', 'text'])
    df_german['label'] = german_labels
    df_german['text'] = german_texts

    # english
    df_english = pd.DataFrame(columns=['label', 'text'])
    df_english['label'] = english_labels
    df_english['text'] = english_texts

    if prep_label:
        # Todo
        num_classes, texts, labels, df_german = _preprocess_labels(df_german)
        num_classes, texts, labels, df_english = _preprocess_labels(df_english)

    if save:
        df_german.to_csv(os.path.join(CSV_PATH, 'german1.csv'), index=False, encoding='utf-8')
        df_english.to_csv(os.path.join(CSV_PATH, 'english1.csv'), index=False, encoding='utf-8')
    else:
        return df_german, df_english

def _preprocess_labels(data):
    # count various labels
    count  = pd.Series(data['label'].str.replace(r'[\[\]\']','').str.split(',').map(Counter).sum())
    num_classes = len(set([element.strip() for element in count.index]))

    data['label'] = data['label'].str.replace(r'[\[\]\']','').str.replace(" ", '').str.split(',')

    #initialize MultiLabelBinarizer 
    mlb = MultiLabelBinarizer() 

    #transform the label column to a series of columns with binary values
    binary_labels = pd.DataFrame(mlb.fit_transform(data['label']), columns=mlb.classes_) 
    binary_labels = binary_labels.sort_index(axis=1)

    #bring data frames together
    data = data.merge(binary_labels, how='inner', left_index=True, right_index=True)
    texts, labels = data['text'], data[mlb.classes_]
    print(data)
    return num_classes, texts, labels, data

def _preprocess_texts(text, filter_stop_words=False):
    if detect(text) == 'de':
        lang = 'de'
        nlp = spacy.load("de", disable=["tagger", "parser", "ner"])
    elif detect(text) == 'en':
        lang = 'en'
        nlp = spacy.load("en", disable=["tagger", "parser", "ner"])
    else:
        print('Can`t load model')
    nlp.add_pipe(nlp.create_pipe("sentencizer"))
    text = text.lower()
    text = _clean_str(text)
    doc = nlp(text)
    filtered_sentences = []
    for sentence in doc.sents:
        filtered_tokens = list()
        for i, w in enumerate(sentence):
            s = w.string.strip()
            if len(s) == 0 or s in PUNCTUATION and i < len(doc) - 1:
                continue
            if lang == 'de':
                if not filter_stop_words or s not in DE_STOP_WORDS:
                    s = s.replace(',', '.')
                    filtered_tokens.append(s)
            elif lang == 'en':
                if not filter_stop_words or s not in EN_STOP_WORDS:
                    s = s.replace(',', '.')
                    filtered_tokens.append(s)
        filtered_sentences.append(filtered_tokens)
        flattened_list = [y for x in filtered_sentences for y in x]
        concatenated_sentences = ' '.join(flattened_list)
        print(concatenated_sentences)
    return concatenated_sentences

def _clean_str(text):
    text = re.sub(r'[^a-zA-Z\s]', "", text)
    text = re.sub(r'(.)\\1{2,}', "", text)
    text = re.sub(r'([a-z])\1+', r'\1', text)
    text =  re.sub(r"\b[a-zA-Z]\b", "", text)
    return text.strip()
Ist alles noch nicht fertig mir geht es hauptsächlich um die Erstellung der CSV-Dateien ob ich dort etwas verbessern könnte !?
Irgendwie hatte ich etwas Probleme damit die Listen:
[["label1", "label2"], ["label1", "label3", "label6"],.....] -enthält die möglichen Labels des zugehörigen Dokuments die Anzahl der Labels geht von 1 - 6
[Text1, Text2, Text3,..] - die Texte der einzelnen Dokumente
in das Dataframe zu schreiben vlt. kann mir ja da noch jmd. einen Tipp geben :)

LG
Felix
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Falls du am Format was tun kannst, würde ich dir empfehlen Parquet zu nutzen. Parquet ist von der Repräsentation wesentlich näher an der Repräsentation von Dataframes in Memory, dadurch kannst du die viel schneller einlesen und schreiben. Außerdem unterstützt Parquet byte arrays als Typ, das löst dein Listen Problem eleganter als es mit CSV geht. In der Regel sind Parquet Dateien durch eingebaute Komprimierung auch wesentlich kleiner als äquivalente CSVs. Wenn es nur darum geht Data Frames zu speichern, kann ich es absolut empfehlen.

Wenn es unbedingt CSV sein muss, kann man einfach JSON in CSV schieben. Wenn alles richtig escaped ist und alle halbwegs brauchbare CSV Parser nutzen, funktioniert dies Konstruktion, die einige die dies lesen jetzt grad wahrscheinlich in angst und schrecken versetzt, tatsächlich überraschend gut. Zur Not kann man da auch noch durch großzügige Verwendung von base64 oder so nachhelfen.

Man kann auch noch ganz zu JSON gehen und JSON Lines nutzen, hat aber einen ziemlich hohen Overhead. Aus letzterem Grund würde ich es für Dateien eher nicht nutzen, es sei den du hast semi-strukturierte Daten wie Logs o.ä. ist aber hier ja nicht der Fall. Auf der anderen Seite Computer sind schon recht schnell und wenn du nicht so viele Daten hast kannst du es dir vielleicht trotzdem erlauben, kann man ja komprimieren aber man verheizt dann natürlich einige CPU Zyklen beim parsen.
Benutzeravatar
Felix92
User
Beiträge: 133
Registriert: Mittwoch 7. November 2018, 17:57

@DasIch danke dir Parquet sagt mir nichts werde ich mir mal ansehen hört sich ja recht vielversprechend an :)

Wäre trotzdem super wenn jemand noch was zu pandas (csv) schreiben könnte bzw. vlt. eine elegantere Lösung als meine ^^
Benutzeravatar
Felix92
User
Beiträge: 133
Registriert: Mittwoch 7. November 2018, 17:57

@DasIch nochmal vielen Dank dieser Tipp war echt Gold wert :)

so jetzt nochmal ich bitte um Verbesserungsvorschläge :)
helper.py

Code: Alles auswählen

"""utils for text processing and language checking
"""
import re
import string

import fitz
import nltk
nltk.download('punkt')
nltk.download('stopwords')
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
import textract
from langdetect import detect

def extract_text(file_path):
    """extract the text from a given text document or image

    Parameters
    ----------
    file_path : str
        string to the current text file

    Returns
    -------
    str
        the extracted string/text from document
    """
    try:
        text = ''
        with fitz.open(file_path) as doc:
            for page in doc:
                text+= page.getText()
    except:
        print('_______________USING_TEXTRACT_______________')
        # If can not extract text then use ocr lib to extract the scanned pdf file.
        text = textract.process(file_path, method='tesseract', encoding='utf-8')
    return text

def check_language(text):
    """checks the language of the given string/text

    Parameters
    ----------
    text : str
        the input string/text

    Returns
    -------
    str
        the language of the string/text (de, en)
    """
    if detect(text) == 'de':
        language = 'de'
    elif detect(text) == 'en':
        language = 'en'
    return language

def clean_str(text):
    """clean the string from unusable characters, numbers, etc.

    Parameters
    ----------
    text : str
        the input string/text

    Returns
    -------
    str
        the cleaned string/text
    """
    # URLs
    text = re.sub(r'^https?:\/\/.*[\r\n]*', '', text, flags=re.MULTILINE)
    # Linebreaks
    text = re.sub(r'(?<!\\)(\\\\)*\\n|\n', "", text, flags=re.MULTILINE)
    text = re.sub(r'[^a-zA-Z\s]', "", text, flags=re.MULTILINE)
    # multi letters >3 times
    text = re.sub(r'(.)\\1{2,}', "", text, flags=re.MULTILINE)
    # alphabetic 
    text =  re.sub(r"\b[a-zA-Z]\b", "", text, flags=re.MULTILINE)
    return text

def preprocess_texts(text, language, filter_stop_words=True):
    """remove stopwords and punctuations

    Parameters
    ----------
    text : str
        the input string/text
    language : str
        the language of the current string/text (de, en)
    filter_stop_words : bool, optional
        removes stopwords from a given dictonary, by default True

    Returns
    -------
    str
        the filtered string/text
    """
    if language == 'de':
        stopWords = stopwords.words('german')
    elif language == 'en':
        stopWords = stopwords.words('english')

    text = str(clean_str(text)).strip()

    word_tokens = word_tokenize(text)

    if filter_stop_words:
        filtered_tokens = [word for word in word_tokens if not word in stopWords and not word in string.punctuation]
    else:
        filtered_tokens = [word for word in word_tokens if not word in string.punctuation]
    concatenated_sentences = ' '.join(filtered_tokens)
    text = concatenated_sentences.lower()
    return text
Data.py

Code: Alles auswählen

"""
This file generates the data for the neural network and contains the data class with utils
"""
import os
import re
from tqdm import tqdm

import pandas as pd
import numpy as np 

from collections import Counter

from sklearn.preprocessing import MultiLabelBinarizer
from sklearn.model_selection import train_test_split
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

from utils.helper import extract_text, check_language, clean_str, preprocess_texts

FOLDER_PATH = '/home/felix/Desktop/MachineLearning'
FILE_PATH = '/home/felix/Desktop/Document_Scanner/text_classifier/data'

def __unzip_labels(label):
    """unzips a string with more than one word

    Parameters
    ----------
    label : tuple
        tuple or list of the text labels from the folder name

    Returns
    -------
    list
        splited labels as list of strings
    """
    labels = re.split('(?=[A-Z])', label)
    label = [label.lower() for label in labels]
    return label

def __create_parquet(path=FOLDER_PATH, prep_texts=True, save=True):
    """creates a german and english dataframe from the folder/documents

    Parameters
    ----------
    path : str , optional
        path to the root folder from documents and subfolder, by default FOLDER_PATH
    prep_texts : bool, optional
        preprocessing the texts before continue, by default True
    save : bool, optional
        save the dataframes as .parquet file, by default True

    Returns
    -------
    tuple of dataframes
        the generated german and english dataframe from folder/documents
    """
    german_labels = list()
    german_texts = list()

    english_labels = list()
    english_texts = list()

    error_files = list()

                #dirs
    for root, _, files in os.walk(path):
        for file in tqdm(files):
            try:
                file_path = os.path.abspath(os.path.join(root, file)) 
                label = os.path.basename(root) 
                label = __unzip_labels(label)
                text = str(extract_text(file_path))
                language = check_language(text)
                if prep_texts:
                    text = preprocess_texts(text, language, filter_stop_words=True)
                if language == 'de':
                    german_labels.append(label) 
                    german_texts.append(text)
                elif language == 'en':
                    english_labels.append(label) 
                    english_texts.append(text)
                else:
                    print(f'can not detect de or en in {file}')
                    error_files.append(file)
            except:
                print(f'{file} occurs an error')
                error_files.append(file)

    print(f'unexcepted files : {error_files}')
            
    # german dataframe
    df_german = pd.DataFrame(columns=['label', 'text'])
    df_german['label'] = german_labels
    df_german['text'] = german_texts

    # english dataframe
    df_english = pd.DataFrame(columns=['label', 'text'])
    df_english['label'] = english_labels
    df_english['text'] = english_texts

    df_german.to_parquet(os.path.join(FILE_PATH, 'german.parquet'), index=False)
    df_english.to_parquet(os.path.join(FILE_PATH, 'english.parquet'), index=False)

    print('--------------------------FILES CREATED--------------------------')

    return df_german, df_english

def __preprocess_parquet_file(data, num_words, maxlen):
    """preprocess the labels and texts from dataframe for neural network usage

    Parameters
    ----------
    data : dataframe
        the generated dataframe
    num_words : int
        the maximum number of words to keep, based on word frequency
    maxlen : int
        the maximum length of all sequences

    Returns
    -------
    tuble
        num_classes: the number of different classes in the dataframe
        texts: the dataframe serie of texts
        labels: the dataframe series of binary splitted labels
    """
    # count various labels
    count  = pd.Series(data['label'].map(Counter).sum())
    num_classes = len(set(count.index))

    # initialize MultiLabelBinarizer 
    mlb = MultiLabelBinarizer() 

    # transform the label column to a series of columns with binary values
    binary_labels = pd.DataFrame(mlb.fit_transform(data['label']), columns=mlb.classes_) 
    binary_labels = binary_labels.sort_index(axis=1)

    # bring data frames together
    data = data.merge(binary_labels, how='inner', left_index=True, right_index=True)
    texts = data['text']
    labels = data[mlb.classes_]

    # tokenize the texts
    tokenizer = Tokenizer(num_words=num_words, filters='!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~\t\n', lower=True, split=' ')
    tokenizer.fit_on_texts(texts)
    sequences = tokenizer.texts_to_sequences(texts)
    texts = pad_sequences(sequences, maxlen=maxlen)
    return num_classes, texts, labels

def load_data(language, num_words, maxlen, test_size, creating_parquet):
    """[summary]

    Parameters
    ----------
    language : str
        load german or english dataframe
    num_words : int
        the maximum number of words to keep, based on word frequency
    maxlen : int
        the maximum length of all sequences
    test_size : float
        percentage for test data
    creating_parquet : bool
        if used it creates new dataframes from

    Returns
    -------
    tuple
        x_train, y_train, x_test, y_test: splitted data 
        num_classes: the number of different classes in the dataframe
        texts: the dataframe serie of texts
        labels: the dataframe series of binary splitted labels
    """
    if language == 'de':
        parquet_file = os.path.join(FILE_PATH, "german.parquet")    
    elif language == 'en':
        parquet_file = os.path.join(FILE_PATH, "english.parquet")

    if not os.path.isfile(parquet_file) or creating_parquet:
        df_german, df_english = __create_parquet()
        if language == 'de':
            df = df_german
        elif language == 'en':
            df = df_english
    else:
        df = pd.read_parquet(parquet_file)  

    num_classes, texts, labels = __preprocess_parquet_file(df, num_words=num_words, maxlen=maxlen)

    x_train, x_test, y_train, y_test =\
    train_test_split(texts, labels, test_size=test_size) 
    return (x_train, y_train), (x_test, y_test) , (num_classes, texts, labels)

class Data():
    """Data class to train a neural network
    """
    def __init__(self, language, num_words, maxlen, test_size, creating_parquet):
        """Creates a data instance with the given arguments

        Parameters
        ----------
        language : str
            load german or english dataframe
        num_words : int
            the maximum number of words to keep, based on word frequency
        maxlen : int
            the maximum length of all sequences
        test_size : float
            percentage for test data
        creating_parquet : bool
            if used it creates new dataframes from
        """
        (self.x_train, self.y_train), (self.x_test, self.y_test), (self.num_classes, self.texts, self.labels) =\
        load_data(language=language, num_words=num_words, maxlen=maxlen, test_size=test_size, creating_parquet=creating_parquet)
        # Convert to float32
        self.x_train = self.x_train.astype(np.float32)
        self.y_train = self.y_train.astype(np.float32)
        self.x_test = self.x_test.astype(np.float32)
        self.y_test = self.y_test.astype(np.float32)
        self.train_size = self.x_train.shape[0]
        self.test_size = self.x_test.shape[0]
        self.train_splitted_size = None
        self.val_size = None

    def get_train_set(self):
        return self.x_train, self.y_train
    
    def get_test_set(self):
        return self.x_test, self.y_test
    
    def get_splitted_train_validation_set(self, validation_size=0.15):
        """splits the train set into validation set
            note: instead of this you can use argument:validation_split=x.x in model.fit() method

        Parameters
        ----------
        validation_size : float, optional
            percentage for validation data, by default 0.15

        Returns
        -------
        tuple
            splitted train and validation set
        """
        self.x_train_, self.x_val, self.y_train_, self.y_val =\
            train_test_split(self.x_train, self.y_train, test_size=validation_size)
        self.val_size = self.x_val.shape[0]
        self.train_splitted_size = self.x_train_.shape[0]
        return (self.x_train_, self.x_val), (self.y_train_, self.y_val)
Funktionieren tut alles allerdings werde ich bestimmt noch mal die ein oder andere Stelle überarbeiten gefällt mir noch nicht so recht :D
Vorschläge dafür oder Anmerkungen ?

Achso und eine Frage noch was sagen die Erfahrungen so wenn es um das extrahieren von Text aus Dokumenten geht (hauptsächlich PDFs) gibt es dort Präferenzen ? (die Genauigkeit ist wichtig Zeit spielt keine Rolle)
So ganz bin ich mit den extrahierten Texten noch nicht glücklich allerdings wäre mir relativ wichtig das ich die Texte gleich als String bekomme und nicht erst das Dokument in Bilder zerstückeln muss (dann pytesseract: image_to_string() ) etc.

Besten Dank
Felix
Benutzeravatar
__blackjack__
User
Beiträge: 13110
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Felix92: Anmerkungen zum `helper`-Modul.

In `extract_text()` ist ein ”nacktes” ``except`` ohne konkrete Ausnahme(n). Das behandelt *alle* Ausnahmen gleich, auch solche mit denen man nicht rechnet und die man lieber als Ausnahme das Programm abbrechen lassen will um den Fehler zu beheben. Zum Beispiel macht es keinen Sinn das bei einem `FileNotFound` im ``except`` dann noch mal versucht wird die nicht existierende Datei mit Tesseract zu verarbeiten.

Zeichenketten wiederholt in einer Schleife mit ``+=`` zu erweitern ist potentiell ineffizient weil Zeichenketten nicht veränderbar sind und damit bei jedem Schleifendurchlauf immer mehr Daten unsinnig im Speicher herumkopiert werden müssen. Idiomatisches Python sammelt die Teilzeichenketten in einer Liste und setzt die am Ende mit `join()` zusammen. In `extract_text()` braucht man aber noch nicht mal eine Liste, weil man die Schleife auch als Generatorausdruck formulieren kann.

`check_language()` rennt in eine Ausnahme wenn das Ergebnis von `detect()` weder "de" noch "en" ist, weil dann `language` undefiniert ist. Die Funktion scheint auch nicht wirklich mehr zu machen als der `detect()`-Aufruf selbst. Zudem verstehe ich auch nicht so ganz warum „detect“ hier durch „check“ ersetzt wurde, denn die Funktion prüft ja nichts, sondern detektiert tatsächlich die Sprache.

In `clean_str()` unterscheiden sich die Codezeilen nur durch das erste Argument von den `re.sub()`-Aufrufen. Die Argumente könnte man also in eine Liste herausziehen und eine Schleife schreiben, die die alle abarbeitet. Oder gar alle zu einem regulären Ausdruck zusammenfassen.

Ist das gewollt das HTTP(S)-URLs nur entfernt werden wenn sie direkt am Zeilenanfang stehen, dann aber mit der kompletten Zeile‽

Was soll denn *das* hier bedeuten: "(?<!\\)(\\\\)*\\n|\n"? Die zweite Alternative ist klar, führt aber dazu, dass die Zeilen ohne Leerzeichen verbunden werden. Entstehen dadurch nicht Fehler wenn ein Wort am Zeilenende nahtlos an das Wort am nächsten Zeilenanfang gepappt wird?

Und die erste Alternativen matcht auf "\n" aber nicht auf "\\n" aber dann wieder auf "\\\n", also letztendlich auf "n" mit einer ungeraden Anzahl von Backslashes davor, aber nicht auf "n" mit einer geraden Anzahl von Backslashes. Warum‽

"(.)\\1{2,}" ist falsch. Da ist ein \ zu viel. Das matcht auf irgendein Zeichen, gefolgt von einem Backslash, gefolgt von mindestens zweimal der Ziffer 1. Also beispielsweise auf "x\11". Auf mindestens drei gleiche Zeichen würde "(.)\1{2,}" matchen.

Das ersetzen von allem was kein ASCII-Buchstabe oder „whitespace“ ist, geht etwas effizienzer wenn man nach der Zeichenklasse ein "+" setzt, damit das Muster nicht tatsächlich jedes Zeichen einzeln finden muss.

`stopWords` in `preprocess_text()` sollte `stopwords` heissen.

Wenn man der Funktion eine andere `language` als "de" oder "en" übergibt, dann führt das erst weiter unten in der Funktion zu einer Ausnahme weil `stopwords` dann undefiniert ist. Das sollte früher auffallen und sinnvoller an den Aufrufer gemeldet werden. Zum Beispiel in einer eigenen Ausnahme. An der Stelle könnte man sich dann vielleicht auch gleich das `filter_stop_words`-Argument sparen wenn man für `language` auch `None` erlaubt.

`clean_str()` liefert bereits eine Zeichenkette, da braucht es keinen zusätzlichen `str()`-Aufruf.

Der ``if``- und der ``else``-Zweig bei `filter_stop_words` ist fast identisch.

Zwischenstand (ungetestet):

Code: Alles auswählen

"""
Utils for text processing and language checking.
"""
import re
import string

import fitz
import nltk

nltk.download("punkt")
nltk.download("stopwords")
import textract
from langdetect import detect as detect_language
from nltk.corpus.stopwords import words as get_stopwords
from nltk.tokenize import word_tokenize


def extract_text(file_path):
    """
    Extract the text from a given text document or image.

    Parameters
    ----------
    file_path : str
        string to the current text file

    Returns
    -------
    str
        the extracted string/text from document
    """
    try:
        with fitz.open(file_path) as document:
            text = "".join(page.getText() for page in document)
    #
    # TODO Only handle expected exceptions here.
    #
    except:
        print("_______________USING_TEXTRACT_______________")
        #
        # If can not extract text then use ocr lib to extract the scanned pdf
        # file.
        #
        text = textract.process(
            file_path, method="tesseract", encoding="utf-8"
        )
    return text


def clean_str(text):
    """
    Clean the string from unusable characters, numbers, etc.

    Parameters
    ----------
    text : str
        the input string/text

    Returns
    -------
    str
        the cleaned string/text
    """
    patterns = [
        # URLs
        r"^https?:\/\/.*[\n\r]*",
        # linebreaks
        r"(?<!\\)(\\\\)*\\n|\n",
        # everything not ascii letters or whitespace
        r"[^a-zA-Z\s]+",
        # multi letters >3 times
        r"(.)\1{2,}",
        # single letters
        r"\b[a-zA-Z]\b",
    ]
    for pattern in patterns:
        text = re.sub(pattern, "", text, flags=re.MULTILINE)
    return text


def preprocess_text(text, language):
    """
    Remove stopwords and punctuation.

    If `language` is given as `None`, no stopwords are filtered.

    Parameters
    ----------
    text : str
        the input string/text
    language : str or None
        the language of the current string/text (de, en) or None

    Returns
    -------
    str
        the filtered string/text
    """
    iso_language_to_language_name = {
        None: None,
        "de": "german",
        "en": "english",
    }
    try:
        language_name = iso_language_to_language_name[language]
    except KeyError:
        raise ValueError(
            "language must be one of {} or `None`".format(
                ", ".join(iso_language_to_language_name.keys())
            )
        )

    filter_sets = [
        set(string.punctuation),
        get_stopwords(language_name) if language_name else frozenset(),
    ]
    return " ".join(
        word
        for word in word_tokenize(clean_str(text).strip())
        if not any(word in filter_set for filter_set in filter_sets)
    ).lower()
Je nach dem was `get_stopwords()` als Ergebnis liefert, könnte es auch Sinn machen aus der Liste `filter_sets` tatsächlich ein `set`-Objekt zu machen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten