Anasayfa / Büyük Veri / Apache Spark K-Ortalamalar Tekniği ile Bilgisayar Ağlarında Anormallik Tespiti Bölüm 1/3

Apache Spark K-Ortalamalar Tekniği ile Bilgisayar Ağlarında Anormallik Tespiti Bölüm 1/3

Merhabalar. Bu yazımızda Apache Spark ML kullanarak K-Ortalamalar yöntemi ile anormallik tespiti yapmaya çalışacağız. Gün geçmiyorki makine öğrenmesi farklı bir alanda kullanılmıyor olmasın. Bu alanların arasında anormallik tespiti de var. Anormal, kelimenin temel ve en basit anlamıyla normal olmayan demek. Eğer eldeki veride hangi kayıtların anormal olduğuna dair bir etiket bulunsaydı, bu bilgiden hareketle denetimli öğrenme (supervised learning) yaparak etiketsiz kayıtları tahmin edebilirdik. Ancak çoğu zaman hangi kayıtların normal hangilerinin anormal olduğuna dair bir bilgi, bir etiket bulunmaz. Bu durumda anormal kayıtları tespit etmek için denetimsiz öğrenme (unsupervised learning) kullanmak gerekir.

Denetimsiz öğrenme yöntemleri arasında en yaygın olanı K-Ortalamalar tekniğidir. K-Ortalamalar tekniğinde her bir nesne, veri bilimci tarafından belirlenen k sayısı kadar kümeye ayrılır. Optimal k sayısını belirlemek bu yöntemin en incelikli noktasıdır. İkinci olarak nesnelerin gruplanması için nesneler arasındaki mesafenin ölçülme işlemi vardır. Nümerik değerler için genelde bu ölçüm öklid mesafesi ile ölçülür. Bu teknikte her bir küme bir nokta tarafından temsil edilir (küme merkezi-centroid), aslında olmayan bir nokta tarafından. Çünkü bu nokta kümeye dahil olan nesnelerin merkezini gösteren farazi bir noktadır ve kümeye eleman giriş çıkışı oldukça değişir. K-Ortalamalar tekniği başlamak için veri bilimciden küme sayısını ister. Kendisi bu küme sayısı kadar rastgele küme merkezleri seçer. Daha sonra her bir noktanın bu küme merkezlerine olan mesafesi hesaplanır ve kendisine en yakın kümeye dahil edilir. Daha sonra küme merkezleri yeni küme elemanlarına göre tekrar hesaplanır. Sonra tüm noktaların tüm küme merkezlerine olan mesafesi tekrar hesaplanır ve en yakın küme merkezinin olduğu kümeye dahil edilir. Yani ilk hesaplamada dahil olduğu kümeden başka bir kümeye geçebilir, çünkü küme merkezleri değişti. Bu işlem tekrarlanır, ta ki herhangi bir hareket olmayana kadar. Aşağıda basit bir gösterimle bu mantık anlatılmaya çalışılmıştır.




Veriyi Anlamak

Veri seti 1999 ylında KDD Cup yarışmasında kullanılmış bilgisayar ağına ait bağlantı kayıtlarıdır. Csv formatındadır. Yaklaşık 708 MB büyüklüğündedir. 4.9 milyon bağlantı ile ilgili 38 farklı nitelik bulunmaktadır (4.9 miyon satır 38 sütun). Niteliklerden bazıları: gönderilen bayt mikterı, oturum açma teşebüs sayısı, TCP hataları vb.

Veriyi Yüklemek

kddcup.data.gz veri setini ana bilgisayara (Windows 10) indirdik. Buradan edge sunucumuz olan node3‘ün Linux CentOS lokal diskinde /home/erkan/veri_setlerim/kdd_cup dizinine taşıdık (bunun için WinSCP kullandım). Ben Spark’ı Hadoop YARN ile birlikte kullandığım için veri setini edge sunucudan (node3) HDFS’e taşıdım. HDFS dizinini de node3 lokal dizinine benzer bir şekilde seçtim. Veriyi node3 kenar sunucudan HDFS’e aktarma ile ilgili hdfs komutları aşağıdadır:

