Laboratorium 4: Cechy wielomianowe

Będziemy przetwarzali następujące pliki:

  • xy-002 $f_{true}=-1.5x^2+3x+4$
  • xy-003 $f_{true}=-1.5x^2+3x+4$
  • xy-004 $f_{true}=-10x^2+500x-25$
  • xy-005 $f_{true}=(x+4)(x+1)(x-3)$

Wykorzystamy kod z poprzedniego laboratorium (Regresja Liniowa)

1. Cechy wielomianowe 2 stopnia

Umieść kod w klasie LinearRegressionPolynomialFeaturesOrderTwo

Ponieważ przetwarzane są kolejne zbiory, najwygodniej będzie umieścić kod w funkcji postaci
static void processDataset(SparkSession spark,String filename,Function<Double,Double> f_true)

1. Dodaj do zbioru danych kolumnę X2 zawierającą po prostu wynik wyrażenia “X*X”

Czyli, np. dla xy-002.csv po załadowaniu i przetworzeniu danych wynikiem będzie:

+--------+----------+------------------+
|       X|         Y|                X2|
+--------+----------+------------------+
|0.411194|-59.938274|    0.169080505636|
|0.549662|-72.006761|    0.302128314244|
|0.860093|-68.979336|    0.739759968649|
| 1.27504|  32.07157|1.6257270015999998|
|2.202931|-91.531894|    4.852904990761|
+--------+----------+------------------+
only showing top 5 rows

root
 |-- X: double (nullable = true)
 |-- Y: double (nullable = true)
 |-- X2: double (nullable = true)

2. Zmień konfigurację VectorAssembler tak, aby budował wektory na podstawie kolumn [“X”, “X2”]

3. Zbuduj model regresji

4. Wyświetl wyniki. Przy wyświetlaniu aproksymowanej funkcji wywoływana jest funkcja lrModel.predict(). Jako argument musisz dostarczyć wektor zawierający zarówno wartość wejściową, jak i jej kwadrat. Możesz to zrealizować dodają parametr order do funkcji plot (przyda się przy regresji 3-stopnia) lub po prostu dodając element.

5. Przetwórz wszystkie pliki i wyświetl ich wykresy oraz w każdym przypadku podaj równanie regresji

Niewątpliwie ciekawym wykresem jest dopasowanie wielomianu 2-stopnia do danych wygenerowanych z wielomianu 3 stopnia

ale bardziej zastanawiający jest przypadek xy-004.csv

6. Popraw dopasowanie (jest to możliwe)

Przeanalizuj parametry algorytmu. Na przykład:

        LinearRegression lr = new LinearRegression()
                .setMaxIter(10)
                .setRegParam(0.3)
                .setElasticNetParam(0.8)
                .setFeaturesCol("features")
                .setLabelCol("Y");

Które z nich można zmienić? Sprawdź, co się stanie, gdy

  • usuniemy regularyzację
  • zwiększymy liczbę iteracji
  • Porównaj metryki, zamieśc wykresy

Uwagi:

  • usuwanie regularyzacji na ogół (dla niesyntetycznych zbiorów danych) psuje zdolność generalizacji
  • zwiększenie liczby iteracji dla dużych zbiorów danych proporcjonalnie zwiększa czas uczenia

2. Cechy wielomianowe 3 stopnia

Umieść kod w klasie LinearRegressionPolynomialFeaturesOrderThree

1. Podobnie, jak w poprzednim przypadku dodaj kolumnę X3 z wartościami kolumny X podniesionymi do 3 potęgi

2. Zmodyfikuj odpowiednio VectorAssembler

3. Przeprowadź regresję (dla wszystkich zbiorów danych)

4. Zamieść wykres xy-005.csv

5. Porównaj w tabelce wartości miary r2 i MSE dla regresji 2 i 3 stopnia (dla wszystkich zbiorów danych)

  • Sprawdź, czy w przypadku regresji 3-stopnia dla danych wygenerowanych z funkcji kwadratowej współczynniki przy $x^3$ były zerowe?
  • Z reguły niewielkie zwiększenie współczynnika regularyzacji i liczby iteracji poprawia dopasowanie dla krzywych 2 stopnia

