Anasayfa / Büyük Veri / Apache Spark ML Kütüphanesi: Pipelines Örnek Uygulama

Apache Spark ML Kütüphanesi: Pipelines Örnek Uygulama

1. Giriş

Merhabalar. Bildiğimiz gibi Spark, büyük veri dünyasının en popüler analitik motoru. Özellikle durağan büyük boyutlu veriler (persistent data) üzerinde hızlı bir şekilde makine öğrenmesi algoritmalarını çalıştırabilmesi Spark’ı farklı kılan özelliklerin başında geliyor. Arkadaşımız o kadar yetenekli ki sadece durağan verileri işlemiyor, aynı zamanda akan verileri de işleyebiliyor. Spark’ın makine öğrenmesi için bir kütüphanesi var. Aslında iki kütüphanesi var. Birisi eski RDD tabanlı ve geliştirilmesine son verilen, ikincisi dataframe tabanlı ve geliştirilmeye devam edilen. Spark 2.0 ile birlikte geliştiricileri ML kütüphanesini scikit-learn kütüphanesinden esinlenerek yeniden yarattılar. Tablovari verinin yaygınlığı sebebiyle dataframe tabanlı olan yeni ML kütüphanesi geliştirilmeye devam ediliyor. Umarım yakın zamanda scikit-learn kadar olgunlaşır. Bu arada scikit-learn kütüphanesini de Spark üzerinde dağıtık olarak kullanabilmek mümkünmüş. Ancak ben henüz denemedim, denersem tecrübelerimi paylaşabilirim.

Makine öğrenmesi denince birçok insan hemen model aşamasına odaklanır. Aslında makine öğrenmesi, bir çok safhanın ardışık olarak sıralandığı bir süreçtir. Bu süreç içerisinde modeli oluşturmak için kullanılan algoritmaların bulunduğu model safhası haricinde, veri ön işleme gibi başka aşamalar da vardır. Sanılanın aksine makine öğrenmesi, sadece makineye ham maddeyi verip bacak bacak üstüne atıp sonra da ürünün arkadan çıkmasını beklemekten ibaret değildir.

Önce problem anlaşılır, bu problemin çözümü için verinin nerelerde bulunduğu ve nasıl temin edileceği araştırılır. Veri okunur, bütünleştirilir, eksiği gediği giderilir, keşfedici analizler yapılır, nitelikler arası ilişkilere bakılır. Veri hazırlığı aşamasında kategorik değişkenler indekslenir, varsa metin (text) halindeki niteliklerin içinden sayısal nitelikler alınır. Daha sonra probleme uygun model seçilir, model değerlendirilir, ayarlamalar ve düzeltmeler yapılır. Gerekirse defalarca model tekrar eğitilir, değerlendirilir ve ayar çekilir. İşin özü aslında makine öğrenmesi süreci biraz karmaşıktır. Şayet süreç sadece aşağıdaki gibi basit olsaydı sorun yoktu.

Ancak gerçekte süreç daha karmaşıktır:

 

Bu karmaşıklıkla daha iyi başa çıkabilmek, belli bir düzen içerisinde makine öğrenmesi sürecini yürütmek ve veri bilimcinin işini kolaylaştırmak için ML (yeni) kütüphanesi olaya biraz daha geniş pencereden bakmış ve makine öğrenmesi sürecindeki diğer safhalarda da veri bilimcilerin işini kolaylaştırmaya çalışmış. İşte bu yazımızda ağırlıklı olarak bu konuyu yani Spark ML  Pipeline kavramını ele alacağız. 4. bölüme kadar olan bölüm alıştırma ve işin altında yatan mantığı öğrenme aşamasıdır. Asıl iş; “Parçaları Pipeline ile Birleştirmek” bölümündedir. Buraya kadar olan bilgi ve uygulamalar 4. Bölümde yapılanları daha iyi anlayabilmek içindir. Şayet düzgün bir Pipeline kurabilirseniz işte o zaman bacak bacak üstüne atma muhabbetini bir nebze olsun yapabilirsiniz 🙂

1.1. Spark ML ve Pipeline ile ilgili Temel kavramlar

Pipeline’ı daha iyi anlayabilmek için ML kütüphensinde kullanılan bazı kavramların ne anlama geldiğini bilmemiz gerekiyor.