[erkan@node3 ~]$ hdfs dfs -mkdir /user/erkan/veri_setlerim/kdd_cup
[erkan@node3 ~]$ hdfs dfs -put /home/erkan/veri_setlerim/kdd_cup/kddcup.data /user/erkan/veri_setlerim/kdd_cup
[erkan@node3 ~]$ hdfs dfs -ls /user/erkan/veri_setlerim/kdd_cup                                           
Found 1 items
-rw-r--r--   2 erkan hadoop  742579829 2017-12-30 21:56 /user/erkan/veri_setlerim/kdd_cup/kddcup.data

Şimdi Zeppelin Notebookta Scala interpreter paragrafı açalım ve verimizi bir dataframe olarak yükleyelim. Veriyi indirdikten sonra Notepad++ ile inceledim ve sütun isimlerimizin olmadığını farkettim. Nitelik isimlerini kddcup.names dosyasından öğrendim ve bir listede topladım, adına colNames dedim. En son nitelik ismi olarak label niteliğini ekledim.

val colNames = Seq("duration", "protocol_type", "service", 
"flag", "src_bytes", "dst_bytes", "land", "wrong_fragment", 
"urgent", "hot", "num_failed_logins", "logged_in", "num_compromised", 
"root_shell", "su_attempted", "num_root", "num_file_creations", 
"num_shells", "num_access_files", "num_outbound_cmds", "is_host_login", 
"is_guest_login", "count", "srv_count", "serror_rate", "srv_serror_rate", 
"rerror_rate", "srv_rerror_rate", "same_srv_rate", "diff_srv_rate", 
"srv_diff_host_rate", "dst_host_count", "dst_host_srv_count", 
"dst_host_same_srv_rate", "dst_host_diff_srv_rate", 
"dst_host_same_src_port_rate", "dst_host_srv_diff_host_rate", "dst_host_serror_rate", 
"dst_host_srv_serror_rate", "dst_host_rerror_rate", "dst_host_srv_rerror_rate","label")

Veriyi yükleyelim ve yüklerken de niteliklerimizi verelim:

val data = spark.read.format("csv").
                      option("header","false").
                      option("inferSchema","true").
                      load("/user/erkan/veri_setlerim/kdd_cup/kddcup.data").toDF(colNames:_*)
// Komut çıktısı
data: org.apache.spark.sql.DataFrame = [duration: int, protocol_type: string ... 40 more fields]

Veri Keşfi

Kaç satırımız var bakalım:

dataWithoutHeader.count()
res3: Long = 4898431

başlangıçta da belirttiğimiz gibi yaklaşık 4.9 milyon satır var.

Şema yazdıralım:

data.printSchema()
root
 |-- duration: integer (nullable = true)
 |-- protocol_type: string (nullable = true)
 |-- service: string (nullable = true)
 |-- flag: string (nullable = true)
 |-- src_bytes: integer (nullable = true)
 |-- dst_bytes: integer (nullable = true)
 |-- land: integer (nullable = true)
 |-- wrong_fragment: integer (nullable = true)
 |-- urgent: integer (nullable = true)
 |-- hot: integer (nullable = true)
 |-- num_failed_logins: integer (nullable = true)
 |-- logged_in: integer (nullable = true)
 |-- num_compromised: integer (nullable = true)
 |-- root_shell: integer (nullable = true)
 |-- su_attempted: integer (nullable = true)
 |-- num_root: integer (nullable = true)
 |-- num_file_creations: integer (nullable = true)
 |-- num_shells: integer (nullable = true)
 |-- num_access_files: integer (nullable = true)
 |-- num_outbound_cmds: integer (nullable = true)
 |-- is_host_login: integer (nullable = true)
 |-- is_guest_login: integer (nullable = true)
 |-- count: integer (nullable = true)
 |-- srv_count: integer (nullable = true)
 |-- serror_rate: double (nullable = true)
 |-- srv_serror_rate: double (nullable = true)
 |-- rerror_rate: double (nullable = true)
 |-- srv_rerror_rate: double (nullable = true)
 |-- same_srv_rate: double (nullable = true)
 |-- diff_srv_rate: double (nullable = true)
 |-- srv_diff_host_rate: double (nullable = true)
 |-- dst_host_count: integer (nullable = true)
 |-- dst_host_srv_count: integer (nullable = true)
 |-- dst_host_same_srv_rate: double (nullable = true)
 |-- dst_host_diff_srv_rate: double (nullable = true)
 |-- dst_host_same_src_port_rate: double (nullable = true)
 |-- dst_host_srv_diff_host_rate: double (nullable = true)
 |-- dst_host_serror_rate: double (nullable = true)
 |-- dst_host_srv_serror_rate: double (nullable = true)
 |-- dst_host_rerror_rate: double (nullable = true)
 |-- dst_host_srv_rerror_rate: double (nullable = true)
 |-- label: string (nullable = true)