3.Pipeline

Kod umieść w klasie LinearRegressionPolynomialFeaturesPipeline

Ponieważ przetwarzane są kolejne zbiory, najwygodniej będzie umieścić kod w funkcji postaci
static void processDataset(SparkSession spark,String filename,int degree,Function<Double,Double> f_true)

Dodawanie manualne cech jako kolumn zbioru danych jest zbyt żmudne - zachodzi konieczność nazywania tych kolumn, później modyfikacji kodu w kilku miejscach. Raczej buduje się ciąg przetwarzania: Pipeline, którego elementami są wstępne przetwarzanie danych i budowa modelu estymatora.

Rozszerzenie cech o cechy wielomianowe jest dokonywane za pomocą odpowiedniej funkcji transformującej dane tablicowe : PolynomialExpansion.

Budowa ciągu przetwarzania

Kolejne etapy przetwarzania to:

  • zastosowanie VectorAssembler
  • PolynomialExpansion
  • LinearRegression

Zamiast wykonywać je natychmiast utwórz i skonfiguruj odpowiednie obiekty

 VectorAssembler vectorAssembler=new VectorAssembler()
                .setInputCols(new String[]{"X"})
                .setOutputCol("features");
 
//        Dataset<Row> df_trans = vectorAssembler.transform(df);
 
        PolynomialExpansion polyExpansion = new PolynomialExpansion()
                .setInputCol("features")
                .setOutputCol("polyFeatures")
                .setDegree(degree);
 
//        df_trans = polyExpansion.transform(df_trans);
 
        LinearRegression lr = new LinearRegression()
        ...

a następnie zdefinuj ciąg przetwarzania Pipeline i wywołaj jego metodę fit()

Pipeline pipeline = new Pipeline()
                        .setStages(new PipelineStage[] {vectorAssembler, polyExpansion, lr});
PipelineModel model = pipeline.fit(df);

Aby uzyskać dostęp do konkretnego elementu ciagu - nalezy po prostu zrzutować wybrany element

LinearRegressionModel lrModel = (LinearRegressionModel)model.stages()[2];

Funkcja do rysowania wykresów

Zmodyfikujemy sygnaturę funkcji.

     *
     * @param x - współrzedne x danych
     * @param y - współrzedne y danych
     * @param pipelineModel - model pipeline
     * @param spark - sesja Spark
     * @param title - tytuł do wyswietlenia (może być null)
     * @param f_true - funkcja f_true (może być null)
     */
    static void plot(List<Double>x, List<Double> y, PipelineModel pipelineModel, SparkSession spark, String title, Function<Double,Double> f_true)

W odróżnieniu od funkcji używanej dotychczas - przekazujemy do niej pipelineModel. Wykorzystamy go, aby wyświetlić przebieg funkcji wyznaczonej w wyniku regresji.

1. Załóżmy, że mamy punkty, dla których mamy wyznaczyć wartości funkcji zgromadzone na liście fx

var xdelta = 0.05*(xmax-xmin);
var fx = NumpyUtils.linspace(xmin-xdelta,xmax+xdelta,100);

Aby przetworzyć je za pomocą pipeline musimy zamienić je na obiekt typu Dataset<Row> zawierający kolumnę o nazwie X z odpowiednimi wartościami.

2. Zamiana wartości double na Row

Przeiteruj przez elementy fx i dla każdego z nich utwórz obiekt Row, np. wołając Row r = RowFactory.create(d); lub umieść takie odwzorowanie w odpowiedniej funkcji strumienia. Zbierz wynik w List<Row> rows

3. Zdefiniuj schemat i utwórz zbiór danych:

StructType schema = new StructType().add("X", "double");
 
Dataset<Row> df_test =  spark.createDataFrame(rows,schema);
df_test.show(5);
df_test.printSchema();

Wynik:

+--------------------+
|                   X|
+--------------------+
|          -2.0917333|
| -1.5355272333333332|
| -0.9793211666666666|
|-0.42311509999999974|
| 0.13309096666666687|
+--------------------+
only showing top 5 rows

3. Wywołaj funkcję transform() obiektu pipeline