Transformer: Bir dataframe’in başka bir dataframe’e dönüştürür. Dönüşüm transform() metoduyla gerçekleşir. Bazen girdi olan dataframe bir kaç ilave sütun ile çıktı olarak üretilir. Girdi sütunlarının tek bir sütun halinde vector formatına dönüştürülmesi veya bir makine öğrenmesi modelinin bir test dataframe’i alıp çıktı olarak bir tahmin seti üretmesi Transformer’a örnek verilebilir.

Estimator: Girdi olarak veri alır ancak çıktı olarak bir Transformer üretir. Dönüşüm fit() metoduyla gerçekleşir. Örneğin bir öğrenme algoritması eğitim verisi ile eğitilir ve çıktı olarak model üretir. LogisticRegression bir Estimator’dür ve fit() metoduyla LogisticRegressionModel‘i eğitir.

Evaluator: Eğitim verisi ile eğitilen modelin test verisi ile uyumluluğunun değerlendirilmesi için kullanılan metriktir.BinaryClassificationEvaluator Evaluator için örnek verilebilir.

Pipeline: Muhtelif Estimator ve Transformator’lerden oluşan bir zincirdir. Pipeline safha safha ilerler, her bir safhada bir Transformator ya da Estimator olabilir. Bu safhalara PipelineStage denir. PipelineStage, belli bir dizilime sahiptir, yani sıralı bir dizidir (Array). Dataframe, her bir PipelineStage’den geçerken dönüşüme uğrar. Pipeline doğrusal olabileceği gibi döngüsel olmayan yönlendirilmiş diyagram (Directed Acyclic Graph (DAG)) ile doğrusal olmayan bir Pipeline oluşturulabilir. Her PipelineStage akış içinde bir kez yer alabilir. PipelineModel ise Pipeline nesnesine Transformator ve Estimator’ların yerleştirilmiş halidir.

Parameter: Tüm Transformer ve Estimator’lar artık parametre belirlemek için ortak bir API kullanıyor. Parametreleri algoritmalara göndermek için iki yöntem vardır: Birincisi model nesnesinin fonksiyonlarıyla. Örneğin LogisticRegression nesnesi lr olsun, lr.setMaxIter(10) ile 10 iterasyon parametresi model nesnesine verilmiş olur. İkinci yöntem ise parametreyi fit() veya trasform() metodları içinde göndermek. ParamMap  içinde gönderilen parametreler setter ile daha önceden belirlenmiş olanları ezer. Şayet iki ayrı LogisticRegression nesnemiz varsa ve isimlerini lr1 ve lr2 vermişsek,  ParamMap içinde maxIter özelliği ile farklı iterasyon sayısı verebiliriz: ParamMap(lr1.maxIter -> 10, lr2.maxIter -> 20).

Teorik bilgi ile daha fazla sıkılmadan hemen uygulamaya geçelim. 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.

2. Veriyi Okumak ve Anlamak

ICS UCI den veri setlerimi Windows ana makineme indirdim. Oradan WinSCP ile Hadoop Cluster Edge Sunucumun lokal diskinde /home/erkan/veri_setlerim/adult/ dizinine kopyaladım. Daha sonra aşağıdaki hdfs komutları ile veri setlerini HDFS’e taşıdım. Olası bir yetki hatasına karşın hem lokal dizinde hem HDFS’de aynı kullanıcı ile işlem yaptım.

2.1. Veriyi anlamak:

Veri seti eğitim ve test olarak iki parçaya ayrılmış durumda. Yani bununla biz uğraşmayacağız. Haklarında farklı demografik bilgiler bulunan insanlardan derlenmiş veri setinde kişilerin gelirinin ellibin dolardan büyük olup olmadığı da etiketlenmiş. Bize verilen niteliklerden öğrenme yaparak yeni bir bireyin gelirinin 50K’dan büyük olup olmadığını bulmamız isteniyor. Bu veri seti makine öğrenmesi eğitimlerinde sıklıkla kullanılır. O yüzden belki daha önce karşılaşmışsınızdır. Biz bu problemi Spark ML Pipeline kullanarak çözmeye çalışacağız.

2.2. Veriyi Yüklemek:

Yüklerken şemayı algılasın diye option("inferSchema","true") seçeneğini kullanıyoruz.

