[JAVA] Einführung in das maschinelle Lernen mit Spark "Price Estimation" # 2 Datenvorverarbeitung (Umgang mit Kategorievariablen)

Überblick

-Einführung in das maschinelle Lernen mit ** Java ** und ** Apache Spark **

Umgebung

Vorverarbeitung unter Berücksichtigung der Art der Daten

Letztes Mal wurde aus CSV-Daten als Spark-Tabellendaten gelesen.

Schauen wir uns nun die Daten an, die dieses Mal zum Lernen verwendet wurden.

Die Daten finden Sie hier [https://raw.githubusercontent.com/riversun/spark-gradient-boosting-tree-regression-example/master/dataset/gem_price_ja.csv].

Dies sind die Daten.

gem_price_ja.csv


id,material,shape,weight,brand,shop,price
0,Silber,Armband,56,Berühmte Marken aus Übersee,Kaufhaus,40864
1,Gold,Ring,48,Inländische berühmte Marke,Direkt verwalteter Laden,63055
2,Dia,Ohrringe,37,Inländische berühmte Marke,Direkt verwalteter Laden,112159
3,Dia,Halskette,20,Berühmte Marken aus Übersee,Direkt verwalteter Laden,216053
4,Dia,Halskette,33,Berühmte Marken aus Übersee,Kaufhaus,219666
5,Silber,Brosche,55,Inländische berühmte Marke,Kaufhaus,16482
6,Platin,Brosche,58,Übersee super berühmte Marke,Direkt verwalteter Laden,377919
7,Gold,Ohrringe,49,Inländische berühmte Marke,Direkt verwalteter Laden,60484
8,Silber,Halskette,59,Keine Marke,Billiger Laden,6256
・ ・ ・ ・

Qualitative und quantitative Daten

Übrigens, wenn Sie sich ansehen, welche Werte für jeden Variablennamen (Material, Form usw.) verfügbar sind, können Sie sie wie folgt organisieren.

image.png

Auf diese Weise sind die Daten auf der linken Seite (** Material, Marke, Geschäft, Form **) der Wert der Zeichenfolge, die die Kategorie angibt. Solche Daten, die nicht direkt als numerischer Wert gemessen werden können, werden als ** qualitative Daten ** bezeichnet.

Andererseits sind die Daten auf der rechten Seite (** Gewicht, Preis **) ein numerischer Wert, der kontinuierliche Werte annimmt. Daten, die direkt als numerischer Wert gemessen oder nach den vier Regeln berechnet werden können, werden als ** quantitative Daten ** bezeichnet.

Kategoriale Variable und kontinuierliche Variable

Wie oben erwähnt, werden Variablen wie ** Material, Marke, Geschäft, Form **, die ** qualitative Daten ** enthalten, als ** kategoriale Variablen ** bezeichnet.

Außerdem werden ** quantitative Daten **, dh Variablen wie ** Gewicht und Preis **, die aufeinanderfolgende Werte (kontinuierliche Werte) annehmen, als ** kategoriale Variablen ** bezeichnet.

image.png

Umgang mit kategorialen Variablen

Da maschinelles Lernen schließlich eine numerische Berechnung ist, müssen die qualitativen Daten kategorialer Variablen vor der Verarbeitung in einen numerischen Wert umgewandelt werden.

Schauen wir uns die kategorialen Variablen genauer an.

Die kategorialen Variablen ** Material ** und ** Form ** sind das Material des Zubehörs und die Form nach der Verarbeitung (** Form **), aber es gibt einen semantischen Unterschied zwischen ihnen.

image.png

** Material ** sind *** Diamant, Platin, Gold, Silber ***, aber wir wissen aus Erfahrung, dass Diamanten teurer sind als Silber, also hat ** Material ** Es scheint eine Hierarchie in den Daten zu geben.

Auf der anderen Seite ist ** Form ** Ringe, Halsketten, Ohrringe, Broschen, Armbänder ***, aber es gibt keine Reihenfolge, in der Armbänder Ringen überlegen sind, und alle Optionen sind gleichberechtigt. Sieht aus als ob.

Kategorievariablen werden in Ordnungsvariablen (Ordnungsvariablen) und Nominalvariablen (Nominalvariablen) unterteilt.

Diejenigen, in denen die Werte kategorialer Variablen geordnet sind und deren Größe verglichen werden kann, werden als ** Ordnungsvariablen ** (oder Ordnungsskalenvariablen) bezeichnet. [^ 1]

[^ 1]: In diesem Beispiel sind Gold und Platin mit dem gleichen Gewicht derzeit teurer, sodass möglicherweise nicht klar ist, welches das obere ist, aber in einem leicht verständlichen Beispiel ** oben, Mitte, unten * Variablen mit * oder ** hoch, mittel, niedrig ** usw. können eindeutig als Ordnungsvariablen definiert werden.

Wenn andererseits die Werte von kategorialen Variablen nicht eingestuft werden, werden sie als ** nominelle Variable ** (oder nominale Skalenvariable) bezeichnet.

Dies ist der Inhalt, der am Anfang des Statistiklehrbuchs erscheint. Es ist jedoch wichtig, sich daran zu erinnern, da dies ein wichtiges Wissen für die Weiterentwicklung des maschinellen Lernens ist.

Wenn Sie sich diesmal die Daten ansehen, sieht es wie folgt aus.

image.png

Dieses Mal werden wir kategoriale Variablen nicht klar in sequentielle Variablen und nominale Variablen unterscheiden, sondern sie als kategoriale Variablen und kontinuierliche Variablen verarbeiten.

Quantifizieren Sie (Index-) kategoriale Variablen

Wie oben erwähnt, müssen kategoriale Variablen für maschinelles Lernen quantifiziert werden.

Es gibt verschiedene Quantifizierungstechniken, aber dieses Mal werden wir einfach jede Variable indizieren.

↓ Ein solches Bild.

image.png

Behandeln Sie kategoriale Variablen in Spark

Schreiben wir nun den Code, um die kategorialen Variablen zu quantifizieren. Führen Sie zunächst den folgenden Code aus.


import org.apache.spark.ml.feature.StringIndexer;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;

public class GBTRegressionStep02 {

  public static void main(String[] args) {

    System.setProperty("hadoop.home.dir", "c:\\Temp\\winutil\\");

    org.apache.log4j.Logger.getLogger("org").setLevel(org.apache.log4j.Level.ERROR);
    org.apache.log4j.Logger.getLogger("akka").setLevel(org.apache.log4j.Level.ERROR);

    SparkSession spark = SparkSession
        .builder()
        .appName("GradientBoostingTreeGegression")
        .master("local[*]")
        .getOrCreate();

    Dataset<Row> dataset = spark
        .read()
        .format("csv")
        .option("header", "true")
        .option("inferSchema", "true")
        .load("dataset/gem_price_ja.csv");

    StringIndexer materialIndexer = new StringIndexer()// (1)
        .setInputCol("material")// (2)
        .setOutputCol("materialIndex");// (3)

    Dataset<Row> materialIndexAddedDataSet = materialIndexer.fit(dataset).transform(dataset);// (4)

    materialIndexAddedDataSet.show(10);// (5)

  }

}

Codekommentar

** (1) ** *** ・ ・ Verwenden Sie *** StringIndexer ***, um kategoriale Variablen zu quantifizieren (zu indexieren). Es hat die Aufgabe, eine bestimmte Variable einzugeben und das codierte (konvertierte) Ergebnis als eine andere Variable auszugeben. Das Codieren ist hier nur ein Vorgang zum Konvertieren einer Zeichenfolge in einen numerischen Wert.

** (2) ** ***. SetInputCol ("Material") *** ・ ・ ・ Geben Sie den Variablennamen an, der in diesen *** StringIndexer *** eingegeben werden soll. Die ** Variablen ** können als ** Spalten ** der Tabelle betrachtet werden, da sie sich beim Laden der CSV-Daten in Spark in einem tabellarischen ** Datensatz ** befinden. col ist ** column ** col, nicht wahr? Mit anderen Worten, der Spaltenname auf der Eingabeseite von *** StringIndexer ** ist *** "Material" ***

** (3) ** ***. SetOutputCol ("materialIndex") *** ・ ・ ・ Geben Sie hier den Spaltennamen auf der Ausgabeseite an.

** (4) ** ・ ・ ・ ** Erstellen Sie ein Lernmodell zum Digitalisieren kategorialer Variablen mit der Methode fit () **. Wenn dem resultierenden Trainingsmodell mit der Transformationsmethode ein Datensatz zugewiesen wird, wird ein neuer Datensatz generiert.

Erstellen Sie ein Lernmodell zur Quantifizierung kategorialer Variablen mit der Anpassungsmethode

aber, Hier ist die ** fit () ** -Methode des *** StringIndexer *** -Objekts mit dem Namen ** materialIndexer ** offensichtlich, wird jedoch nicht gelernt. Es wird einfach ein ** Gerät ** [^ 2] erstellt, das die als kategoriale Variable angegebene Zeichenfolge digitalisiert.

[^ 2]: *** StringIndexer *** erbt eine Klasse namens ** Estimator **. ** Estimator ** ist ursprünglich eine Klasse für ** Lernende **, aber bei der Pipeline-Verarbeitung, die eine Funktion von ** spark.ml ** ist, wie *** StringIndexer *** Durch die Ausstattung des Vorverarbeitungssystems und des Lernenden mit derselben Schnittstelle ist es eine solche Schnittstelle, um den Ablauf von ** Vorverarbeitung → Vorverarbeitung → Lernen ** als Pipeline zu realisieren.

Bei der Methode ** transform ** wird der Prozess zum Erstellen einer neuen indizierten Variablen mit dem Namen ** materialIndex ** aus dem Spaltennamen ** material ** in dem tatsächlich eingegebenen Datensatz ausgeführt.

Ich denke, das ist am unklarsten, aber ich werde die Bedeutung später verstehen, also ist es jetzt mit einem Zauber in Ordnung.

** (5) ** ・ ・ ・ ** Zeigt den Datensatz nach der Verarbeitung von StringIndexer ** an.

Wenn ich den Code ausführe, sieht er folgendermaßen aus:

image.png

Rechts wurde eine Spalte mit dem Namen ** materialIndex ** hinzugefügt!

Auf diese Weise können kategoriale Variablen beim maschinellen Lernen nur verwendet werden, wenn sie auf irgendeine Weise quantifiziert werden, sodass auch andere kategoriale Variablen quantifiziert werden.

Also werde ich versuchen, andere kategoriale Variablen ** Form, Marke, Shop ** mit *** StringIndexer ** auf die gleiche Weise zu quantifizieren (zu indizieren).

Dann wird es wie folgt sein

import org.apache.spark.ml.feature.StringIndexer;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;

public class GBTRegressionStep02_part02 {

  public static void main(String[] args) {
    SparkSession spark = SparkSession
        .builder()
        .appName("GradientBoostingTreeGegression")
        .master("local[*]")
        .getOrCreate();

    Dataset<Row> dataset = spark
        .read()
        .format("csv")
        .option("header", "true")
        .option("inferSchema", "true")
        .load("dataset/gem_price_ja.csv");

    StringIndexer materialIndexer = new StringIndexer()// (1)
        .setInputCol("material")
        .setOutputCol("materialIndex");

    StringIndexer shapeIndexer = new StringIndexer()// (2)
        .setInputCol("shape")
        .setOutputCol("shapeIndex");

    StringIndexer brandIndexer = new StringIndexer()// (3)
        .setInputCol("brand")
        .setOutputCol("brandIndex");

    StringIndexer shopIndexer = new StringIndexer()// (4)
        .setInputCol("shop")
        .setOutputCol("shopIndex");

    Dataset<Row> dataset1 = materialIndexer.fit(dataset).transform(dataset);// (5)
    Dataset<Row> dataset2 = shapeIndexer.fit(dataset).transform(dataset1);// (6)
    Dataset<Row> dataset3 = brandIndexer.fit(dataset).transform(dataset2);// (7)
    Dataset<Row> dataset4 = shopIndexer.fit(dataset).transform(dataset3);// (8)

    dataset4.show(10);
  }
}

Für ** (1) bis (4) ** wird *** StringIndexer *** für jede Variable vorbereitet. ** (5) - (8) ** werden nacheinander in der Reihenfolge ** materialIndexer → shapeIndexer → brandIndexer → shopIndexer ** indiziert, um einen neuen Datensatz zu erstellen.

Wenn Sie dies tun, erhalten Sie:

image.png

Sie können sehen, dass rechts ** materialIndex, shapeIndex, brandIndex, shopIndex ** hinzugefügt wurden.

Übrigens, ↓ Ist das etwas, was nicht geht?

    Dataset<Row> dataset1 = materialIndexer.fit(dataset).transform(dataset);// (5)
    Dataset<Row> dataset2 = shapeIndexer.fit(dataset).transform(dataset1);// (6)
    Dataset<Row> dataset3 = brandIndexer.fit(dataset).transform(dataset2);// (7)
    Dataset<Row> dataset4 = shopIndexer.fit(dataset).transform(dataset3);// (8)

Wie hübsch.

Wenn ein bestimmter Prozess abgeschlossen ist, z. B. ** materialIndexer → shapeIndexer → brandIndexer → shopIndexer **, können Sie intelligenter schreiben, dass der nächste Prozess mit diesem Prozess als Eingabe ausgeführt wird.

Das ist der Mechanismus namens ** Pipeline **, den wir als nächstes sehen werden.

Vorverarbeitung von Pipeline-Daten

↓ Verarbeitung

Before


    Dataset<Row> dataset1 = materialIndexer.fit(dataset).transform(dataset);
    Dataset<Row> dataset2 = shapeIndexer.fit(dataset).transform(dataset1);
    Dataset<Row> dataset3 = brandIndexer.fit(dataset).transform(dataset2);
    Dataset<Row> dataset4 = shopIndexer.fit(dataset).transform(dataset3);

Sie können es mit *** Pipeline *** als ↓ umschreiben.

After


    Pipeline pipeline = new Pipeline()
        .setStages(new PipelineStage[] { materialIndexer, shapeIndexer, brandIndexer, shopIndexer });// (1)

    PipelineModel pipelineModel = pipeline.fit(dataset);// (2)

    Dataset<Row> indexedDataset = pipelineModel.transform(dataset);// (3)

** (1) ** ・ ・ ・ Erstellen Sie ein *** Pipeline *** -Objekt wie dieses und beschreiben Sie *** StringIndexer *** in der Reihenfolge, in der Sie die Verarbeitung mit *** setStages *** ausführen möchten. Ich werde. Durch einfaches Anordnen auf diese Weise können diese Prozessreihen als Gruppe als *** Pipeline *** ausgeführt werden.

** (2) ** ・ ・ ・ *** Holen Sie sich das PipelineModel mit der Pipeline # fit ***. Zu diesem Zeitpunkt verwende ich immer noch nur *** StringIndexer ***, daher habe ich keine Verarbeitung zum Lernen hinzugefügt, aber wenn die Verarbeitung zum Lernen enthalten ist, kann ich sie mit der *** fit () *** -Methode *** erhalten PipelineModel *** bezeichnet ein Trainingsmodell, das auf dem angegebenen Datensatz basiert.

** (3) ** Pipeline ・ ・ *** Wenn die PipelineModel # -Transformation *** ausgeführt wird, wird eine Reihe von *** StringIndexer *** -Prozessen gleichzeitig ausgeführt, und als Ergebnis wird ein neuer Datensatz erstellt. Dies bedeutet auch, dass das Trainingsmodell auf den angegebenen Datensatz angewendet wird, wenn die Pipeline eine Verarbeitung für das Training enthält.

Der Quellcode und die Ausführungsergebnisse sind unten aufgeführt.


import org.apache.spark.ml.Pipeline;
import org.apache.spark.ml.PipelineModel;
import org.apache.spark.ml.PipelineStage;
import org.apache.spark.ml.feature.StringIndexer;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;

public class GBTRegressionStep02_part03 {

  public static void main(String[] args) {

    System.setProperty("hadoop.home.dir", "c:\\Temp\\winutil\\");

    org.apache.log4j.Logger.getLogger("org").setLevel(org.apache.log4j.Level.ERROR);
    org.apache.log4j.Logger.getLogger("akka").setLevel(org.apache.log4j.Level.ERROR);

    SparkSession spark = SparkSession
        .builder()
        .appName("GradientBoostingTreeGegression")
        .master("local[*]")
        .getOrCreate();

    Dataset<Row> dataset = spark
        .read()
        .format("csv")
        .option("header", "true")
        .option("inferSchema", "true")
        .load("dataset/gem_price_ja.csv");

    StringIndexer materialIndexer = new StringIndexer()
        .setInputCol("material")
        .setOutputCol("materialIndex");

    StringIndexer shapeIndexer = new StringIndexer()
        .setInputCol("shape")
        .setOutputCol("shapeIndex");

    StringIndexer brandIndexer = new StringIndexer()
        .setInputCol("brand")
        .setOutputCol("brandIndex");

    StringIndexer shopIndexer = new StringIndexer()
        .setInputCol("shop")
        .setOutputCol("shopIndex");

    Pipeline pipeline = new Pipeline()
        .setStages(new PipelineStage[] { materialIndexer, shapeIndexer, brandIndexer, shopIndexer });// (1)

    PipelineModel pipelineModel = pipeline.fit(dataset);// (2)

    Dataset<Row> indexedDataset = pipelineModel.transform(dataset);// (3)
    indexedDataset.show(10);

  }

}

Ausführungsergebnis

image.png

Das Ergebnis ist das gleiche, weil sie semantisch dasselbe tun.

Schreiben Sie den Indizierungsprozess etwas intelligenter

Nun, der Ausführungsteil des Prozesses wurde mit *** Pipeline *** aktualisiert, aber der folgende Teil ist auch etwas redundant.

    StringIndexer materialIndexer = new StringIndexer()
        .setInputCol("material")
        .setOutputCol("materialIndex");

    StringIndexer shapeIndexer = new StringIndexer()
        .setInputCol("shape")
        .setOutputCol("shapeIndex");

    StringIndexer brandIndexer = new StringIndexer()
        .setInputCol("brand")
        .setOutputCol("brandIndex");

    StringIndexer shopIndexer = new StringIndexer()
        .setInputCol("shop")
        .setOutputCol("shopIndex");

    Pipeline pipeline = new Pipeline()
        .setStages(new PipelineStage[] { materialIndexer, shapeIndexer, brandIndexer, shopIndexer });/

Dies ist eher ein Java-Schreibproblem als Spark, aber lassen Sie es uns etwas sauberer machen.

Ich habe es wie folgt umgeschrieben.

    List<String> categoricalColNames = Arrays.asList("material", "shape", "brand", "shop");// (1)

    List<StringIndexer> stringIndexers = categoricalColNames.stream()
        .map(col -> new StringIndexer()
            .setInputCol(col)
            .setOutputCol(col + "Index"))// (2)
        .collect(Collectors.toList());

    Pipeline pipeline = new Pipeline()
        .setStages(stringIndexers.toArray(new PipelineStage[0]));// (3)

** (1) ** ・ ・ ・ Setzen Sie den Namen der Kategorievariablen auf List

** (2) ** List ・ ・ *** List # stream *** wird verwendet, um *** StringIndexer *** zu generieren. In *** setOutputCol *** lautet der Spaltenname auf der Ausgabeseite ** Kategorievariablenname + "Index" ** (dh ** materialIndex, shapeIndex, brandIndex, shopIndex **). Das Verarbeitungsergebnis ist ** Liste ** von *** StringIndexer ***.

** (3) ** ・ ・ ・ Auf die Pipeline-Stufe setzen, so dass es sich um ein Array von *** StringIndexer *** handelt

Der Quellcode dieser Zeit ist also wie folgt zusammengefasst. Es war ziemlich erfrischend.


import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.spark.ml.Pipeline;
import org.apache.spark.ml.PipelineModel;
import org.apache.spark.ml.PipelineStage;
import org.apache.spark.ml.feature.StringIndexer;
import org.apache.spark.sql.Dataset;
import org.apache.spark.sql.Row;
import org.apache.spark.sql.SparkSession;

public class GBTRegressionStep02_part04 {

  public static void main(String[] args) {

    // System.setProperty("hadoop.home.dir", "c:\\Temp\\winutil\\");//for windows

    org.apache.log4j.Logger.getLogger("org").setLevel(org.apache.log4j.Level.ERROR);
    org.apache.log4j.Logger.getLogger("akka").setLevel(org.apache.log4j.Level.ERROR);

    SparkSession spark = SparkSession
        .builder()
        .appName("GradientBoostingTreeGegression")
        .master("local[*]")
        .getOrCreate();

    Dataset<Row> dataset = spark
        .read()
        .format("csv")
        .option("header", "true")
        .option("inferSchema", "true")
        .load("dataset/gem_price_ja.csv");

    List<String> categoricalColNames = Arrays.asList("material", "shape", "brand", "shop");

    List<StringIndexer> stringIndexers = categoricalColNames.stream()
        .map(col -> new StringIndexer()
            .setInputCol(col)
            .setOutputCol(col + "Index"))
        .collect(Collectors.toList());

    Pipeline pipeline = new Pipeline()
        .setStages(stringIndexers.toArray(new PipelineStage[0]));

    PipelineModel pipelineModel = pipeline.fit(dataset);

    Dataset<Row> indexedDataset = pipelineModel.transform(dataset);
    indexedDataset.show(10);

  }

}

Wenn Sie dies tun, erhalten Sie weiterhin einen Datensatz mit allen indizierten kategorialen Variablen.

StringIndexer-Quantifizierungsrichtlinie

Ich habe bereits erwähnt, dass kategoriale Variablen in Ordinalvariablen und Nominalvariablen unterteilt werden können. Welche Art von Richtlinie weist Spark's ** StringIndexer ** Indexnummern zu? Ist es?

** StringIndexer ** ordnet nach Nummerierung als Index, aber die Standardreihenfolge ist ** Häufigkeit des Auftretens **.

Zusätzlich zur ** Häufigkeit des Auftretens ** können Sie auch die ** alphabetische Reihenfolge ** angeben.

Ein Einstellungsbeispiel ist unten gezeigt.

Häufigkeit des Auftretens-absteigende Reihenfolge(Standard)


    StringIndexer materialIndexer1 = new StringIndexer()
        .setStringOrderType("frequencyDesc")
        .setInputCol("material")
        .setOutputCol("materialIndex");

Alphabetischer Reihenfolge-absteigende Reihenfolge


    StringIndexer materialIndexer2 = new StringIndexer()
        .setStringOrderType("alphabetDesc")
        .setInputCol("material")
        .setOutputCol("materialIndex");

Alphabetischer Reihenfolge-aufsteigende Reihenfolge


    StringIndexer materialIndexer3 = new StringIndexer()
        .setStringOrderType("alphabetAsc")
        .setInputCol("material")
        .setOutputCol("materialIndex");

Ich werde es diesmal nicht verwenden, aber wenn Sie die Indexreihenfolge aussagekräftig machen möchten, können Sie die Variablennamen anpassen und nach ** setStringOrderType ** sortieren.

Zusammenfassung der Apache Spark ** spark.ml ** -Pipeline-Verarbeitung

image.png

** Fortsetzung von Nächstes Mal "# 3 Trainiere mit Trainingsdaten und mache [Preisschätzungs-Engine]" **

Nächstes Mal werden wir die Daten tatsächlich trainieren und eine "Preisschätzungs-Engine" erstellen.

Recommended Posts

Einführung in das maschinelle Lernen mit Spark "Price Estimation" # 2 Datenvorverarbeitung (Umgang mit Kategorievariablen)
Einführung in das maschinelle Lernen mit Spark "Preisschätzung" # 3 Lernen wir mit Trainingsdaten und erstellen eine [Preisschätzungs-Engine]
Erste Schritte mit maschinellem Lernen mit Spark "Preisschätzung" # 1 Laden von Datensätzen mit Apache Spark (Java)
[Einführung in die Informatik Teil 3: Versuchen wir es mit maschinellem Lernen] Implementieren wir die k-Mittelungsmethode im Java-Center of data set-
[Maschinelles Lernen mit Apache Spark] Verknüpfen Sie die Wichtigkeit (Feature-Wichtigkeit) einer Baummodellvariablen mit dem Variablennamen (erklärender Variablenname).
Einführung in Spring Boot + In-Memory-Datenraster (Ereignisverarbeitung)
[Maschinelles Lernen mit Apache Spark] Sparse Vector (spärlicher Vektor) und Dense Vector (dichter Vektor)