protocol_type, service, flag, src_bytes ve label nitelikleri string, muhtemelen kategoriktir. Şimdi buları seçip bir bakalım gerçekten kategorik mi yoksa inferSchema() nitelik türünü iyi belirleyememiş mi? Bu niye bu kadar önemli? Spark ML kütüphanesindeki hemen hemen tüm algoritmalar girdi olarak nümerik değerler ister, bu sebeple kategorik nitelikleri tespit edip onlarda gerekli dönüştürme işlemlerini yapmalıyız. K-Ortalamalar algoritması da girdi olarak nümerik değerler istiyor.

Kategorik nitelikleri ve label değişkeni seçerek tablo formatında görelim. Mümkün olduğu kadar farklı değerleri görebilmek adına sorguya distinct() fonksiyonunu ekliyorum.

data.select("protocol_type","service","flag","label").distinct().show(truncate=false)
+-------------+-------+----+----------+
|protocol_type|service|flag|label     |
+-------------+-------+----+----------+
|tcp          |exec   |RSTO|portsweep.|
|tcp          |private|RSTR|portsweep.|
|tcp          |finger |SF  |ipsweep.  |
|tcp          |netstat|SH  |nmap.     |
|tcp          |imap4  |SH  |imap.     |
|icmp         |ecr_i  |SF  |normal.   |
|tcp          |telnet |SF  |perl.     |
|tcp          |mtp    |REJ |satan.    |
|tcp          |supdup |RSTR|portsweep.|
|tcp          |domain |REJ |satan.    |
|tcp          |private|S0  |portsweep.|
|tcp          |domain |S0  |normal.   |
|tcp          |supdup |REJ |portsweep.|
|tcp          |Z39_50 |REJ |satan.    |
|tcp          |uucp   |SF  |satan.    |
|tcp          |domain |S0  |portsweep.|
|tcp          |sql_net|REJ |satan.    |
|tcp          |discard|SF  |satan.    |
|tcp          |telnet |S3  |satan.    |
|tcp          |X11    |S0  |satan.    |
+-------------+-------+----+----------+
only showing top 20 rows

Evet beklediğimiz gibi inferSchema() nitelik türlerini doğru ve başarılı bir şekilde belirlemiş.
Biz her ne kadar kümeleme yapacak olsak da aslında veri seti içinde bağlantının mahiyetiyle ilgili bir etiket var ve bu label niteliğinde belirtilmiş. Şimdi kaç çeşit etiket var bakalım ve her etiketten kaç satır var görelim. Bunun için groupBy() ve count() kullanmamız gerekecek.

data.select("label").groupBy("label").count().orderBy($"count".desc).show(25)
+----------------+-------+
|           label|  count|
+----------------+-------+
|          smurf.|2807886|
|        neptune.|1072017|
|         normal.| 972781|
|          satan.|  15892|
|        ipsweep.|  12481|
|      portsweep.|  10413|
|           nmap.|   2316|
|           back.|   2203|
|    warezclient.|   1020|
|       teardrop.|    979|
|            pod.|    264|
|   guess_passwd.|     53|
|buffer_overflow.|     30|
|           land.|     21|
|    warezmaster.|     20|
|           imap.|     12|
|        rootkit.|     10|
|     loadmodule.|      9|
|      ftp_write.|      8|
|       multihop.|      7|
|            phf.|      4|
|           perl.|      3|
|            spy.|      2|
+----------------+-------+

