Kısıtlı Gruplama (CONSTRAINED CLUSTERING)

Bu yazıda çok atıf alan, yıllar önce geliştirilmesinde katkıda bulunduğum eski bir makalenin R’da farklı uygulamaları hakkında yazacağım. “Constrained K-Means Clustering” isimli, Paul Bradley ve danışmanım Kristin Bennett ile 2000 yılında NIPS konferansı için yazdığımız, fakat kabul alamadığımız sonrasında Microsoft Teknik Raporu olarak yayınlanan makalemiz. Makalenin orjinaline şu adresten ulaşabilirsiniz. Bu makale çok önemli bir çığır açmıştı. Sonrasında konu must-link, cannot-link kısıtlarıyla yarı-eğitmenli öğrenme (semi-supervised learning) konusu ile ilişkilendirilmişti. Bu konu ile ilgili 2008 yılında “Using Assignment Constraints to Avoid Empty Clusters in K-Means Clustering” isimli bir kitap bölümü kaleme almıştım. Yine bu makale Paul Bradley ve Kristin Bennett ile beraber yazılmıştı. Bu kitap bölümüne şu adresten ulaşabilirsiniz.

Elbette önerdiğimiz modeller hep matematik programlama model bazlı olduğu için Matlab ve IBM ILOG CPLEX’de uygulanmıştı, fakat R’da uygulanma fırsatını bulamamıştık. Geçen yıllar içinde yarı-eğitmenli öğrenme konusunda yapılan yayınlar, yöntemin R’da bir paket geliştirilmesine yol açmıştır. Bu yazımızda R’da conclust paketinin kullanma örneğini özgün bir şekilde ve özgün bir veri seti üzerinde yapacağız. conclust paketinin yardım dokümanına şu adresten kolaylıkla erişebilirsiniz.

Öncelikle veri setimiz hakkında bir açıklama yapmak istiyorum. 2007-2010 yılları arasında Tankut Atan, Ufuk Kula ve Gürdal Ertek Hocalarla birlikte tamamladığım TÜBİTAK 1001 projemizde kullandığımız ve elde ettiğimiz verileri burada tekrar sizlerin paylaşımına sunacağım. Bu yazıda paylaşılan veriler ve kod için GitHub sayfası hazırlanmış olup, altta paylaşılmıştır. GitHub sayfasından kolaylıkla indirebilirsiniz.

Türk hazır giyim sanayi için veri madenciliği tabanlı bir kalıcı indirim yönetim sistemi prototipi” isimli TÜBİTAK projemizde amacımız birliktelik analizi (association mining) ile pozitif ve negatif ilişkileri olduğunu bulduğumuz ürünler için indirim oranlarının optimizasyonunu sağlamaktı. Veri setimiz bir hazır giyim perakendecisinin tüm sezon satışlarını ilgilendirdiği için pozitif ilişki kombin, negatif ilişki ikame anlamında kullanılabilir.

Pozitif ve negatif ilişkilerin bulunmasında elbette ham satış verileri yani satış fişi (fatura) verileri kullanılabilir. Ancak bu yazımızı ilgilendiren konu grublama ve özel olarak kısıtlı gruplamadır. Dolayısıyla ürünlerin satış grafiklerine göre benzerlikleri bizi ilgilendirebilir. Must-link (aynı grup – pozitif) ve Cannot-link (farklı grup – negatif) kısıtlar satış grafiklerinin üzerine ek bilgidir. Burada incelenen veriler ilgili yılın yaz sezonuna ait olup dokuzuncu ve kırkikinci haftalar arası satış rakamları ile oluşturulmuş bir veri kümesidir. Her bir ürün için satışlar tek satır halinde verilmiştir. Dolayısıyla her satırda urunkod ID değişkenin yanında 34 haftalık satış rakamları bulunmaktadır. Bazı ürünlerin yaz sezonuna erken girerken bazılarının geç çıktığına dikkatinizi çekerim.

Örnek veri seti Excel dosyası olarak verilmiştir. Urunkod’u alfa-nümerik bir alandır. conclust paketi matris şeklinde veri kabul etmektedir. Gruplanması istenen objelerin ID’leri bulundukları satır olarak kabul edilmektedir. Ayrı bir ID değişkeni kullanılmamaktadır.

İlk yapmamız gereken ilgili Excel dosyasındaki verilerin R’da okunmasıdır. Bu amaçla aşağıdaki kod parçası ile 3 farklı worksheet’teki veriler sırasıyla okunabilir. view fonksiyonu verilerin düzgün okunup okunmadığını anlamanız için kontrol amaçlı kullanabileceğiniz bir fonksiyon. Elbette başlangıçta kullanacağımız paketler yüklenmiştir. readxl ve sqldf paketlerinin daha önce kurulduğunu varsaydım.

library(readxl)
library(sqldf)
install.packages('conclust')
library(conclust)
ConsClusData <- read_excel("ConsClusOrnekVeri.xlsx", 
                                sheet = "Data")
View(ConsClusData)

posLink <- read_excel("ConsClusOrnekVeri.xlsx", 
                           sheet = "Pozitif")

negLink <- read_excel("ConsClusOrnekVeri.xlsx", 
                      sheet = "Negatif")

