Implemetanacja Pandas DataFrame Dataset w TorchText

Dziś techniczny wpis o tym, jak podawać dane do sieci w Pytorch przy pomocy Pandas DataFrame z wykorzystaniem biblioteki TorchText. Z wpisu dowiesz się jak zaimplementować swój własny DataSet oraz jak wpleść ramki z Pandas w proces nauki sieci.

W chwili pisania TorchText nie ma możliwości pobierania danych wprost z Pandas. Obsługuje szereg typów plików oraz gotowych zbiorów, ale zakładam, że większość z nas i tak na wstępnym etapie przetwarza dane przy pomocy Pandas.

Jeżeli nie znacie jeszcze TorchText to zapraszam do wpisu „Przetwarzasz teksty, robisz NLP, TorchText Ci pomoże!” . Omówiłem w nim idee pracy z TorchText oraz na własnych doświadczeniach opisałem w czym może ona pomóc.

Skąd TorchText pobiera dane

Biblioteka TrochText ma za zadanie pomagać w wczytywaniu i przetwarzaniu danych tekstowych (sekwencyjnych). Zapewnia szereg klas do wczytywania danych z różnych formatów. Klasą bazową służącą do wczytywania danych jest Dataset. Jednak najczęściej wykorzystywaną implementacją jest TabularDataset, która „Defines a Dataset of columns stored in CSV, TSV, or JSON format.” (cytując dokumentację).

W chwili pisania tego artykułu nie ma klasy, która pozwalałaby na wczytanie danych z Pandas DataFrame. A co w tym złego się zapytacie?

A to, że dla mnie ramka z danymi to pierwsze, w czym obrabiam dane. Daje szerokie możliwości wstępnej obróbki oraz wczytywanie z niej danych. Jest także wspierane przez wiele innych bibliotek. Stąd pojawiła się potrzeba, aby ramka była moim źródłem danych i stanowiła część całego procesu od wczytania po trening.

Tego problemu doświadczyli także inni, szybki research i trafiłem na przykładową prostą implementację z stack overflow (https://stackoverflow.com/questions/52602071/dataframe-as-datasource-in-torchtext).

Dataframe jako źródło danych do TorchTexh – implementacja

Implementacja jest dosyć prosta. Należy stworzyć dwie klasy. Pierwsza dziedziczącą po Dataset, a druga dziedzicząca po data.Example. Cała filozofia polega na przemapowaniu listy pól na nazwami kolumn z dataframe (Fields) na pola w klasie Example.

Jak się do tego zabrać? Na początku zdefiniujmy klasę DataFrameDataset.

# code taken from https://stackoverflow.com/questions/52602071/dataframe-as-datasource-in-torchtext

from torchtext.data import Field, Dataset, Example
import pandas as pd


class DataFrameDataset(Dataset):
    """Class for using pandas DataFrames as a datasource"""

    def __init__(self, examples, fields, filter_pred=None):
        """
        Create a dataset from a pandas dataframe of examples and Fields
        Arguments:
            examples pd.DataFrame: DataFrame of examples
            fields {str: Field}: The Fields to use in this tuple. The
                string is a field name, and the Field is the associated field.
            filter_pred (callable or None): use only exanples for which
                filter_pred(example) is true, or use all examples if None.
                Default is None
        """
        self.examples = examples.apply(
            SeriesExample.fromSeries, args=(fields,), axis=1).tolist()
        if filter_pred is not None:
            self.examples = filter(filter_pred, self.examples)
        self.fields = dict(fields)
        # Unpack field tuples
        for n, f in list(self.fields.items()):
            if isinstance(n, tuple):
                self.fields.update(zip(n, f))
                del self.fields[n]


Do konstruktora tej klasy przekazujemy dwa argumenty. Pandasową ramkę z danymi oraz słownik określający jak poszczególne kolumny z tej ramki mają być przetwarzane (Fields). Następnie w konstruktorze budujemy listę self.examples. Jest to lista przykładów, każdy przykład to jeden wiersz z naszego źródła danych.

Do budowania Example wykorzystamy własną pomocniczą klasę SeriesExample. Z przekazanej serii danych stworzy ona egzemplarze z ustawionymi polami o nazwach przekazanych w liście fields. Zwróćcie uwagę na metody setattr.

class SeriesExample(Example):
    """Class to convert a pandas Series to an Example"""

    def __str__(self):
        # todo: implement something more usefull :)
        return str(self)

    @classmethod
    def fromSeries(cls, data, fields):
        return cls.fromdict(data.to_dict(), fields)

    @classmethod
    def fromdict(cls, data, fields):
        ex = cls()

        for key, field in fields.items():
            if key not in data:
                raise ValueError("Specified key {} was not found in "
                                 "the input data".format(key))
            if field is not None:
                setattr(ex, key, field.preprocess(data[key]))
            else:
                setattr(ex, key, data[key])
        return ex

Przykład użycia DataframeDataset

Przykład użycia tej klasy, znajdziecie we wpisie tworzącym sieć LSTM do zliczania znaków. Tam bez wyjaśnienia użyłem tej klasy, gdzie wygenerowane dane trafiły do dataframe. Następnie na jego podstawie stworzyłem DataFrameDataset, dzięki czemu można go było użyć w TorchText.

W wielkim skrócie, aby użyć DataFrameDataset musicie wykonać:

#tu są nasze przykładowe dane, 
data = {'text': ["Ola ola Polska Gola"], 'label': [1]}
train_df = pd.DataFrame.from_dict(data)
 
TEXT = data.Field(sequential=True, lower=True, fix_length=None)
LABEL = data.Field(sequential=False, use_vocab=False, 
                   is_target=True)
 
fields = {"text": TEXT, "label": LABEL}

train_ds = DataFrameDataset(train_df, fields)
TEXT.build_vocab(train_ds, min_freq=1)
train_iter = BucketIterator(
    train_ds, 
    batch_size=batch_size, 
    sort_key=lambda x: len(x.text), 
    sort_within_batch=True, 
    device=device)

Ładujemy nasze dane do ramki, w tym przykładzie powinniśmy mieć w ramce dwie kolumny „text” i „label”. Pierwsza zawierająca w każdym wierszu teksty, a druga etykietę przypisaną do danego tekstu.

Tworzymy dwa pola (Fields) a następnie do konstruktora klasy DataFrameDataset przekazujemy ramkę oraz słownik: „nazwa kolumny z ramki”: odpowiedni Field.

Dalej już możemy postępować standardowo jak przy normalnym użyciu TorchText.

Podsumowanie i materiały

We wpisie pokazałem, w jaki sposób wykorzystać ramkę z Pandas jako źródło danych do TorchText. Jest to wygodny skrót, który pomaga w ujednoliceniu całego procesu czyszczenia danych oraz ich wczytywania.
Z doświadczenia w naszych firmowych komercyjnych projektach mogę powiedzieć, że sprawdził się bardzo dobrze.

Można go jeszcze rozszerzyć o to, aby dane nie pochodziły z samej ramki a z Python’owego generatora. Spowoduje to, że nie będziecie musieli martwić się o wielkość danych i zasoby RAM’u przy gigantycznych zbiorach tekstowych.

PS. Zostaw swojego maila a dostaniesz informacje o nowych artykułach i planowanych przez mnie kursach.

Photo by Alex Hiller on Unsplash

Ciekawe, wartościowe, podziel się proszę opinią!

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.