Toplamda 23 farklı etiket var.

Veri Temizliği

Veri setinde null değer var mı kontrol edelim. Ben bunun için pahalı bir yöntem kullanıyorum. Ne yapıyorum? Tüm sütunları bir for döngüsü ile dolaşarak null kontrolü yapıyor ve sayıyorum. Sonuç sıfırdan büyük ise o sütunda null var anlamına geliyor.

var sayac = 1
for (col <- colNames){ if(data.filter(data.col(col).isNull).count() > 0){
        println(sayac+". "+col + " içinde null değer var.")
    }else{
        println(sayac+". "+col + " içinde null değer yok.")
    }
     sayac+=1
}
//Sonuçlar
sayac: Int = 1
1. duration içinde null değer yok.
2. protocol_type içinde null değer yok.
3. service içinde null değer yok.
4. flag içinde null değer yok.
5. src_bytes içinde null değer yok.
6. dst_bytes içinde null değer yok.
7. land içinde null değer yok.
8. wrong_fragment içinde null değer yok.
9. urgent içinde null değer yok.
10. hot içinde null değer yok.
11. num_failed_logins içinde null değer yok.
12. logged_in içinde null değer yok.
13. num_compromised içinde null değer yok.
14. root_shell içinde null değer yok.
15. su_attempted içinde null değer yok.
16. num_root içinde null değer yok.
17. num_file_creations içinde null değer yok.
18. num_shells içinde null değer yok.
19. num_access_files içinde null değer yok.
20. num_outbound_cmds içinde null değer yok.
21. is_host_login içinde null değer yok.
22. is_guest_login içinde null değer yok.
23. count içinde null değer yok.
24. srv_count içinde null değer yok.
25. serror_rate içinde null değer yok.
26. srv_serror_rate içinde null değer yok.
27. rerror_rate içinde null değer yok.
28. srv_rerror_rate içinde null değer yok.
29. same_srv_rate içinde null değer yok.
30. diff_srv_rate içinde null değer yok.
31. srv_diff_host_rate içinde null değer yok.
32. dst_host_count içinde null değer yok.
33. dst_host_srv_count içinde null değer yok.
34. dst_host_same_srv_rate içinde null değer yok.
35. dst_host_diff_srv_rate içinde null değer yok.
36. dst_host_same_src_port_rate içinde null değer yok.
37. dst_host_srv_diff_host_rate içinde null değer yok.
38. dst_host_serror_rate içinde null değer yok.
39. dst_host_srv_serror_rate içinde null değer yok.
40. dst_host_rerror_rate içinde null değer yok.
41. dst_host_srv_rerror_rate içinde null değer yok.
42. label içinde null değer yok.

Çok güzel. Hiç bir nitelikte null değer yok. Bu yazıda bukadar. Bölüm-2 ile devam edeceğiz.

Hakkında Erkan ŞİRİN

2014'ten beri hem akademik alanda hem de sektörde pratik anlamda büyük veri ve veri bilimi ile ilgili çalışmalar yürütmektedir. Halihazırda İmpektra Bilişim A.Ş.'de büyük veri yöneticisi olarak çalışmakta olup aynı zamanda Gazi Üniversitesi Yönetim Bilişim Sistemleri doktora öğrencisidir. Büyük veri ve veri bilimi ile ilgili birçok kurum ve şirkete eğitimler vermekte ve projeler icra etmektedir. Çalışma alanları: büyük veri platformlarının kurulum ve yönetimi, büyük veri üzerinde makine öğrenmesi, olağan dışılık tespiti, sahtecilik tespiti, veri hazırlama sürecidir.

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

Pandas Dataframe’i MongoDB’ye Yazmak

Merhabalar bu yazımızda Python Pandas Dataframe verisini Python kullanarak MongoDB’ye yazacağız. MongoDB ile etkileşime geçmek …

Bir cevap yazın

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