This is an old revision of the document!


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

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.

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, najlepiej umieść kod w funkcji postci 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 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 odpowiednią funkcje 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, zbierz metryki.

Rozwiązanie 2

Zamień sposób podziału na losowy (np. zakomentuj poprzedni kod).

var dfs = df.randomSplit(new double[]{0.7,0.3});
var df_train = dfs[0];
var df_test = dfs[1];
ed/lab_04.1710094545.txt.gz · Last modified: 2024/03/10 19:15 by pszwed
CC Attribution-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0