This is an old revision of the document!
Table of Contents
Regresja logistyczna
Celem jest budowa modelu regresji logistycznej pozwalającej przewidywać, czy dany student zdał egzamin z języka C++ w pierwszym terminie. Implementujemy oprogramowanie w języku Java.
Dla przypomnienia
- Regresja logistyczna jest metodą klasyfikacji binarnej.
- Posługuje się pojęciem szansy $o=\frac{p}{1-p}$
- Modeluje powiązanie prawdopodobieństwa etykiety 1 z modelem liniowym jako $logit(p) = ln(\frac{p}{1-p})=w^T x$
- Po przekształceniach: prawdopodobieństwo etykiety 1 opisane jest wzorem $p=\frac{1}{1+exp(-w^T x)}$
Patrz: wykład 4
Zbiory danych
- egzamin-cpp.csv - zanonimizowane wyniki zaliczeń/egzaminu z C i C++ w 2016 roku
- grid.csv - kombinacje ocen
egzamin-cpp.csv
ImieNazwisko;OcenaC;DataC;OcenaCpp;Egzamin Dqhoil Dhxpluj;3.5;2016-01-14;4;3 Bhnhgpxj Lwjmq;4.5;2016-01-14;4;3 Wkgjnerme Djfbw;4;2016-01-20;3;2 Sredvmuwt Tcimknl;4.5;2016-01-20;4.5;3.5 Tiowe Bqoilnqbrx;4;2016-01-14;4.5;3 Bvaysqv Wuyih;3.5;2016-01-14;5;3 Jjoaxp Ktapcy;5;2016-01-20;4;3.5
grid.csv
ImieNazwisko,OcenaC,DataC,OcenaCpp 'Xxxxx Yyyyyy',3,2016-01-17,2 'Xxxxx Yyyyyy',3,2016-01-17,3 'Xxxxx Yyyyyy',3,2016-01-17,3.5 'Xxxxx Yyyyyy',3,2016-01-17,4 'Xxxxx Yyyyyy',3,2016-01-17,4.5 'Xxxxx Yyyyyy',3,2016-01-17,5 'Xxxxx Yyyyyy',3.5,2016-01-17,2 'Xxxxx Yyyyyy',3.5,2016-01-17,3 'Xxxxx Yyyyyy',3.5,2016-01-17,3.5 'Xxxxx Yyyyyy',3.5,2016-01-17,4 'Xxxxx Yyyyyy',3.5,2016-01-17,4.5 'Xxxxx Yyyyyy',3.5,2016-01-17,5 'Xxxxx Yyyyyy',4,2016-01-17,2 'Xxxxx Yyyyyy',4,2016-01-17,3 'Xxxxx Yyyyyy',4,2016-01-17,3.5 'Xxxxx Yyyyyy',4,2016-01-17,4 'Xxxxx Yyyyyy',4,2016-01-17,4.5 'Xxxxx Yyyyyy',4,2016-01-17,5 'Xxxxx Yyyyyy',4.5,2016-01-17,2 'Xxxxx Yyyyyy',4.5,2016-01-17,3 'Xxxxx Yyyyyy',4.5,2016-01-17,3.5 'Xxxxx Yyyyyy',4.5,2016-01-17,4 'Xxxxx Yyyyyy',4.5,2016-01-17,4.5 'Xxxxx Yyyyyy',4.5,2016-01-17,5 'Xxxxx Yyyyyy',5,2016-01-17,2 'Xxxxx Yyyyyy',5,2016-01-17,3 'Xxxxx Yyyyyy',5,2016-01-17,3.5 'Xxxxx Yyyyyy',5,2016-01-17,4 'Xxxxx Yyyyyy',5,2016-01-17,4.5 'Xxxxx Yyyyyy',5,2016-01-17,5
1. Ładowanie danych i przetwarzanie wstępne
1. Utwórz sesję Sparka i załaduj zbiór danych. Wyświetl zawartość i schemat
SparkSession spark = SparkSession.builder() .appName("LogisticRegressionOnExam") .master("local") .getOrCreate(); ...
root |-- ImieNazwisko: string (nullable = true) |-- OcenaC: double (nullable = true) |-- DataC: date (nullable = true) |-- OcenaCpp: double (nullable = true) |-- Egzamin: double (nullable = true)
2. Regresja logistyczna wymaga, aby atrybutów wejściowe były typu numerycznego. Jets też metodą klasyfikacji binarnej (etykiety powinny mieć wartości 0 i 1)
- przekonwertuj datę za pomocą funkcji
unix_timestamp
- nadaj nowej kolumnie nazwętimestamp
- Dodaj kolumnę
Wynik
będącą wynikiem testu, czyEgzamin>=3.0
- użyj funkcji SQL IF()
+-----------------+------+----------+--------+-------+----------+-----+ | ImieNazwisko|OcenaC| DataC|OcenaCpp|Egzamin| timestamp|Wynik| +-----------------+------+----------+--------+-------+----------+-----+ | Dqhoil Dhxpluj| 3.5|2016-01-14| 4.0| 3.0|1452726000| 1| | Bhnhgpxj Lwjmq| 4.5|2016-01-14| 4.0| 3.0|1452726000| 1| | Wkgjnerme Djfbw| 4.0|2016-01-20| 3.0| 2.0|1453244400| 0| |Sredvmuwt Tcimknl| 4.5|2016-01-20| 4.5| 3.5|1453244400| 1| | Tiowe Bqoilnqbrx| 4.0|2016-01-14| 4.5| 3.0|1452726000| 1| | Bvaysqv Wuyih| 3.5|2016-01-14| 5.0| 3.0|1452726000| 1| | Jjoaxp Ktapcy| 5.0|2016-01-20| 4.0| 3.5|1453244400| 1| | Mkengbtw Aainhh| 3.5|2016-01-20| 3.0| 2.0|1453244400| 0| | Fbffjb Muupwshu| 4.0|2016-01-14| 5.0| 4.0|1452726000| 1| | Yahwfyp Bvnlsig| 5.0|2016-01-14| 4.5| 4.0|1452726000| 1| +-----------------+------+----------+--------+-------+----------+-----+
2. Analiza działania algorytmu
2.1 Budowa modelu i interpretacja współczynników
1. Skonfiguruj algorytm i zbuduj model
LogisticRegression lr = new LogisticRegression() .setMaxIter(100) .setRegParam(0.1) .setElasticNetParam(0) .setFeaturesCol("features") .setLabelCol("Wynik"); LogisticRegressionModel lrModel = lr.fit(df);
2. Napisz kod, który drukuje równanie regresji logistycznej
Oczekiwany wynik:
logit(zdal) = 0.719097*OcenaC + -0.000000*timestamp + 0.993461*OcenaCPP + 118.340611
3. Zinterpretuj współczynniki równania regresji (napisz kod lub zamieść wykonane obliczenia). Pamiętaj, że timestamp jest wyrażony w sekundach.
Poniższe wyniki były wygenerowane za pomocą kodu. W praktyce wynik nie zależy od daty…
Wzrost OcenaC o 1 zwiększa logit o 0.719097, a szanse zdania razy 2.052578 czyli o 105.257821% Wzrost DataC o 1 dzień zwiększa logit o -0.000000,a szanse zdania razy 0.992648 czyli o -0.735167% Wzrost OcenaCPP o 1 zwiększa logit o 0.719097,a szanse zdania razy 2.700564 czyli o 170.056381%
2.2 Predykcja i jej wyniki
1. Wywołaj funkcję predykcji i wyświetl dane…
Dataset<Row> df_with_predictions=lrModel.transform(df_trans); var df_predictions = df_with_predictions .select("features","rawPrediction","probability","prediction");
2. Napisz funkcję, która wyświetli informacje dotyczące predykcji
private static void analyzePredictions(Dataset<Row> dfPredictions,LogisticRegressionModel lrModel) { dfPredictions.foreach(new ForeachFunction<Row>() { ... }); }
Wewnątrz:
- oblicz wartośc
logit
jako iloczyn skalarny współczynników i cech powiększony olrModel.intercept()
- Oblicz prawdopodobieństwo P(0) i P(1) z odpowiedniego wzoru - wykorzystując wartośc logit
- Wyświetl i porównaj wartości
rawPrediction
iprawdopodobieństwa
- Wyświetl prawdopodobieństwo wybranej przez klasyfikator etykiety - czyli większe z prawdopodobieństw
- Funkcja
row.getAs(String)
zwraca element w danej kolumnie. Użyj też funkcjiVector.argmax()
2.3 Dodaj do zbioru prawdopodobieństwo wybranej etykiety
Zadaniem jest dodanie do zbioru danych kolumny prob
zawierającej wartość prawdopodobieństwa przypisanego etykiecie. Musimy wyodrębnić je z atrybutu probability
. W tym celu użyjemy mechanizmu User Defined Function (UDF). UDF to funkcja użytkownika rozszerzająca interfejs funkcjonalny Sparka. Funkcja musi zostać zarejestrowana w sesji.
1. Dodaj klasę zagnieżdżoną:
static class MaxVectorElement implements UDF1<Vector,Double> { @Override public Double call(Vector vector) throws Exception { return // największy element wektora - czyli o indeksie vector.argmax() } }
2. Zarejestruj funkcję (zazwyczaj bezpośrednio po utworzeniu sesji)
spark.udf().register( "max_vector_element",new MaxVectorElement(),DataTypes.DoubleType);
Alternatywnym rozwiązaniem może być rejestracja funkcji jako wyrażenia lambda, bez deklarowania klasy
UDF1<Vector,Double> mve = v-> ???; spark.udf().register( "max_vector_element_alt",mve,DataTypes.DoubleType);
3. Przekonwertuj zbiór z wynikami predykcji. Usuń kolumny typu features
i rawPredictions
Dodaj kolumnę prob
z rezultatami wywołania UDF
.withColumn("prob",callUDF("max_vector_element",col("probability")))...
Oczekiwany wynik:
+-----------------+------+----------+--------+-------+----------+-----+----------+------------------+ | ImieNazwisko|OcenaC| DataC|OcenaCpp|Egzamin| timestamp|Wynik|prediction| prob| +-----------------+------+----------+--------+-------+----------+-----+----------+------------------+ | Dqhoil Dhxpluj| 3.5|2016-01-14| 4.0| 3.0|1452726000| 1| 1.0|0.6822140478017863| | Bhnhgpxj Lwjmq| 4.5|2016-01-14| 4.0| 3.0|1452726000| 1| 1.0| 0.815034643789244| | Wkgjnerme Djfbw| 4.0|2016-01-20| 3.0| 2.0|1453244400| 0| 1.0|0.5214319094648453| |Sredvmuwt Tcimknl| 4.5|2016-01-20| 4.5| 3.5|1453244400| 1| 1.0|0.8738590749460415| | Tiowe Bqoilnqbrx| 4.0|2016-01-14| 4.5| 3.0|1452726000| 1| 1.0| 0.834828781680339| +-----------------+------+----------+--------+-------+----------+-----+----------+------------------+
2.4 Zapisz zbiór z wynikami
df_predictions = df_predictions.repartition(1); df_predictions.write() .format("csv") .option("header", true) .option("delimiter",",") .mode(SaveMode.Overwrite) .save("????/egzamin-with-classification.csv");
Instrukcja df_predictions = df_predictions.repartition(1);
jest opcjonalna. Jaka będzie postać wyjścia, kiedy zmienimy argument repartition
- np. ustawimy 5.