Dataset<Row> df_pred = pipelineModel.transform(df_test);
df_pred.show(5);
df_pred.printSchema();

W wyniku jej wykonania do zbioru zostaną dodane kolejno kolumny features, polyfeatures i prediction zgodnie z kolejnymi etapami przetwarzania

+--------------------+--------------------+--------------------+-------------------+
|                   X|            features|        polyFeatures|         prediction|
+--------------------+--------------------+--------------------+-------------------+
|          -2.0917333|        [-2.0917333]|[-2.0917333,4.375...| -33.40167605867543|
| -1.5355272333333332|[-1.5355272333333...|[-1.5355272333333...|-29.392038747489657|
| -0.9793211666666666|[-0.9793211666666...|[-0.9793211666666...| -26.24979833578732|
|-0.42311509999999974|[-0.4231150999999...|[-0.4231150999999...|-23.976336940609087|
| 0.13309096666666687|[0.13309096666666...|[0.13309096666666...| -22.57303667899562|
+--------------------+--------------------+--------------------+-------------------+
only showing top 5 rows

root
 |-- X: double (nullable = true)
 |-- features: vector (nullable = true)
 |-- polyFeatures: vector (nullable = true)
 |-- prediction: double (nullable = false)

4. Należy oczywiście wyświetlić zawartość kolumny prediction

4. Podział na zbiór uczący i testowy

Rozwiązanie 1

1. Po załadowaniu zbioru do podziel go korzystając z funkcji limit i offset:

        long rowsCount = df.count();
        int trainCount = (int)(rowsCount*.7);
        var df_train = df.select("*").limit(trainCount);
        var df_test = df.select("*").offset(trainCount);
        System.out.println(df_train.count());
        System.out.println(df_test.count());

2. Zamień wywołania w kodzie tak, aby przetwarzany i wyświetlany był zbiór df_train

3. Dodaj wyświeltanie df_test:

        x = df_test.select("X").as(Encoders.DOUBLE()).collectAsList();
        y = df_test.select("Y").as(Encoders.DOUBLE()).collectAsList();
        plot(x,y,model,spark,String.format("Linear regression: %s (test data)",filename),f_true);

4. Dodaj kod odpowiedzialny za obliczanie metryk na zbiorze testowym

  • wpierw przetrannsformuj go
  • następnie oblicz metryki korzystając z kolumn Y i prediction
        var df_test_prediction = model.transform(df_test);
        RegressionEvaluator evaluator = new RegressionEvaluator()
                .setLabelCol("Y")
                .setPredictionCol("prediction")
                .setMetricName("rmse"); // or any other evaluation metric
        double rmse = evaluator.evaluate(df_test_prediction);
        evaluator.setMetricName("r2");
        double r2 = evaluator.evaluate(df_test_prediction);

5. Wydrukuj wartości metryk i obejrzyj wykresy

Podziały przetestuj na plikach

  • xy-003.csv dla cech wielomianowych 3 stopnia.
  • xy-005.csv dla cech wielomianowych 2 stopnia

Zapisz rysunki i metryki.

Rozwiązanie 2

Zamień sposób podziału na losowy (np. zakomentuj poprzedni kod lub utwórz kopię klasy).

var dfs = df.randomSplit(new double[]{0.7,0.3});
var df_train = dfs[0];
var df_test = dfs[1];

1. Przetestuj takie same konfiguracje zbiorów danych i stopni wielomianu

Podział jest losowy. Może się zdarzyć, że w danym losowaniu osiągniemy nie najlepszy podział Można oczywiście ustawić seed i otrzymać powtarzalne wyniki (np. seed =3 wydaje się całkiem dobry).

2. Wróć do poprzedniego rozwiązania Stały podział może być całkiem dobry, jeżeli wcześniej dane zostały losowo pomieszane. Jest to częste podejście przy publikacji zbiorów testowych.

Dodaj przed podziałem następujący kod i porównaj wyniki:

df = df.orderBy(org.apache.spark.sql.functions.rand(3));

3. Zbierz w tabelce metryki dla wszystkich sposobów ustalania podziału

ed/lab_04.txt · Last modified: 2024/03/21 02:25 by pszwed
CC Attribution-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0