Çoklu Doğrusallık Sorunu Çözümünde VIF
Çoklu regresyon analizinde bağımsız niteliklerin bağımlı nitelik üzerindeki etkisi incelenir. Bazı bağımsız nitelikler birbirleriyle yüksek doğrusal korelasyona sahip olduğundan bağımlı değişken üzerindeki etkisi dağılır. Modelin sadeliği va anlaşılırlığı adına bağımlı değişkene en çok etki eden az sayıda değişken ile model oluşturmak arzu edilen bir durumdur. Küçük veri analizinde kullanılan SPSS gibi paket programlar çoklu bağlantı tesptini yapabilmektedir. Ancak büyük veri dünyasında bu işler henüz o kadar olgunlaşmadı ve kolay da değil. Geçen gün Spark Scala ile çalışma yaparken benim de işim düştü çoklu bağlantı tespitine. Spark ML kütüphanesinde bir şey bulamadım. Böyle olunca ekip olarak biz de bir VIF değeri hesaplayan fonksiyon yazdık.
Az önce de bahsettiğim gibi bu yazımızda nitelikler arası çoklu bağlantıyı tespit etme yöntemlerinden biri olan Variance Inflation Factor (VIF – Varyans Enflasyon Faktörü)’den bahsedeceğiz ve Spark Scala ile basit bir örnek yapacağız. VIF’de her bir nitelik diğer niteliklerle sıra ile regresyona sokulur. bunun sebebi R2 değerini hesaplamaktadır. Çünkü VIF değerini tespit etmek için R2‘nin bilinmesine ihtiyaç vardır. R2 ile ilgili bilgilerimizi kısaca bir tazeleyelim. Veri noktalarının regresyon eğrisine uzaklıklarının, ortalamaya olan uzaklıklarına oranının 1’den çıkarılması bize R2‘yi verir, daha detaylı bilgi için buraya bakınız. VIF ise 1’in R2‘nin 1’den çıkarılmış haline bölümüdür.
İki bağımsız değişken arasındaki ilişki arttıkça VIF değeri artar, azaldıkça azalır. İlişkinin olmaması 1 alt sınır; tam ilişki ise ∞ ise üst sınırdır.
Uygulama esnasında kullanılan yazılım geliştirme ortamı, programlama dili, işletim sistemi ve sürüm bilgileri: Spark 2.1.1, YARN, Hadoop 2.6.2, Scala 2.11, Apache Zeppelin Notebook, Spark2 interpreter.
Veri Setini Yükleme
[erkan@node3 ~]$ hdfs dfs -put /home/erkan/veri_setlerim/iris.csv /user/erkan/veri_setlerim/ [erkan@node3 ~]$ hdfs dfs -ls /user/erkan/veri_setlerim
Çıktı:
-rw-r--r-- 2 erkan hadoop 4551 2018-01-09 19:12 /user/erkan/veri_setlerim/iris.csv
Iris veri setini HDFS’e aktardıktan sonra Zeppelin’e geçiyorum. Iris veri dosyasını incelediğimde başlıkların olmadığını gördüm. Bu yüzden veri yüklerken option(“header”,”true”) seçeneğini kullanmıyorum. Onun yerine load() fonksiyonu sonunda toDF() içine el ile hazırladığım nitelikler listesini parametre olarak veriyorum.
val nitelikler = Seq("sepal-length","sepal-with","pedal-length","pedal-width","label") val irisDF = spark.read.format("csv").option("inferSchema","true").load("/user/erkan/veri_setlerim/iris.csv").toDF(nitelikler:_*)
Yüklediğimiz veriye bir göz atalım:
irisDF.show(5)
Çıktı:
+------------+----------+------------+-----------+-----------+ |sepal-length|sepal-with|pedal-length|pedal-width| label| +------------+----------+------------+-----------+-----------+ | 5.1| 3.5| 1.4| 0.2|Iris-setosa| | 4.9| 3.0| 1.4| 0.2|Iris-setosa| | 4.7| 3.2| 1.3| 0.2|Iris-setosa| | 4.6| 3.1| 1.5| 0.2|Iris-setosa| | 5.0| 3.6| 1.4| 0.2|Iris-setosa| +------------+----------+------------+-----------+-----------+ only showing top 5 rows
Son olarak VIF sonuçlarını hesaplayıp yazdıran fonksiyonumuzu paylaşıyorum. Gerekli açıklamaları kodların içinde yazdığım için burada fazla bir açıklamada bulunmuyorum.
// Nitelikler arası çoklu bağlantıyı tespitedip gereksiz niteliklerin budanması için //Önce kütüphaneler import org.apache.spark.ml.regression._ import org.apache.spark.ml.feature.VectorAssembler import org.apache.spark.sql.DataFrame // Dataframe alarak içindeki her bir nitelik için VIF hesaplayan ve sonuçları yazdıran fonksiyon def printCloumnsVIF(data:DataFrame):Unit = { // Tüm sütun isimlerini listele ve hesaplamak istemediğin sütun isimlerini listeden çıkar. // (Bunlar hedef nitelik veya kategorik nitelikler olabilir mesela) val columns = data.columns.filter(_ != "label") // Sütun isimleri listesini (columns) tek tek dolaş. for (col<- columns) { // Döngüde sırası gelen nitelik haricindeki tüm nitelikler bağımsız nitelik olsun. val inputCols = columns.filter(_ != col) // Hedef değişken döngüde sırası gelen nitelik olsun val outputCol = col // Vector Assembler nesnesi oluştur val assembler = new VectorAssembler().setInputCols(inputCols).setOutputCol("features") // VectorAssembler nesnesi ile döngü sırası haricindeki tüm nitelikleri tek sütunda birleştir val assembled_df = assembler.transform(data) // Lineer regresyon nesnesi oluştur girdi nitelikler ve hedef niteliği set et. val lr = new LinearRegression().setFeaturesCol("features").setLabelCol(col) // Oluşturduğun lineer modeli eğit val model = lr.fit(assembled_df) // Eğitilen model r2 hesaplamıştı. Bu modelden r2'yi kullanarak VIF'yi hesapla ve yazdır. println(col + " için VIF değeri: " + 1/(1-model.summary.r2)) } } // Yukarıda hazırladığımız fonksiyonu burada çağırıp içine iris datasını verelim. printCloumnsVIF(irisDF)
Çıktılarımız:
import org.apache.spark.ml.regression._ import org.apache.spark.ml.feature.VectorAssembler import org.apache.spark.sql.DataFrame printCloumnsVIF: (data: org.apache.spark.sql.DataFrame)Unit sepal-length için VIF değeri: 7.1031134428332985 sepal-with için VIF değeri: 2.09903862574209 pedal-length için VIF değeri: 31.39729165071975 pedal-width için VIF değeri: 16.141563956997715
Sonuçların yorumu:
sepal-length ve sepal-with değerleri fena durmuyor. Kalan iki nitelikte aşırı bir bağlantıya sahip değilse de ilk iki niteliğe göre epey fazla. Ben olsam ilk iki niteliği tutardım. Acaba doğru mu yapardım?
Hoşçakalın…
Kapak resmi kaynak: https://www.kiplinger.com/article/business/T019-C000-S010-inflation-rate-forecast.html