Veriler R ortamına alındıktan sonra önişleme gerekmektedir. Hatırlarsanız conclust paketinde kullanılan algoritmaların ID değişkeni gerektirmediğini iletmiştim. Bu durum aynı grup ve farklı grup kısıtlarını ifade ederken satır numaralrını kullanmamızı gerektiriyor yani 1 ve 2 nolu satırda bulunan veriler aynı grupta olması gerekiyorsa must-link (aynı grup) kısıt kümesinde ” 1 2″ diye kayıt oluşması gerekiyor. Bu yüzden urunkod ile verilmiş ilişkilerin satır numaralarına dönüşmesi gerekiyor. Bu amaçla sqldf paketini kullanmayı düşündüm. Bazı işlerin SQL’de yapılması gerçekten çok basit. Elbette ana veri dosyamızda satıi verilerinin urunkod yerine yeni bir ID değişkeni ile eşleştirilmesi gerekti.

ConsClusData$ID<-1:600

join_string1 <- "select
                posLink.*
              , ConsClusData.ID as ID1
              from posLink
                join ConsClusData
                on posLink.item1 = ConsClusData.urunkod"
ara_join1 <- sqldf(join_string1, stringsAsFactors = FALSE)

join_string2 <- "select
                ara_join1.ID1
              , ConsClusData.ID as ID2
              from ara_join1
                join ConsClusData
                on ara_join1.item2 = ConsClusData.urunkod"
pos_join <- sqldf(join_string2, stringsAsFactors = FALSE)

join_string1 <- "select
                negLink.*
              , ConsClusData.ID as ID1
              from negLink
                join ConsClusData
                on negLink.item1 = ConsClusData.urunkod"
ara_join1 <- sqldf(join_string1, stringsAsFactors = FALSE)

join_string2 <- "select
                ara_join1.ID1
              , ConsClusData.ID as ID2
              from ara_join1
                join ConsClusData
                on ara_join1.item2 = ConsClusData.urunkod"
neg_join <- sqldf(join_string2, stringsAsFactors = FALSE)

rm(ara_join1)

Yukarıdaki kod parçasında öncelikle ana verisetinde ID değişkeni oluşturulmuştur. Pozitif ilişki (aynı grup) ve negatif ilişki (fark grup) kısıtlarının ayrı ayrı iki adımda yapıldığı kolaylıkla fark edilebilir. (ITEM1, ITEM2) diye genel anlamda ilişki kısıtı bulunmaktadır. ITEM1 ve ITEM2‘de bulunan urunkod değerleri karşılık gelen satır numaraları ile ID değişkeni üzerinden değiştirilmiştir. İki adımlı iki farklı JOIN işlemi ile pos_join ve neg_join verisetleri sırasıyla must_link ve cannot_link setleri olarak elde edilmiştir. En son satırda gereksiz veri silinmiştir. Join işlemleri sonucunda elde edilen satır sayısı orjinal kısıt setleri ile uyuşmamıştır. Çünki gruplama setinde kullanılan 600 veri ile sınırlandırılmıştır. Yani bu 600 verinin içine olmayan ürün kodlarına ait kısıtlar da bulunmaktadır. Burada gruplama seti 600 ile bir veri hazırlama aşamasında sınırlandırılmış olamlı. Yani yıllar önce orjinal veri üzerinde çalışıldığında.

Artık conclust R paketinde bulunan farklı gruplama algoritmaları kullanılabilir. Bu pakette 4 farklı algoritma uygulanmıştır. Bunlar alfabetik sırayla ccls, ckmeans, lcvqe ve mpckm algoritmalarıdır. Bu algortimalar hakkında conclust paketinin yardım dosyasında bazı açıklamalar bulunmaktadır. Genel olarak ilgili fonksiyonlar çağrıldığında sırasıyla ana veriseti, aynı grup kısıt ve farklı grup kısıt kümeleri ayrıca max_iter limiti verilebilir. Aşağıdaki kod parçası farklı algoritmaları çağırmakta ve sonuçları ana verisetinde yeni tahmin değişkeni olarak kaydetmektedir. Burada orjinal verisetine tahmin edilen grupları eklememizin sebebi sonuçları başka bir yazıda karşılaştırabilmektir.

clustData<-ConsClusData[,c(2:35)]

pred<-mpckm(clustData, 5, pos_join, neg_join, maxIter = 13)
pred
ConsClusData$mpckm_pred<-pred

pred<-ckmeans(clustData, 5, pos_join, neg_join, maxIter = 13)
pred
ConsClusData$ckmeans_pred<-pred

pred<-lcvqe(clustData, 5, pos_join, neg_join, maxIter = 13)
pred
ConsClusData$lcvqe_pred<-pred

pred<-ccls(clustData, 5, pos_join, neg_join, maxIter = 13)
pred
ConsClusData$ccls_pred<-pred

Burada herhangi bir sebep olmadan 5 grup bulunmaya çalışılmıştır. Aslında maxIter parametresi her algoritmada gerekmemektedir. Algoritmaların çalışma zamanları farklılık göstermektedir. Yukarıda ilk satırda orjinal verinin ID değişkenleri olmadan sadece gruplanacak veri elde edilmiş ve algoritmaya girdi olarak verilmiştir. ckmeans algoritması tüm verileri aynı gruba atamıştır. Bu algoritmanın istendiği gibi çalışmadığına işaret etmektedir. Çünki birbirleriyle çatışan kısıtlar olabilir. Yani kirli bir kısıt seti.

Bir sonraki yazımızda sonuçları analiz etmeye çalışacağız. Tüm koda ve Excel dosyasına GitHub sayfasından ulaşabilirsiniz.