2.3. Nitelik İsimlerini Değiştirmek, Şemayı Anlamak:

Başlıkları dahil etmedim çünkü indirdiğim dosyaları incelediğimde ilk satırlarda başlık bilgisi yoktu. Ben de o yüzden indirdiğim yerden veri seti niteliklerinden bir sequence liste oluşturdum ve içine sırasıyla nitelik isimlerini yazdım:

Şimdi bu listeyi kullanarak mevcut dataframe sütun isimlerini komple değiştirelim ve yenibir dataframe adresleyelim. Bildiğimiz gibi Spark’ta dataframe immutable olduğundan her dönüşüm ve değişikliği farklı bir dataframe ismiyle tutmalıyız.

Şemamızı yazdıralım ve nitelkler hakkında biraz daha bilgi edinelim:
Yukarıdaki 14 niteliği görüyoruz ve şimdi veriyi daha iyi anlamaya başladık. Muhtemelen string olanlar kategorik, double ve integer olanlar ise sürekli niteliklerdir.

2.4. Veri Seti Hakkında Temel İstatistiksel Bilgiler:

Satır sayısına bakalım:

Nümerik niteliklerin temel istatistiklerine bakalım:

Kategorik değişkenler ile hedef değişken gelir arasındaki ilişkiye çapraz tablo ile bakalım:

Kadın ve erkeklerin 50K’nın üzerinde kazanma sayıları:

Erkekler sanki daha çok kazanıyor gibi.
Mesleklere göre gelir durumu:

Yönetici tayfasıyla profesyonel meslek erbabı 50’yi daha çok aşmayı başarmış. Özel ev hizmetleri (neyse?) hemen hemen hepsi 5oK’nın altında kalmış garibanlar.

Gelir seviyesini gruplayalım bakalım kaç kişi 50K’nın altında kaç kişi üstünde:

50K’nın altında kazananlar yaklaşık üç kat daha fazla. Buradan kabaca insanların dörtte birinin 50K üzerinde, kalan dörtte üçünün ise 50K altında gelire sahip olduğunu söyleyebiliriz. Bu oran niye önemli? Şimdi biz kimin 50K’nın üzerinde geliri olduğunu tahmin etmeye çalışıyoruz. İki tane sonuç var: 50K’dan yüksek veya değil. Şayet sallasak ve hepsine 50K’dan düşük desek zaten %75’ini doğru bileceğiz. O yüzden bizim makine öğrenmesi sonucu daha yüksek doğruluk değerlerine ulaşmamız lazım ki yaptığımız işin bir anlamı olsun. Yani bu problemde %75 doğruluk başarısı düşük bir başarı.

3. Veri Hazırlığı

Bu aşamada veri temizliği, etiket indeksleme (label indexing) ve kategorik nitelikler için string indeksleme (string indexing), oneHot Encoding.

3.1. Veri Temizliği

Veri seti içinde null değer var mı bakalım, varsa o satırı komple çıkaralım. Aslında bu basit ancak bazen yanlış bir çözüm olabilir. Burada konumuz eksik değerleri tamamlamak olmadığı için ben temizlik adı altında sadece null veya aykırı değerleri bulup ilgili satırı çıkaracağım.

3.2. Null Değer İçeren Nitelikleri Bulma

Sanırım dataframe içindeki null değer içeren sütunları bulan hazır bir fonksiyon yok. Varsa da ben bilmiyorum. Hal böyle olunca iş başa düştü ve ben de basit bir programlama ile null içeren sütunları bulayım dedim. Şöyle yapmayı düşünüyorum.

  • Dataframe sütun isimlerini nitelikler isminde bir listeye aktar.
  • sayac adında bir değişken belirle ve başlangç değeri olarak 1 ata.
  • Liste içinde for döngüsü ile dolaş.
  • Dolaşırken her bir sütun null içeriyor mu kontrol et ve say. Sayının sıfırdan büyük olup olmadığını if ile kontrol et.
  • Eğer null varsa sayı sıfırdan büyüktür yani ilgili sütun null içeriyordur.
  • Sütun ismini ve nul içerip içermediğini yazdır.

Nitelikleri listeye aktarma:

Sayac ve for döngüsü:Nitelikler listei içini nitelik ile dolaşıyoruz. Her turda ilgili niteliği isNull ile filtreleyip sayıyoruz.

Yukarıdaki kodun çıktısı:

Gördüğümüz gibi yukarıdaki 14 nitelik ve bir hedef nitelik olmak üzere toplam 15 sütunun hiçbirinde null değer yok.
Soru işareti olan değerleri bulma:
Buradada yukarıda null değere benzer bir süreci takip ediyoruz.

Yukarıdaki kodun çıktısı:

Null değerlerin aksine  bilinmeyen anlamına gelen ? bazı sütunlarda var. ? içeren nitelikler: workclass, occupation ve native-country. Şimdi ? işareti olan satırları dataframe dışı bırakalım. Bunun için de filter kullanacağız. Ancak bu sefer filter önüne not koyacağız ve içeriğin tersini filtreleyecek yani ? bulunmayanları süzecek.

Elde ettiğimiz ?’den arındırılmış yeni dataframe’i saydıralım:

Yazı başında veri setini saydırmıştık ve sonuç 32561 çıkmıştı. Yeni dataframe sayısı 30162, demekki 2399 satırda ? varmış.

3.3. Hedef Değişken Indeksleme (Label Indexing)

Hedef değişkenimizde iki farklı değer var; “>50K” ve “<=50K”.  Bunun için StringIndexer kullanacağız. StringIndexer bu iki niteliği sayacak ve en çok tekrarlanana 0 diğerine 1 verecek. Bu durumda “<=50K” 1, “>50K” 0 olacak. Çünkü ilkinin tekrar sayısı daha yüksek. Hadi kodlamaya başlayalım:

spark.ml.feature.StringIndexer sınıfını indirmemiz gerekir:import org.apache.spark.ml.feature.StringIndexer
Bu sınıftan bir nesne yaratalım ve adına labelIndexer diyelim: val labelIndexer = new StringIndexer() Daha sonra bu nesnenin metodlarını kullanarak input ve output sütun isimlerini belirleyelim: labelIndexer.setInputCol("salary") ve labelIndexer.setOutputCol("salary_index"). Şimdide labelIndexer nesnesinin fit() metodu ile maasDF3 dataframe salary niteliğine uyumlandıralım (İşin özü aslında “<=50K” 1, “>50K” 0 yapmak).

labelIndexer.fit(maasDF3) ile modeli eğitip yeni bir değişkene atıyoruz: labelIndexerTransformer ve bununla da maasDF’ü dönüştürüyor ve maasDF3Transformed adında yeni bir dataframe’e atıyoruz.

Belediğimiz gibi “<=50K” 1.0, “>50K” 0.0 olmuş.

3.4. Kategorik Nitelik Ön İşleme: StringIndexer ve OneHotEncoder

Elimizdeki veri setinde toplam 14 nitelik var. Bunlardan bazıları nümerik bazıları ise kategorik. Genel olarak makine öğrenmesi algoritmaları kategorik niteliklerden hoşlanmazlar, bunun yerine nümerik nitelik ile çalışmayı severler. Bu veri setinde kategorik nitelikler; workclass, education, maritial_status, occupation, relationship, race, sex ve native-country. Bir kategorik nitelik için işlem yapalım, bu en baştaki workclass olsun, yani özel sektörde mi, kamu da mı yoksa kendi işinde mi çalıştığı bilgilerini içeren nitelik. Bu sefer kodları tek seferde yazıp açıklamaya çalışacağım.

Kütüphaneyi indirdik ve takip eden üç satırda yeni bir StringIndexer nesnesi oluşturduk ve girdi, çıktı sütun isimlerini belirledik. Beşinci satırda stringIndexer nesnesinin fit() metodu ile modelimizi eğittik ve maasDF3Indexed değişkenine atadık. Bu arada fit() metoduna parametre olarak en güncel dafaframe olan maasDF3‘ü veriyoruz. Dataframe’ler immutable olduğundan her değişiklikte farklı bir isim ile tutmak zorundayız. Biraz kafa karıştırıcı olabilir ancak buna alışmak lazım. Altıncı satırda workclass niteliğindeki benzersiz değerler liste halinde yazdırılıyor (bilgi amaçlı). Son satırda stringIndexerTransformer nesnesi transform() metoduyla workclass_index adında yeni bir sütun eklenmiş yeni dataframe oluşturup maasDF3Indexed adı altında tutuyoruz.
Şimdi oneHotEncoder ile yukarıda oluşturduğumuz workclass_index niteliğini işleyelim.

İlk satırda OneHotEncoder nesnesi oluşturduk. İkinci ve üçüncü satırda girdi ve çıktı için nitelik adı belirledik. Üçüncü satırda oneHotEncoder nesnesi transform() metoduna son dataframe olan maasDF3Indexed‘i parametre verdik. Dönüştürme sonunda yeni bir nitelik daha eklendiğinden yeni dataframe’i oneHotEncodedDF adıyla tuttuk. Son satırda üzerinde işlem yapıyor olduğumuz workclass niteliği orjinal sütun, stringIndexer ve OneHotEncoder ile dönüştürülmüş sütünları seçip show() ile görüyoruz. Yukarıdaki şekil ile anlatılmak isteneni burada liste halinde görebiliyoruz.

3.5. Vector Assembler

Spark ML kütüphanesi girdi olarak tüm nitelikleri bir sütun altında vektör türünde ister. Nasıl olur bu peki, formatı nedir? Bunu az önce son olarak oluşan oneHotEncodedDF üzerinden basitçe anlatalım. oneHotEncodedDF içinden workclass sütunu ile ilgili dönüşümü gösteren tüm yeni sütunları seçip göstermiştik.
Bu dönüşüm nasıl oldu ve en son niteliğin formatı hakkında konuşalım.
1. StringIndexer sınıfı ile workclass kategorik niteliğini aşağıdaki şekilde görülen rakamlarla eşleştirdik.

Aşağıda bir satırdan workclass ile ilgili dönüşüme uğramış üç sütunu görüyoruz. Yukarıdaki eşleşmede State-gov’un 3.0 ile eşleştiğini göstermiştik. Aşağıda ise workclass_index niteliğinde bu değeri görüyoruz. workclass_onehotindex sütunu ise vector ve workclass sütununu içindeki State-gov değerini rakamlarla gösteriyor.

 

Şema yazdırarak workclass_onehotindex niteliğinin türünün vector olduğunu görelim.

Gördüğümüz gibi workclass_onehotindex sütununun türü vector.

Şayet bu işi normal niteliklerle yapsaydık aşağıdaki şekle benzer yedi tane daha ilave nitelik (workclass’daki farklı değer sayısınca) koyacak ve her bir satırda sadece biri 1.0 olacak diğerleri ise 0.0 olacaktı. Yani seyrek matris oluşturmak zorunda kalacaktık. İşte vector türü tüm nitelikleri rakamlarla ve tek bir sütun içinde ifade etmemize olanak sağlıyor. one hot denmesinin sebebi de sadece bir tanesinin sıcak olması yani 1.0 değeri almasıdır.

4. Parçaları Pipeline ile Birleştirmek

Tüm kategorik nitelikleri dönüştürmek, bu arada yeni dataframe’in hangisi olduğunu takip etmek, oluşan yeni nitelikler hangisi isimleri neydi, eskilere ne oldu vb. işleri takip etmek gerçekten süreci karmaşık hale getiriyor. Pipeline ise bu noktada imdada yetişerek işleri kolaylaştırıyor. Veri seti yükleme ve temizliği ile ilgili fonksiyonlar aşağıdadır.

Şimdi sıra kategorik nitelikleri vector türüne dönüştürme işlemleri
Lojistik Regresyon modelini oluşturup eğitelim, test edelim ve değerlendirelim:

Hakkında Erkan ŞİRİN

GÖZ ATMAK İSTEYEBİLİRSİNİZ

Big Data Eğitimi

Büyük veri dünyasının yıldızı Apache Hadoop’u herkesin anlayabileceği basit ve sade bir yaklaşımla anlatıyoruz. Kendi kendinize öğrenirken …

2 yorumlar

  1. Türkçe için güzel bir kaynak olmuş.

  2. Merhaba,

    AUC tam olarak ne oluyor. Örneğin ben tek bir satır veri göndermek istiyorum. Bu eğitilmiş veriye bakarak workclass’ nı belirlemek istiyorum. Cevap verirseniz müteşşekkir olurum.

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir