CSDKursNotlari/YapayZeka-MakineOgrenmesi-VeriBilimi-OzetNotlar-Ornekler.txt

28668 lines
1.5 MiB
Text
Raw Normal View History

#----------------------------------------------------------------------------------------------------------------------------
Yapay Zeka ve Makine öğrenmesi
UNIX/Linux Sistem Programalama Kursu
Sınıfta Yapılan Örnekler ve Özet Notlar
Eğitmen: Kaan ASLAN
Bu notlar Kaan ASLAN tarafından oluşturulmuştur. Kaynak belirtmek koşulu ile her türlü alıntı yapılabilir.
(Notları okurken editörünüzün "Line Wrapping" özelliğini pasif hale getiriniz.)
Son Güncelleme: 30/03/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
1. Ders - 23/12/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Tanışma ve kursun tanıtımı yapıldı.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2. Ders - 24/12/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Python gözden geçirmesine başlandı.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
3. Ders - 06/01/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Python gözden geçirmesine devam edildi.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
4. Ders - 07/01/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Python gözden geçirmesine devam edildi.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
5. Ders - 13/01/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Python gözden geçirmesine devam edildi.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
6. Ders - 14/01/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Python gözden geçirmesine devam edildi.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
7. Ders - 20/01/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
NumPy kütüphanesinin gözden geçirilmesine başlandı.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
8. Ders - 21/01/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
NumPy kütüphanesinin gözden geçirilmesine devam edildi ve Pandas kütüphanesinin gözden geçirilmesine başlandı.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
9. Ders - 27/01/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Kurumuzun ilk bölümünde zeka, yapay zeka, öğrenme, makine öğrenmesi ve veeri bilimi kavramlarının ne anlama geldiğini
ıklayacağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Zeka kapsamı, işlevleri ve yol açtığı sonuçları bakımından karmaşık bir olgudur. Zekanın ne olduğu konusunda psikologlar
ve nörobilimciler arasında tam bir fikir birliği bulunmamaktadır. Çeşitli kuramcılar ve araştırmacılar tarafından zekanın
çeşitli tanımları yapılmıştır. Bu kuramcılar ve araştırmacıların bazıları yaptıkları tanımdan hareketle zekayı ölçmek için
çeşitli araçlar da geliştirmeye çalışmışlardır.
Charles Spearman zekayı "g" ve "s" biçiminde iki yeteneğin birleşimi olarak tanımlamıştır. Spearman'a göre "g" faktörü akıl
yürütme ve problem çözmeyle ilgili olan "genel zekayı" belirtir. "s" faktörü ise müzik, sanat, iş yaşamı gibi özel alanlara
yönelik "spesifik zekayı" belirtmektedir. İnsanlar "zeka" denildiğinde daha çok genel zekayı kastetmektedirler.
Howard Gardner tarafından kuramsal hale getirilen "çoklu zeka (multiple intelligence)" zeka teorisinde Gardner zekayı
"sözel/dilbilimsel (verbal/linguistic), müzikal (musical), mantıksal/matematiksel (logical/mathematical), görsel uzamsal
(visual/spatial), kinestetik (kinesthetic), kişilerarası (interpersonal), içsel (intrapsersonal)" olmak üzere başlangıçta yedi
türe ayırmıştır. Sonra bu yedi türe "doğasal (naturalistic), varoluşsal (existentialist)" biçiminde iki tür daha ekleyerek
dokuza çıkarmıştır.
Robert Sternberg'e göre ise "analitik (analytical), pratik (practical) ve yaratıcı (creative)" olmak üzere üç tür zeka vardır.
Analitik zeka problemi parçalara ayırma, analiz etme ve çözme ile ilgili yetileri içermektedir. Bu yetiler aslında zeka testlerinin
ölçmeye çalıştığı yetilerdir. Pratik zeka yaşamı sürdürmek için gerekli olan pratik becerilerle ilgilidir. Yaratıcı zeka ise
yeni yöntemler bulmak, problemleri farklı biçimlerde çözebilmek, yenilikler yapabilmekle ilgili yetilerdir.
Catell-Horn-Carroll (CHC) Teorisi diye isimlendirilen çok katmanlı zeka teorisi üzerinde en çok durulan zeka teorisidir.
(Raymond Catell aslında Spearman'ın John Horn ise Catell'in öğrencisidir.) Catell zekayı "kristalize zeka (crystalized intelligence)"
ve "akıcı zeka (fluid intelligence)" biçiminde ikiye ayırmıştır. Kristalize zeka öğrenilmiş ve oturmuş bilgi ve becerilerle ilgili
iken akıcı zeka problem çözme ve yeni durumlara uyum sağlama becerileriyle ilgilidir. John Horn ise Catell'in bu iki tür zekasını
genişleterek ona görsel işitsel yetileri, belleğe erişimle ilgili yetileri, tepki zamanlarına ilişkin yetileri, niceliksel işlemlere
yönelik yetileri ve okuma yazma becerilerini de eklemiştir. Nihayet John Carroll zeka ile ilgili 460 yeteneği faktör analizine
sokarak üç katmanlı bir zeka teorisi oluşturmuştur. CHC teorisi Stanford Binet ve Wechsler zeka testlerinin ileri sürümlerinin
benimsediği zeka anlayışıdır.
Bu bilgilerin eşliğinde kuramsal farklılıklara değinmeden yine de zekanın genel bir tanımını yapmak şöyle yapabiliriz: "Zeka
yeni durumlara uyum sağlamak ve problem çözmek için deneyimlerden öğrenme, bilgi edinme ve kaynakları etkin bir biçimde kullanma"
becerisidir.
Yapay zeka ise -ismi üzerinde- insan zekası ile ilgili bilişsel süreçlerin makineler tarafından sağlanmasına yönelik süreçleri
belirtmektedir. Yapay zeka terimi ilk kez 1955 yılında John McCarthy tarafından uydurulmuştur. Doğal zekada olduğu gibi yapay
zekanın da farklı kişiler tarafından pek çok tanımı yapılmaktadır. Ancak bu terim genel olarak "insana özgü nitelikler
olduğu varsayılan akıl yürütme, anlam çıkartma, genelleme ve geçmiş deneyimlerden öğrenme gibi yüksek zihinsel süreçlerin
makineler tarafından gerçekleştirilmesi" biçiminde tanımlanabilir. Yapay zekanın diğer bazı tanımları şunlardır:
- Yapay zeka insan zekasına ilişkin "öğrenme", "akıl yürütme", "kendini düzeltme" gibi süreçlerin makineler tarafından simüle
edilmesidir.
- Yapay zeka zeki makineler yaratma amacında olan bilgisayar bilimlerinin bir alt alanıdır.
- Bilgisayarların insanlar gibi davranmasını sağlamayı hedefleyen bilgisayar bilimlerinin bir alt dalıdır.
Yapay zekanın simüle etmeye çalıştığı bilişsel süreçlerin bazılarının şunlar olduğuna dikkat ediniz:
- Bir şeyin nasıl yapılacağını bilme (knowledge)
- Akıl yürütme (reasoning)
- Problem çözme (problem solving)
- Algılama (perception)
- Öğrenme (learning)
- Planlama (planning)
- Doğal dili anlama ve konuşma
- Uzmanlık gerektiren alanlarda karar verme
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay zeka ile ilgili düşünceler ve görüşler çok eskiye kadar götürülebilir. Ancak modern yapay zeka çalışmalarının 1950li
yıllarda başladığı söylenebilir. Şüphesiz yapay zeka alanındaki gelişmeleri de aslında başka alanlardaki gelişmeler tetiklemiştir.
Örneğin bugün kullandığımız elektronik bilgisayarlar olmasaydı yapay zeka bugünkü durumuna gelemeyecekti. İşte aslında pek
çok bilimsel ve teknolojik gelişmeler belli bir noktaya gelmiş ve yapay zeka dediğimiz bu alan 1950lerde ortaya çıkmaya başlamıştır.
Yapay zeka çalışmalarının ortaya çıkmasına yol açan önemli gelişmeler şunlar olmuştur:
- Mantıktaki Gelişmeler: Bertrand Russell ve Alfred North Whitehead tarafından 1913 yılında yazılmış olan "Principia Mathematica"
adlı üç cilt kitap "biçimsel mantıkta (formal logic)" devrim niteliğinde etki yapmıştır.
- Matematikteki Gelişmeler: 1930larda Alonzo Church "Lambda Calculus" denilen biçimsel sistemi geliştirmiş ve özyinelemeli
fonksiyonel notasyonla hesaplanabilirliği araştırmış ve sorgulamıştır. Yine 1930larda Kurt Gödel "biçimsel sistemler (formal
systems)" üzerindeki çalışmalarıyla teorik bilgisayar bilimlerinin öncülüğünü yapmıştır.
- Turing Makineleri: Alan Turingin henüz elektronik bilgisayarlar gerçekleştirilmeden 1930lu yılların ortalarında (ilk kez 1936)
tasarladığı teorik bilgisayar yapısı olan "Turing Makineleri" bilgisayar bilimlerinin ve yapay zeka kavramının ortaya çıkmasında
etkili olmuştur. (Turing mekinelerinin çeşitli modelleri vardır. Bugün hala Turing makineleri algoritmalar dünyasında algoritma
analizinde ve algoritmik karmaşıklıkta teorik bir karşılaştırma aracı olarak kullanılmaktadır.)
- Elektronik Bilgisayarların Ortaya Çıkması: 1940lı yıllarda ilk elektronik bilgisayarlar gerçekleştirilmeye başlanmıştır.
Bilgisayarlar yapay zeka çalışmalarının gerçekleştirilmesinde en önemli araçlar durumundadır.
Yapay Zeka (Artificial Intelligence) terimi ilk kez John McCarthy tarafından 1955 yılında uydurulmuştur. John McCarthy,
Marvin Minsky, Nathan Rocheste ve Claude Shannon tarafından 1956 yılında Dartmauth Collegede bir konferans organize edilmiştir.
Bu konferans yapay zeka kavramının ortaya çıkışı bakımından çok önemlidir. Bu konferans yapay zekanın doğumu olarak kabul
edilmektedir. Yapay zeka terimi de bu konferansta katılımcılar tarafından kabul görmüştür. John McCarthy aynı zamanda dünyanın
ilk programlama dillerinden biri olan Lispi de 1958 yılında tasarlamıştır. Lisp hala yapay zeka çalışmalarında kullanılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
10. Ders - 28/01/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay zeka çalışmalarının "yaz dönemleri" ve "kış dönemleri" olmuştur. Buradaki "yaz dönemleri" konuya ilginin arttığı,
finansman sıkıntısının azaldığı, çeşitli kurumların yapay zeka çalışmaları için fonlar ayırdığı dönemleri belirtmektedir.
"Kış dönemleri" ise yaz dönemlerinin tersine konuya ilginin azaldığı, finansman sıkıntısının arttığı, kurumların yapay zeka
çalışmaları için fonlarını geri çektiği dönemleri belirtmektedir.
1956-1974 yapay zekanın altın yılları olmuştur. Bu yıllar arasında çeşitli algoritmik yöntemler geliştirilmiş ve pek çok uygulama
üzerinde çalışılmıştır. Örneğin arama (search) yöntemleri uygulanmış ve arama uzayı (search space) sezgisel (heuristic) yöntemlerle
daraltılmaya çalışılmıştır. Yine bu yıllarda doğal dili anlamaya yönelik ilk çalışmalar gerçekleştirilmiştir. Bu ilk çalışmalardan
elde edilen çeşitli başarılar yapay zeka alanında iyimser bir hava estirmiştir. Örneğin:
- 1958 yılında Simon ve Newell "10 yıl içinde dünya satranç şampiyonunun bir bilgisayar olacağını" iddia etmişlerdir. (Halbuki bu
durum 90'lı yılların ikinci yarısında gerçekleşmeye başlamıştır.)
- 1970 yılında Minsky 3 yıldan 8 yıla kadar makinelerin ortalama bir insan zekasına sahip olabileceğini iddia etmiştir.
1974-1980 yılları arasında yapay zeka alanında kış dönemine girilmiştir. Daha önce yapılan tahminlerin çok iyimser olduğu
görülmüş bu da biraz hayal kırıklığına yol açmıştır. Bu yıllarda yapay sinir ağları çalışmaları büyük ölçüde durmuştur.
Yeni projeler için finans elde edilmesi zorlaşmıştır.
1980'li yıllarla birlikte yapay zeka çalışmalarında yine yükseliş başlamıştır. 80'li yıllarda en çok yükselişe geçen yapay zeka
alanı "uzman sistemler" olmuştur. Japonya bu tür projelere önemli finans ayırmaya başlamıştır. Ayrıca Hopfield ve Rumelhart'ın
çalışmaları da "yapay sinir ağlarına" yeni bir soluk getirmiştir.
1987-1993 yılları arasında yine yapay zeka çalışmaları kış dönemine girmiştir. Konuya ilgi azalmış ve çeşitli projeler için finans
kaynakları da kendilerini geri çekmiştir.
1993 yılından itibaren yapay zeka alanı yine canlanmaya başlamıştır. Bilgisayarların güçlenmesi, Internet teknolojisinin gelişmesi,
mobil aygıtların gittikçe yaygınlaşması sonucunda veri analizinin önemi artmış ve bu da yapay zeka çalışmalarına yeni bir boyut
getirmiştir. 1990'lı yılların ortalarından itibaren veri işlemede yeni bir dönem başlamıştır. Veri madenciliği bir alan olarak
kendini kabul ettirmiştir. Özellikle 2011 yılından başlayarak büyük veri (big data) analizleri iyice yaygınlaşmış, yapay sinir
ağlarının bir çeşidi olan derin öğrenme (deep learning) çalışmaları hızlanmış, IOT uygulamaları da yapay zekanın önemini hepten
artırmıştır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay zeka aslında pek çok alt konuya ayrılabilen bir alandır. Yapay zekanın önemli alt alanları şunlardır:
- Makine Öğrenmesi
- Yapay Sinir Ağları ve Derin Öğrenme
- Robotik Sistemlerin Tasarımı ve Gerçekleştirilmesi
- Bulanık Sistemler (Fuzzy Logic Systems)
- Evrimsel Yöntemler (Genetic Algoritmalar, Differential Evoluation, Neuroevolution vs. )
- Üst Sezgisel (Meta Heuristic) Yöntemler (Karınca Kolonisi, Particle Swarm Optimization)
- Olasılıksal (Probabilistic) Yöntemler (Bayesian Network, Hidden Markov Model, Kalman Filter vs.)
- Uzman Sistemler (Expert Systems)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay zekanın yukarıdaki uygulama alanlarından bazıları zaman içerisinde bazı kesimler tarafından artık yapay zekanın bir
konusu olarak görülmemeye başlanmıştır. Örneğin OCR işlemleri eskiden her kesim tarafından bir yapay zeka faaliyeti olarak
görülürdü. Ancak zamanla OCR işlemleri o kadar bilindik ve rutin bir hale geldi ki artık bu işlemler geniş bir kesim tarafından
bir yapay zeka faaliyeti olarak ele alınmıyor. Benzer biçimde satrançta bir büyük ustayı yenecek bir programın yazılması eskiden
bir yapay zeka faaliyeti olarak görülüyorken artık bu faaliyet de bazı kesimler tarafından bir yapay zeka faaliyeti olarak
ele alınmamaktadır. İşte zaman içerisinde yapay zeka olarak görülen bir süreci makineler algoritmik olarak başardıkça onun
yapay zeka alanından çıkartılması gibi bir durum oluşmaya başlamıştır. Eskiden yapay zeka faaliyeti olarak adlandırılan bazı
faaliyetlerin yapay zeka kapsamından çıkarılmasına "Yapay Zeka Etkisi (AI Effect)" denilmektedir. Günümüzde de yapay zeka
faaliyeti olarak ele aldığımız bazı faaliyetlerin zamanla yapay zeka faaliyeti olmaktan çıkabileceğine de dikkatinizi çekmek
istiyoruz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de makine öğrenmesinin ne anlama geldiği üzerinde duracağız. Ancak makine öğrenmesinin ne olduğundan önce öğrenmenin
ne olduğunu ele almak gerekir. Psikolojide öğrenme "davranışta göreli biçimde kalıcı değişiklikler oluşturan süreçler"
biçiminde tanımlanmaktadır. Bu tanımdaki davranış (behavior) klasik davranışçılara göre "gözlemlenebilen devinimleri"
kapsamaktadır. Ancak daha sonra "radikal davranışçılar" bu davranış tanımını zihinsel süreçleri de kapsayacak biçimde
genişletmiştir.
Psikolojide öğrenme kabaca dört bölümde ele alınmaktadır:
1) Klasik Koşullanma (Classical Conditioning)
2) Edimsel Koşullanma (Operant Conditioning)
3) Sosyal Bilişsel Öğrenme (Social Cognitive Learning)
4) Bilişsel Öğrenme (Cognitive Learning):
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
11. Ders - 03/02/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Organizmada doğal bir tepki oluşturan uyaranlara "koşulsuz uyaranlar (unconditioned stimuli)", koşulsuz uyaranlara karşı
organizmanın verdiği tepkilere de "koşulsuz tepkiler (unconditioned responses)" denilmektedir. Örneğin "gök gürültüsü"
koşulsuz uyarana, gök gürültüsüne karşı verilen tepki de koşulsuz tepkiye örnek verilebilir. İşte klasik koşullanmada
başlangıçta organizmada bir tepkiye yol açmayan "nötr bir uyaran (neutral stimulus)" koşulsuz bir uyaranla (unconditioned
stimulus) zamansal bakımdan eşleştirildiğinde artık bu nötr uyaran organizmada koşulsuz uyaranın oluşturduğu tepkiye
benzer bir tepki oluşturmaya başlamaktadır. Nötr uyaranın zamanla koşulsuz uyaranla benzer tepkilere yol açması durumunda
artık bu nötr uyarana "koşullu uyaran (conditioned stimulus)", organizmanın da bu koşullu uyarana verdiği tepkiye "koşullu
tepki (conditioned response)" denilmektedir. Bu süreci şekilsel olarak aşağıdaki gibi ifade edebiliriz:
Koşulsuz Uyaran ---> Koşulsuz Tepki (Doğal Durum)
Nötr Uyaran ---> Koşulsuz Uyaran (Klasik Koşullanma Süreci)
Nötr Uyaran ---> Koşulsuz Uyaran (Klasik Koşullanma Süreci)
... ---> ... (Klasik Koşullanma Süreci)
Nötr uyaran artık koşullu uyaran haline gelmiştir.
Koşullu Uyaran ---> Koşullu Tepki (Öğrenilmiş Yeni Durum)
Klasik koşullanma sürecinin gerçekleşmesi için önce nötr uyaranın sonra koşulsuz uyaranın zamansal bakımdan peş peşe uygulanması
gerekmektedir. Burada iki uyaran arasındaki zaman uzarsa (örneğin 5 saniyeden büyük olursa) organizmanın onları ilişkilendirmesi
güçleşmektedir. Pek çok çalışma 0.5 saniye civarındaki bir zaman aralığının klasik koşullanma için ideal bir zaman aralığı
olduğunu göstermektedir.
Klasik koşullanma ilk kez Ivan Pavlov tarafından fark edilmiş ve tanımlanmıştır. Pavlov'un köpek deneyi klasik koşullanmaya
tipik bir örnektir. Bu deneyde Pavlov köpeğe yemek gösterdiğinde (koşulsuz uyaran) köpek salya salgılamaktadır. Yemeği görünce
köpeğin salya salgılaması doğal olan koşulsuz bir tepkidir. Daha sonra Pavlov köpeğe bir metronum sesinden (nötr uyaran) sonra
yemeği göstermiş ve bu işlemi bir süre tekrarlamıştır. Bu süreç sonunda artık köpek yalnızca metronom sesini duyduğunda (artık
nötr uyaran koşullu uyaran haline gelmiştir) salgı salyalar hale gelmiştir. Köpeğin metronom sesini duyduğunda salgı salgılaması
daha önce yapmadığı koşullu bir tepkidir.
Klasik koşullanma süreci pek çok hayvan üzerinde denenmiştir. Hayvanların çok büyük çoğunluğu klasik koşullanma ile
öğrenebilmektedir. Birinci kuşak davranışçılar pek çok davranışın nedenini klasik koşullanmayla açıklamışlardır. Gerçekten
de fobilerin çoğunda klasik koşullanmanın etkili olduğu görülmektedir. Özellikle olumsuz birtakım sonuçlar doğuran uyaranlar
çok kısa süre içerisinde klasik koşullanmaya yol açabilmektedir. Örneğin karanlık bir sokakta saldırıya uğrayan bir kişi
yeniden karanlık bir sokağa girdiğinde klasik koşullanma etkisiyle yeniden saldırıya uğrayacağı hissine kapılabilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Edimsel koşullanma en önemli öğrenme yollarından biridir. Pek çok süreç edimsel koşullanma ile öğrenilmektedir. Klasik
koşullanmada önce uyaran sonra tepki gelmektedir. Halbuki edimsel koşullanmada önce tepki sonra uyaran gelir. Organizma bir
faaliyette bulunur. Bunun sonucunda hoşa giden bir durum (buna ödül de denilmektedir) oluşursa bu davranış tekrarlanır ve
böylece öğrenme gerçekleşir. Yani özetle organizmada hoşa giden sonuçlar doğuran davranışlar tekrarlanma eğilimindedir.
Edimsel koşullanma bir süreç olarak ilk kez Edward Thorndike tarafından fark edilmiştir. Thorndike "puzzle box" ismini
verdiği bir kafes düzeneği ile yaptığı deneylerden "organizmada olumlu sonuçlar doğuran tepkilerin tekrarlanma eğiliminde
olduğu" sonucunu çıkarmıştır ve bu durumu "etki yasası (law of effect)" terimiyle ifade etmiştir. Her ne kadar edimsel
koşullanmayı gerçek anlamda ilk kez Thorndike fark ettiyse de bunu genişleterek kuramsal bir öğrenme modeli haline getiren
asıl kişi B.F. Skinner olmuştur. Bu konuda kullanılan terminoloji de büyük ölçüde Skinner tarafından oluşturulmuştur.
Edimsel koşullanmada ödül oluşturan uyaranlara "pekiştireç (reinforcer)" denilmektedir. Davranış ne kadar pekiştirilirse
o kadar iyi öğrenilmektedir. Pekiştireçler "pozitif" ve "negatif" olmak üzere ikiye ayrılmaktadır. Pozitif pekiştireçler
doğrudan organizmanın hoşuna gidecek uyaranlardır. Negatif pekiştireçler ise organizmanın içinde bulunduğu hoş olmayan
durumu ortadan kaldırarak dolaylı ödül oluşturan uyaranlardır. Edimsel koşullanma için bazı örnekler şöyle verilebilir:
- Ödevini yapan öğrenciye öğretmenin ödül vermesi ödev yapma davranışını artırmaktadır (pozitif pekiştireç).
- Maddenin bunaltıyı (anxiety) ortadan kaldırması kişiyi madde kullanımına teşvik etmektedir (negatif pekiştireç).
- Arabada emniyet kemeri bağlı değilken rahatsız edici bir ses çıkmaktadır. Bu sesi ortadan kaldırmak için sürücü ve yolcular
emniyet kemerini bağlarlar (negatif pekiştireç).
- Ağlayan çocuğun isteklerini ebeveynin karşılaması istekleri karşılanmayan çocukta ağlama davranışını artırabilmektedir
(pozitif pekiştireç).
Bugün psikolojide edimsel koşullanma davranışı değiştirmede ve şekillendirmede en önemli araçlardan biri olarak kabul
edilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
Sosyal bilişsel öğrenmeye Bu yönteme "taklit yoluyla öğrenme" ya da model alarak öğrenme" de denilmektedir. Biz başkalarını
taklit ederek de davranışlarımızı değiştirebilmekteyiz. Sosyal bilişsel öğrenme büyük ölçüde Albert Bandura tarafından kuramsal
hale getirilmiştir. Aslında sosyal bilişsel öğrenmede de bir bakıma pekiştirmeler söz konusudur. Ancak bu pekiştirmeler doğrudan
değil dolaylı (vicarious) biçimde olmaktadır. Sosyal bilişsel öğrenmede birtakım bilişsel süreçlerin de devreye girdiğine
dikkat ediniz. Çünkü bu süreçte kişinin başkalarının yaptığı davranışları izleme, izlediklerini bellekte saklama ve onlardan
sonuçlar çıkartma gibi süreçler de işin içine karışmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Biliş (cognition) organizmanın bilgiişlem faaliyetlerini anlatan bir terimdir. Biliş denildiğinde düşünme, bellek, dikkat,
bilinç, akıl yürütme gibi faaliyetler anlaşılmaktadır. Araştırmacılar hiç pekiştireç olmadan öğrenmenin insanlarda ve bazı
hayvanlarda mümkün olduğunu göstermişlerdir. Yani biz klasik koşullanma, edimsel koşullanma ve sosyal öğrenme süreçleri
olmadan yalnızca bilişsel etkinliklerle de öğrenebilmekteyiz.
Halk arasında öğrenme denildiğinde genellikle sürecin davranışsal boyutu göz ardı edilmekte yalnızca bilişsel tarafı
değerlendirilmektedir. Halbuki her türlü öğrenmede açık ya da örtük göreli bir biçimde kalıcı bir davranışın ortaya çıkması
beklenir. Ancak "davranış (behavior)" sözcüğünün tanımı konusunda da tam bir anlaşma bulunmamaktadır. Yukarıda da belirttiğimiz
gibi ilk davranışçılar yalnızca gözlemlenebilen süreçleri davranış olarak tanımlarken radikal davranışçılar zihinsel süreçleri
de davranış tanımının içine katmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
O halde makine öğrenmesi (machine learning) nedir? Aslında psikolojideki öğrenme kavramı makine öğrenmesinde de geçerlidir.
Biz makinenin (makine demekle donanımı ve yazılımı kastediyoruz) bir biçimde davranışını arzu edilen yönde değiştirmesini
isteriz. Yani makinenin davranışı bizim istediğimiz yönde ve istediğimiz hedefleri gerçekleştirme anlamında değişmelidir.
İşte makine öğrenmesi kabaca geçmiş bilgilerden ve deneyimlerden faydalı birtakım sonuçlar (davranışlar) ortaya çıkartan
algoritmalar ve yöntemler topluluğudur. Makine öğrenmesinde üç bileşen vardır: Deneyim, Görev ve Performans. Deneyim
canlılarda olduğu gibi makine öğrenmesinde de en önemli öğelerdendir. Makine öğrenmesinde deneyim birtakım verilerin analiz
edilmesini ve onlardan bir kestirim ya da faydalı sonuçlar çıkartılması sürecidir. Görev makinenin yapmasını istediğimiz
şeydir. Görevler düşük bir deneyimle düşük bir performansla gerçekleştirilebilirler. Deneyim arttıkça görevin yerine getirilme
performansı da artabilir. İşte makine öğrenmesi temelde bunu hedeflemektedir. O halde makine öğrenmesinde bir veri grubu
incelenir, analiz edilir, bundan sonuçlar çıkartılır, sonra hedeflenen görev yerine getirilmeye çalışılır. Bu görevin
yerine getirilmesi de gitgide iyileştirilir. Bu süreç çeşitli algoritmalarla ve yöntemlerle değişik biçimlerde ve
yaklaşımlarla yürütülmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Makine öğrenmesi genel olarak üç bölümde ele alıp incelenmektedir:
1) Denetimli Öğrenme (Supervised Learning)
2) Denetimsiz Öğrenme (Unsupervised Learning)
3) Pekiştirmeli Öğrenme (Reinforcement Learning)
Denetimli (supervised) öğrenmede makineye (yani algoritmaya) biz daha önce gerçekleşmiş olan olayları ve sonuçları girdi
olarak veririz. Makine bu olaylarla sonuçlar arasında bağlantı kurar. Daha sonra biz yeni bir olayı makineye verdiğimizde
onun sonucunu makineden kestirmesini isteriz. Denetimsiz öğrenmede biz makineye yalnızca olayları veririz. Makine bunların
arasındaki benzerliklerden ve farklılıklardan hareketle bizim istediğimiz sonuçları çıkartmaya çalışır. Örneğin biz makineye
resimler verip bunların elma mı armut mu olduğunu söyleyelim. Ve bunu çok miktarda yapalım. Sonra ona bir elma resmi
verdiğimizde o daha önceki deneyimlerden hareketle bunun elma olduğu sonucunu çıkartabilecektir. İşte bu denetimli öğrenmeye
bir örnektir. Şimdi biz makineye elma ve armut resimlerini verelim ama bunların ne olduğunu ona söylemeyelim. Ondan bu
resimleri ortak özelliklerine göre iki gruba ayırmasını isteyelim. Bu da denetimsiz öğrenmeye örnektir. Pekiştirmeli öğrenmede
ise tıpkı edimsel koşullanmada olduğu gibi hedefe yaklaşan durumlar ödüllendirilerek makinenin öğrenmesi sağlanmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay zeka alanın diğer pek çok disiplinle yakın ilgisi vardır. Bu ilişkileri açıklamak istiyoruz.
İstatistik: İstatistik temelde iki bölüme ayrılmaktadır:
- Betimsel istatistik (descriptive statistics)
- Sonuç çıkartıcı istatistik (inferential statistics)
Betimsel istatistik verilerin gruplanması, özetlenmesi, karakteristiklerinin betimlenmesi ve gösterilmesi ile ilgilidir. Yani
betimleyici istatistik "zaten var olan durumu" betimlemektedir. Sonuç çıkartıcı istatistik ise "kestirim yapmakla" ilgilidir.
Makine öğrenmesi istatistiğin kestirimsel yöntemlerini açıkça kullanmaktadır. Örneğin regresyon analizi, kümeleme analizi,
karar ağaçları, faktör analizi vs. gibi pek çok makine öğrenmesi yöntemi aslında istatistiğin bir konusu olarak ortaya çıkmıştır.
Ancak bu istatistiksel yöntemler makine öğrenmesi temelinde genişletilmiş ve dinamik bir biçime dönüştürülmüştür.
Matematik: Makine öğrenmesi uygulamalarında pek çok matematiksel yöntemlerden de faydalanılmaktadır. Örneğin makine öğrenmesinde
matematiğin çok değişkenli fonksiyonlar, limit, türev gibi konuları optimizasyon süreçlerinde sıkça kullanılmaktadır.
Veri Madenciliği (Data Mining): Veri madenciliği verilerin içerisinden çeşitli faydalı bilgilerin bulunması, onların çekilerek
elde edilmesi ve bunlarla ilişkin süreçlerle ilgilenmektedir. Şüphesiz bu süreçler istatistiksel birtakım bilgilerin yanı sıra
yazılımsal uygulamaları da bünyesinde barındırmaktadır. (Gerçek madenciliğin "zaten var olan olan değerli bir madeni çıkarmakla"
ilgili olduğuna dikkat ediniz. Veri madenciliği de "verinin içinde zaten var olan değerli unsurların" açığa çıkarılması ile
ilgilidir. Bu nedenle veri madenciliğindeki "madencilik" teriminin süreci anlatmak için uygun bir sözcük olduğu söylenebilir.)
Bilgisayar Bilimleri (Computer Science): Bilgiişlem ve programlama etkinlikleriyle ilgili geniş kapsamlı bir bilim dalıdır.
Bilgisayar bilimlerindeki teknikler ve yöntemler kullanılmadan makine öğrenmesi uygulamaları gerçekleştirilememektedir.
İlgili Konudaki Özel Bilgiler: Şüphesiz her türlü algoritmik yöntem için bir biçimde hedeflenen konuda belli bir bilgi
birikiminin var olması gerekir. Örneğin ne kadar iyi programlama ve istatistik bilirseniz bilin görüntüsel verilerle
çalışmak için bir görüntünün (resmin) nasıl bir organizasyona sahip olduğunu bilmeniz gerekir. Ya da örneğin hiç muhasebe
bilmeyen iyi bir programcının bir muhasebe programı yazabilmesini bekleyebilir miyiz?
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Veri bilimi (data science) ve makine öğrenmesi uygulamaları için pek çok dil kullanılabilmektedir. Bu bağlamda ilk akla
gelen dil şüphesiz Python'dur. Ancak C++, Java, C# gibi popüler programlama dilleri de bu amaçla gittikçe daha fazla kullanılır
hale gelmektedir. Python programlama dili son on yıldır bir atak yaparak dünyanın en popüler ilk üç dili arasına girmiştir.
Python dilinin özellikle veri bilimi ve makine öğrenmesi konusunda popülaritesinin neden bu kadar arttığına ilişkin görüşlerimiz
şöyledir:
- Son yıllarda veri işleme ve verilerden kestirim yapma gereksiniminin gittikçe artmıştır ve Python dili de veri analizi için
iyi bir araç olarak düşünülmektedir.
- Veri bilimi ve makine öğrenmesi için Python dilinden kullanılabilecek pek çok kütüphane vardır. (Bu konudaki kütüphaneler
diğer dillerden -şimdilik- daha fazladır.)
- Python nispeten basit bir dildir. Bu basitlik ana hatları veri analizi olan konularda uygulamacılara kolaylıklar sunmaktadır.
Bu nedenle Python diğer disiplinlerden gelip de veri analizi ve makine öğrenmesi uygulaması yapmak isteyenler için nispeten daha
kolay bir araç durumundadır.
- Python genel amaçlı bir programalama dili olmasının yanı sıra aynı zamanda matematiksel alana da yakın bir programlama dilidir.
Yani Python'un matematiksel alana yönelik ifade gücü (expressivity) popüler diğer programlama dillerinden daha yüksektir.
- Python dilinin çeşitli prestijli üniversitelerde "programlamaya giriş (introduction to programming)" gibi derslerde kullanılmaya
başlanmış olması onun popülaritesini artırmıştır. Ayrıca Python özellikle 3'lü versiyonlarla birlikte dikkate değer biçimde
iyileştirilmiştir.
- Python dilinin veri analizi için diğer dillere göre daha erken yola çıktığı söylenebilir. Bu alanda algoritma geliştiren
araştırmacılar algoritimalarını daha çok Pyhon kullanarak gerçekleştirmişlerdir.
Pekiyi Python dilinin veri analizi ve makine öğrenmesi konusunda hangi dezavantajları vardır? Bu dezavantajları da şöyle
sıralayabiliriz:
- Python nispeten yavaş bir dildir. Bu yavaşlık büyük ölçüde Python dilinin dinamik tür sistemine sahip olmasından, Python
programlarının yorumlayıcı yoluyla çalıştırılmasından ve dilin seviyesinin yüksek olmasından kaynaklanmaktadır. Her ne kadar
veri analizi ve makine öğrenmesinde kullanılan kütüphaneler (NumPy, Pandas, SciPy, Keras gibi) asıl olarak C programlama dili
ile yazılmış olsalar da bu C rutinlerinin Python'dan çağrılması ve diğer birtakım işlemler yavaşlığa yol açmaktadır.
- Python yüksek seviyeli bir dil olduğu için dilin olanakları ince birtakım işlemlerin yapılabilmesine olanak sağlamamaktadır.
Pekiyi Python genel olarak yavaş bir dilse bu durum veri analizi ve makine öğrenmesi uygulamalarında bir sorun oluşturmaz mı?
İşte Python'ın yavaşlığı ve yorumlayıcılarla çalışılan (interpretive) bir dil olması bazı projelerde Python'ı uygun bir dil
olmaktan çıkartabilmektedir. Makine öğrenmesi konusunda çalışmalar yapan şirket ve kurumlardan bazıları önceleri Python'ı
ana dil olarak kullanırken daha sonra bir prototip dil olarak kullanmaya başlamıştır. (Burada prototip dil olarak kullanmak
demekle projeyi Python'da hızlı bir biçimde gerçekleştirildikten sonra ürün aşamasında onu yeniden C++, Java ve C# gibi dillerle
kodlamayı kastediyoruz.)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bilgisayar teknolojisinin gelişmesine paralel olarak veri toplama ve depolama olanaklarının artmasıyla birlikte veri
analizinde yeni bir dönemin başladığı söylenebilir. Özellikle 2000'li yıllardan itibaren insanlık eskiden olduğundan çok
daha fazla miktarda veriyle karşılaşmıştır. Bu bakımdan insanlığının bir veri bombardımanına maruz kaldığı söylenebilir. İşte
"veri bilimi (data science)" 2000'li yılların başlarında böyle bir bağlamda kullanılmaya başlanmış bir terimdir. Veri biliminin
tanımı konusunda tam bir fikir birliği bulunmamaktadır. Ancak veri bilimi "verilerden birtakım faydalı bilgilerin ve içgörülerin
(insights) elde edilmesine yönelik çalışmaların yapıldığı" bir alan olarak tanımlanabilir. Veri bilimi ile uğraşan uygulamacılara
"veri bilimcisi (data scientist)" denilmektedir. (Bu bağlamda veri bilimcisinin bir bilim insanı gibi ele alınmadığını vurgulamak
istiyoruz.)
Aslında veri bilimi istatistik biliminin uygulamalı ve dinamik bir alt alanı gibi de düşünülebilir. Bazı bilim adamlarına göre
"veri bilimi" terimi gereksiz biçimde uydurulmuş bir terimdir ve aslında bu alan "uygulamalı istatistikten" başka bir şey değildir.
Ancak ne olursa olsun son 30 senedir verilerin işlenmesi ve bunlardan faydalı birtakım sonuçların çıkarılması için yapılan
işlemlerin klasik istatistiksel çalışma ile örtüşmediği de açıktır. Veri bilimi terimi -bazı çevreler tarafından eleştiriliyor
olsa da- kendini kabul ettirmiş ve yaygınlık kazanmış bir terim gibi görünmektedir.
İstatistik ile veri bilimi arasındaki farklılıkları birkaç cümleyle şöyle özetleyebiliriz: Veri bilimi klasik istatistikten
farklı olarak disiplinler arası bir niteliğe sahiptir. Veri bilimi yazılımı çok daha yoğun kullanmaktadır. Veri bilimi
örneklemlerden ziyade çok daha büyük verilerle uğraşma eğilimindedir. İstatistiksel çalışmalar daha çok hipotezleri doğrulamaya
odaklanırken veri bilimi daha çok faydalı hipotezler oluşturmaya odaklanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
12. Ders - 04/02/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
İstatistikte verilerin merkezine ilişkin bilgi veren ölçülere "merkezi eğilim ölçüleri (measures of central tendency)"
denilmektedir. Merkezi eğilim ölçülerinin en yaygın kullanılanı "aritmetik ortalamadır". Aritmetik ortalama (mean) değerlerin
toplanarak değer sayısına bölünmesiyle elde edilmektedir.
Aritmetik ortalama hesaplamak için çeşitli kütüphanelerde çeşitli fonksiyonlar hazır olarak bulunmaktadır. Örneğin Python'ın
standart kütüphanesindeki statistics modülünde bulunan mean fonksiyonu aritmetik ortalama hesaplamaktadır.
>>> import statistics
>>> a = [1, 2, 7, 8, 1, 5]
>>> statistics.mean(a)
4
mean fonksiyonu herhangi bir dolaşılabilir nesneyi parametre olarak alabilmektedir.
NumPy kütüphanesindeki mean fonksiyonu axis temelinde (yani satırsal ve sütunsal biçimde) ortalama hesaplayabilmektedir. Örneğin:
>>> import numpy as np
>>> a = np.array([[1, 2, 3], [5, 6, 7], [8, 9, 10]])
>>> np.mean(a, axis=0)
array([4.66666667, 5.66666667, 6.66666667])
NumPy'da mean fonksiyonu aynı zamanda ndarray sınıfının metodu biçiminde de bulunmaktadır. Örneğin:
>>> import numpy as np
>>> a = np.array([[1, 2, 3], [5, 6, 7], [8, 9, 10]])
>>> a.mean(axis=0)
array([4.66666667, 5.66666667, 6.66666667])
Pandas kütüphanesinde Series ve DataFrame sınıflarının mean metotları aritmetik ortalama hesabı yapmaktadır. DataFrame
sınıfının mean metodunda default axis 0 biçimindedir. Yani sütunsal ortalamalar elde edilmektedir. Örneğin:
>>> import pandas as pd
>>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
>>> df
0 1 2
0 1 2 3
1 4 5 6
2 7 8 9
>>> df.mean()
0 4.0
1 5.0
2 6.0
dtype: float64
Aritmetik ortalama aralıklı (interval) ve oransal (ratio) ölçeklere uygulanabilir. Aritmetik ortalama O(N) karmaşıklıkta
yapılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Diğer bir merkezi eğilim ölçüsü de "medyan (median)" denilen ölçüdür. Medyan sayıların küçükten büyüğe sıraya dizildiğinde
ortadaki değerdir. Ancak sayılar çift sayıda ise sayıların tam ortasında bir değer olmadığı için ortadaki iki değerin aritmetik
ortalaması medyan olarak alınmaktadır. Medyan işlemi uç değerlerden (outliers) etkilenmez. Ancak medyan işlemi aritmetik
ortalamadan daha fazla zaman alan bir işlemdir. Çünkü medyan için önce değerlerini sıraya dizilmesi gerekmektedir. Dolayısıyla
medyan işlemi O(N log N) karmaşıklıta bir işlemdir. Medyan işlemi Python'ın standard kütüphanesinde statistics modülü içerisindeki
median isimli fonksiyonla yapılabilmektedir. Örneğin:
>>> import statistics
>>> a = [1, 23, 56, 12, 45, 21]
>>> statistics.median(a)
22.0
NumPy kütüphanesinde medyan işlemi eksensel biçimde median fonksiyonuyla yapılabilmektedir. Örneğin:
>>> import numpy as np
>>> a = np.random.randint(1, 100, (10, 10))
>>> a
array([[ 8, 26, 42, 26, 10, 66, 94, 91, 3, 97],
[31, 62, 82, 86, 45, 73, 38, 29, 43, 62],
[78, 49, 22, 32, 74, 15, 54, 59, 37, 87],
[ 5, 75, 34, 82, 58, 63, 84, 40, 92, 20],
[57, 21, 2, 65, 69, 37, 78, 9, 57, 9],
[57, 6, 72, 17, 39, 13, 25, 49, 85, 46],
[57, 47, 57, 47, 25, 40, 20, 72, 10, 16],
[12, 83, 35, 89, 86, 84, 66, 54, 50, 38],
[90, 88, 65, 82, 29, 18, 86, 37, 60, 70],
[38, 17, 40, 81, 18, 89, 4, 22, 59, 65]])
>>> np.median(a, axis=0)
array([47.5, 48. , 41. , 73. , 42. , 51.5, 60. , 44.5, 53.5, 54. ])
ndarray sınıfının median isimli bir metodu yoktur.
Benzer biçimde Pandas kütüphanesinde de Series ve DataFrame sınıflarının median isimli metotları eksensel median işlemi
yapabilmektedir. Tabii DataFrame sınıfının median metodunun default ekseni yine 0'dır (yani sütunsal işlem yapılmaktadır).
Örneğin:
>>> a = np.random.randint(1, 100, (10, 10))
>>> a
array([[39, 73, 50, 26, 3, 87, 78, 49, 87, 39],
[91, 39, 6, 99, 85, 1, 50, 59, 20, 19],
[83, 91, 94, 54, 28, 84, 25, 41, 19, 83],
[38, 46, 78, 56, 53, 3, 2, 8, 27, 42],
[33, 32, 36, 61, 7, 6, 88, 72, 71, 88],
[32, 71, 68, 46, 64, 70, 45, 92, 8, 64],
[71, 42, 36, 82, 45, 17, 13, 63, 8, 60],
[70, 56, 76, 59, 64, 28, 81, 62, 60, 72],
[82, 66, 93, 44, 67, 53, 97, 92, 89, 44],
[34, 2, 3, 75, 19, 17, 34, 89, 98, 14]])
>>> df = pd.DataFrame(a)
>>> df
0 1 2 3 4 5 6 7 8 9
0 39 73 50 26 3 87 78 49 87 39
1 91 39 6 99 85 1 50 59 20 19
2 83 91 94 54 28 84 25 41 19 83
3 38 46 78 56 53 3 2 8 27 42
4 33 32 36 61 7 6 88 72 71 88
5 32 71 68 46 64 70 45 92 8 64
6 71 42 36 82 45 17 13 63 8 60
7 70 56 76 59 64 28 81 62 60 72
8 82 66 93 44 67 53 97 92 89 44
9 34 2 3 75 19 17 34 89 98 14
>>> df.median()
0 54.5
1 51.0
2 59.0
3 57.5
4 49.0
5 22.5
6 47.5
7 62.5
8 43.5
9 52.0
dtype: float64
Tabii median işlemi de ancak sayısal verilere yani aralıklı ve oransal ölçeklere uygulanabilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Merkezi eğilim ölçülerinin bir diğeri de "mod" denilen ölçüdür. Bir grup verideki en çok yinelenen değere "mod (mode)"
denilmektedir. Mod özellikle kategorik ve sıralı ölçeklerde ortalamanın yerini tutan bir işlem olarak kullanılmaktadır.
Mod işlemi genel olarak O(N log N) karmaşıklıkta yapılabilmektedir. (Tipik mod algoritmasında değerler önce sıraya dzilir.
Sonra yan yana aynı değerlerden kaç tane olduğu tespit edilir.) Mod işlemi Python standart kütüphanesindeki statistics
modülünde bulunan mode fonksiyonuyla yapılabilir. Örneğin:
>>> a = [1, 3, 3, 4, 2, 2, 5, 2, 7, 9, 5, 3, 5, 7, 5]
>>> statistics.mode(a)
5
Eğer en çok yinelenen değer birden fazla ise mode fonksiyonu dizilimde ilk karşılaşılan en çok yinelenen değere geri
dönmektedir.
NumPy kütüphanesinde mod işlemini yapan bir fonksiyon bulunmamaktadır. Ancak SciPy kütüphanesinde mod işlemi için stats
modülü içerisindeki mode fonksiyonu kullanılabilir. Bu fonksiyon yine eksensel işlemler yapabilmektedir. mode fonksiyonu
ModeResult isimli bir sınıf türünden tuple sınıfından türetilen bir sınıf türünden bir nesne verir. Bu sınııfın mode ve
count örnek öznitelikleri en çok yinelenen değerleri ve onların sayılarını bize vermektedir. ModeResult sınıfı bir çeşit
demet özelliği gösterdiği için demet gibi de kullanılabilir.
Örneğin:
>>> import scipy.stats
>>> import numpy as np
>>> a = np.random.randint(1, 10, (20, 10))
>>> a
array([[2, 5, 1, 9, 9, 9, 1, 5, 1, 8],
[3, 1, 1, 8, 2, 5, 3, 2, 5, 4],
[7, 3, 7, 6, 1, 2, 4, 5, 3, 7],
[8, 3, 9, 4, 9, 9, 4, 5, 1, 8],
[6, 1, 6, 6, 3, 2, 2, 4, 3, 9],
[1, 4, 5, 6, 4, 4, 6, 2, 7, 3],
[6, 5, 7, 4, 5, 8, 5, 4, 4, 9],
[5, 1, 4, 8, 8, 9, 6, 1, 8, 6],
[8, 8, 5, 4, 2, 8, 6, 1, 1, 5],
[3, 8, 4, 9, 7, 6, 6, 9, 6, 4],
[5, 8, 1, 6, 7, 8, 7, 7, 6, 4],
[7, 7, 1, 8, 8, 3, 1, 8, 3, 1],
[5, 5, 5, 4, 9, 3, 8, 7, 9, 8],
[8, 9, 9, 2, 7, 3, 3, 6, 2, 6],
[1, 6, 6, 8, 4, 4, 3, 2, 2, 4],
[1, 2, 4, 5, 8, 3, 1, 5, 1, 3],
[5, 9, 3, 1, 1, 6, 9, 5, 4, 1],
[3, 1, 4, 9, 1, 2, 5, 1, 3, 7],
[8, 7, 3, 3, 7, 4, 1, 7, 6, 9],
[9, 2, 1, 6, 5, 3, 7, 9, 5, 7]])
>>> mr = scipy.stats.mode(a, axis=0)
>>> mr.mode
array([[5, 1, 1, 6, 7, 3, 1, 5, 1, 4]])
>>> mr.count
array([[4, 4, 5, 5, 4, 5, 4, 5, 4, 4]])
Pandas kütüphanesinde Series ve DataFrame sınıflarının mode metotları da mode işlemi yapmaktadır. Örneğin:
>>> a = np.random.randint(1, 10, (20, 10))
>>> df = pd.DataFrame(a)
>>> df
0 1 2 3 4 5 6 7 8 9
0 5 5 9 1 4 7 4 3 9 6
1 1 3 2 1 6 8 4 4 3 3
2 8 7 9 5 8 1 5 3 8 1
3 8 7 1 7 1 7 5 8 7 6
4 1 9 9 5 4 7 5 6 9 9
5 7 9 1 7 9 6 5 4 7 8
6 3 2 6 5 4 8 6 5 5 9
7 4 9 6 9 5 4 9 4 4 7
8 2 3 3 8 4 8 2 1 4 1
9 7 6 6 1 7 3 5 1 6 9
10 3 3 1 5 9 6 1 3 1 4
11 4 6 2 1 1 1 6 3 2 1
12 9 1 1 6 3 7 1 1 7 8
13 8 3 3 5 9 1 1 8 2 4
14 8 7 7 1 6 5 8 6 4 8
15 4 8 4 9 2 6 7 9 2 1
16 6 3 2 6 1 2 5 3 9 2
17 3 9 3 6 9 1 9 7 9 4
18 6 2 9 1 4 2 4 8 6 2
19 3 2 7 2 5 9 1 7 9 2
>>> df.mode()
0 1 2 3 4 5 6 7 8 9
0 3 3.0 1 1.0 4.0 1 5.0 3.0 9.0 1.0
1 8 NaN 9 NaN NaN 7 NaN NaN NaN NaN
DataFrame sınıfının mode metodu bize bir DataFrame nesnesi vermektedir. Uygulamacı genellikle bunun ilk satırı ile ilgilenir.
Diğer satırlar eşit miktarda tekrarlanan elemanlardan oluşmaktadır. Tabii belli bir sütunda eşit miktarda tekrarlanan elemanların
sayısı az ise artık geri döndürülen DataFrame'in o sütuna ilişkin satırlarında NaN değeri bulunacaktır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Değerlerin merkezine ilişkin bilgiler dağılım hakkında iyi bir fikir vermeyebilir. Örneğin iki ülkede kişi başına düşen
ortalama yıllık gelir (gayri safi milli hasıla) 15000 dolar olabilir. Ancak bu iki ülke arasında gelir dağılımında önemli
farklılıklar bulunuyor olabilir. O halde değerlerin ortalamasının yanı sıra onların ortalamaya göre nasıl yayıldıkları da
önemlidir. İstatistikte değerlerin ortalamaya göre yayılımı için "merkezi yayılım ölçüleri (measures of dispersion)" denilen
bazı ölçüler kullanılmaktadır. Merkezi yayılım ölçüleri aslında değerlerin ortalamadan ortalama uzaklığını belirlemeyi hedeflemektedir.
Eğer biz değerleri ortalamadan çıkartıp onların ortalamasını alırsak 0 elde ederiz. Aşğıdaki programda değerlerin ortalamadan
uzaklıklarının ortaalaması bulunmuştur. (Bu tür durumlarda yuvarlama hatalarından dolayı sıfır yerine sıfıra çok yakın
değerler elde edilebilir.)
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
a = np.array([1, 4, 6, 8, 4, 2, 1, 8, 9, 3, 6, 8])
mean = np.mean(a)
print(mean)
result = np.mean(a - mean)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Merkezi yayılım ölçüsü olarak "değerlerin ortalamadan ortalama mutlak uzaklığına" başvurulabilir. Burada mutlak değer
alınmasının nedeni uzaklıkları negatif olmaktan kurmak içindir. Bu durumda ortalama 0 çıkmaz. Ancak bu yöntem de aslında
çok iyi bir yöntem değildir. Aşağıda aynı dizilimin ortalamadan ortalama mutlak uzaklığı hesaplanmışır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
a = np.array([1, 4, 6, 8, 4, 2, 1, 8, 9, 3, 6, 8])
mean = np.mean(a)
print(mean)
result = np.mean(np.abs(a - mean))
print(result) # 2.5
#----------------------------------------------------------------------------------------------------------------------------
Aslında ortalamadan ortalama uzaklık için "standart sapma (standard deviation)" denilen ölçü tercih edilmektedir. Standart
sapmada ortalamadan uzaklıkların mutlak değeri değil kareleri alınarak negatiflikten kurtulunmaktadır. Kare alma işlemi
değerleri daha fazla farklılaştırmaktadır. Yani aynı değerlerin oluşma olasılığı kare alma sayesinde daha azalmaktadır.
Aynı zamanda bu işlem bazı durumlarda başka faydalara da yol açmaktadır. (Örneğin ileride bu kare alma işlemlerinin optimizasyon
problemlerinde uygun bir işlem olduğunu göreceğiz.)
Standart sapma, değerlerin ortalamadan farklarının karelerinin ortalamasının karekökü alınarak hesaplanmaktadır. Ancak burada
ortalama bulunurken değer sayısı n olmak üzere bölüm n'e ya da (n - 1)'e yapılabilmektedir. Anakütle (population) için genellikle
n'e bölme yapılırken örneklemden hareketle anakütle standart sapmasının tahmin edileceği durumlarda (n - 1)'e bölme yapılmaktadır.
(n - 1)'e bölme işlemine "Bessel düzeltmesi (Bessel's correction)" denilmektedir. Genellikle standart sapma hesaplayan fonksiyonlar
kaça bölüneceğini "ddof (delta degrees of freedom)" parametresiyle programcıdan istemektedir. ddof değeri "n eksi kaça
bölüneceğini" belirtir. Örneğin ddof=0 ise n'e bölme ddof=1 ise (n - 1)'e bölme uygulanır.
Aşağıdaki örnekte NumPy kullanılarak bir standart sapma hesaplayan fonksiyon yazılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
def sd(a, ddof = 0):
return np.sqrt(np.sum((a - np.mean(a)) ** 2) / (len(a) - ddof))
a = [1, 4, 6, 8, 4, 2, 1, 8, 9, 3, 6, 8]
result = sd(a)
print(result) # 2.7688746209726918
#----------------------------------------------------------------------------------------------------------------------------
Python'ın Standart Kütüphanesinde statistics modülü içerisinde standart sapma hesabı yapan stdev ve pstdev fonksiyonları
bulunmaktadır. stdev fonksiyonu (n - 1)'e bölme yaparken pstdev (buradaki 'p' harfi "population" sözcüğünden gelmektedir)
fonksiyonu n'e bölme yapmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import statistics
a = [1, 4, 6, 8, 4, 2, 1, 8, 9, 3, 6, 8]
std = statistics.stdev(a)
print(std) # 2.891995221924885
std = statistics.pstdev(a)
print(std) # 2.7688746209726918
#----------------------------------------------------------------------------------------------------------------------------
NumPy kütüphanesinde std isimli fonksiyon eksensel standart sapma hesaplayabilmektedir. Fonksiyonun ddof parametresi
default durumda 0'dır. Yani default durumda fonksiyon n'e bölme yapmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
a = np.array([1, 4, 6, 8, 4, 2, 1, 8, 9, 3, 6, 8])
result = np.std(a)
print(result) # 2.7688746209726918
#----------------------------------------------------------------------------------------------------------------------------
Pandas kütüphanesinde de Series ve DataFrame sınıflarının std isimli metotları eksensel standart sapma hesabı yapabilmektedir.
Ancak bu metotlarda ddof parametresi default 1 durumundadır. Yani bu metotlar default durumda (n - 1)'e bölme yapmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
s = pd.Series([1, 4, 6, 8, 4, 2, 1, 8, 9, 3, 6, 8])
result = s.std()
print(result) # 2.891995221924885
s = pd.Series([1, 4, 6, 8, 4, 2, 1, 8, 9, 3, 6, 8])
result = s.std(ddof=0)
print(result) # 2.7688746209726918
#----------------------------------------------------------------------------------------------------------------------------
Standart sapmanın karesine "varyans (variance)" denilmektedir. Varyans işlemi standart kütüphanedeki statistics modülünde
bulunan variance ve pvariance fonksiyonlarıyla yapılmaktadır. NumPy kütüphanesinde varyans işlemi var fonksiyonuyla ya da
ndarray sınıfının var metoduyla, Pandas kütüphenesinin Series ve DataFrame sınıflarındaki var metoduyla yapılmaktadır.
Yine NumPy'daki variance fonksiyonundaki ddof default olarak 0, Pandas'taki ddof ise 1'dir.
Pekiyi neden standart sapma varken ayrıca onun karesi için varyans terimi uydurulmuştur? İşte istatistikte pek çok durumda
aslında doğrudan ortalamadan farkların karesel ortalamaları (yani standart sapmanın karesi) kullanılmaktadır. Bu nedenle
bu hesaba ayrı bir isim verilerek anlatımlar kolaylaştırılmak istenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import statistics
import numpy as np
import pandas as pd
data = [1, 4, 6, 8, 4, 2, 1, 8, 9, 3, 6, 8]
result = statistics.pvariance(data)
print(result) # 7.666666666666667
result = np.var(data)
print(result) # 7.666666666666667
s = pd.Series(data)
result = s.var(ddof=0)
print(result) # 7.666666666666667
#----------------------------------------------------------------------------------------------------------------------------
13. Ders - 10/02/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bir deney sonucunda oluşacak durum baştan tam olarak belirlenemiyorsa böyle deneylere "rassal deney (random experiment)"
denilmektedir. Örneğin bir paranın atılması deneyinde para "yazı" ya da "tura" gelebilir. O halde "paranın atılması"
rassal bir deneydir. Benzer biçimde bir zarın atılması, bir at yarışında hangi atın birinci olacağı gibi deneyler rassal
deneylerdir. Bir deneyin sonucu önceden bilinebiliyorsa bu tür deneylere "deterministik deneyler" de denilmektedir.
Bazı bilimlerdeki süreçler deterministiktir. Ancak bazı bilimlerdeki süreçlerin sonucunda oluşacak durumlar önceden
tam olarak kestirilememektedir. Önceden sonucu tam olarak kestirilemeyen süreçlere "olasılıksal (probabilistic)" ya da
"stokastik (stochastic)" süreçler denilmektedir.
Deterministik süreçlerle olasılıksal süreçler aslında üzerinde çok düşünülmüş konulardandır. Kimilerine göre olasılıksal
süreç diye bir şey yoktur. Her şey deterministiktir. Bir sürecin olasılıksal olması sadece "bizim onun sonucunu
belirleyemememizden" kaynaklanmaktır. Örneğin paranın atılması sırasındaki tüm bilgilere sahip olsak artık bu deneyin sonucu
olasılıksal değil deterministik hale gelecektir. Tabii evrende kaotik bir süreçler söz konusudur. Yani bir olay başka bir
olayı etkilemekte ve küçük değişiklikler büyük sonuçlara yol açabilmektedir. Buna feslefede "kaos teorisi" halk arasında
da "kelebek etkisi" denilmektedir. Eğer evrende mutlak bir determinizm varsa evren reset konumuna alındığında her şey
yine bugüne aynı biçimde gelecektir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bir rassal deney sonucunda oluşabilecek tüm olası durumların kümesine "örnek uzayı (sample space)" denilmektedir. Örneğin
bir paranın atılması deneyinde örnek uzayı S = { Yazı, Tura} biçimindedir. Biz zarın atılması deneyindeki örnek uzayı ise
S = {1, 2, 3, 4, 5, 6} biçimindedir. İki zarın atılmasındaki örnek uzayı S = {(1, 1), (1, 2), (6, 5), (6, 6)} biçimindedir.
Örnek uzayın her bir alt kümesine "olay (event)" denilmektedir. Örneğin bir zarın atılmasındaki bazı olaylar şunlar olabilir:
E1 = {1, 3}
E2 = {3, 4, 5}
E3 = {6}
Bir kümenin bütün alt kümelerine o kümenin "kuvvet kümesi (power set)" denilmektedir. Bir rassal deneydeki bütün olaylar
kuvvet kümesi içerisindeki olaylardır. Bir kümenin tüm alt kümelerinin sayısı 2^n tanedir.
Örnek uzayın tek elemanlı olaylarına (yani alt kümelerine) "basit olay (simple events)" denilmektedir. Örneğin paranın
atılması deneyindeki basit olaylar {Yazı} ve {Tura} biçimindedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Olasılığın (probablity) değişik tanımları yapılabilmektedir. Olasılığın en yaygın tanımlarından birisi "göreli sıklık
(relative frequency)" tanımıdır. Bu tanıma göre bir rassal olay çok sayıda yinelendikçe elde edilen olasılık değeri belli
bir değere yakınsamaya başlar. Örneğin bir paranın 100 kere atılmasında 50 kere yazı 50 tura gelmeyebilir. Ancak para sonsuz
sayıda atılırsa (ya da çok fazla sayıda atılırsa) tura gelme sayısının paranın atılma sayısına oranı 0.5'e yakınsayacaktır.
Buna istatistike "büyük sayılar yasası (law of large numbers)" da denilmektedir.
Aşağıdaki örnekte bir para değişik miktarlarda yazı tura biçiminde atılmıştır. Elde edilen oranlar gitgide 0.5'e yakınsayacaktır.
Programın çalıştırılmasıyla şu değerler elde edilmiştir:
head = 0.4, tail = 0.6
head = 0.5, tail = 0.5
head = 0.479, tail = 0.521
head = 0.4977, tail = 0.5023
head = 0.50005, tail = 0.49995
head = 0.500075, tail = 0.499925
head = 0.5002417, tail = 0.4997583
head = 0.49997078, tail = 0.50002922
#----------------------------------------------------------------------------------------------------------------------------
import random
HEAD = 1
def head_tail(n):
head = tail = 0
for _ in range(n):
val = random.randint(0, 1)
if val == HEAD:
head += 1
else:
tail += 1
return head / n, tail / n
head, tail = head_tail(10)
print(f'head = {head}, tail = {tail}')
head, tail = head_tail(100)
print(f'head = {head}, tail = {tail}')
head, tail = head_tail(1000)
print(f'head = {head}, tail = {tail}')
head, tail = head_tail(10_000)
print(f'head = {head}, tail = {tail}')
head, tail = head_tail(100_000)
print(f'head = {head}, tail = {tail}')
head, tail = head_tail(1_000_000)
print(f'head = {head}, tail = {tail}')
head, tail = head_tail(10_000_000)
print(f'head = {head}, tail = {tail}')
head, tail = head_tail(100_000_000)
print(f'head = {head}, tail = {tail}')
#----------------------------------------------------------------------------------------------------------------------------
Olasılığın temel matematiksel teorisi Kolmogorov tarafından oluşturulmuştur. Kolmogorov üç aksiyom kabul edildiğinde bütün
olasılık kurallarının teoremi-ispat biçiminde açıklanabileceğini göstermiştir. Kolmogorov'un üç aksiyomu şöyledir:
1) Örnek uzayının olsılığı 1'dir. Yani P(S) = 1'dir. Buradan olasılığın en yüksek değerinin 1 olacağını ve tüm örnek uzayının
olma olasılığının 1 olduğunu anlamalıyız. Örneğin bir zarın atılmasındaki örnek uzayı {1, 2, 3, 4, 5, 6} biçimindedir.
P({1, 2, 3, 4, 5, 6}) = 1'dir. Buradaki P({1, 2, 3, 4, 5, 6}) bir zarın 1 ya da 2 ya da 3 ya da 4 ya da 5 ya da 6 gelme olasılığı
anlamındadır.
2) Herhangi bir olayın olasılığı 0 ya da 0'dan büyüktür. Yani P(E) >= 0'dır. Burada olasılığın en düşük değerinin 0 olduğu
belirtilmektedir. O halde olasılık değeri 0 iel 1 arasındadır.
3) Bir kümenin olasılığı demek rassal deney sonucunda o kümenin herhangi elemanının oluşma olasılığı demektir. Örneğin
zarın atılmasındaki P({3, 5}) olasılığı zarın üç ya da 5 gelme olasılığı anlamına gelir. İki küme ayrıksa (yani ortak elemanları)
yoksa Bu iki kümenin birleşimlerinin olasılığı bu iki kğmenin olaıslık toplamlarına eşittir. Yani E1 ve E2 iki olay
olmak üzere eğer bu iki olay ayrıksa (yani E1 ⋂ E2 = ∅ ise) P{E1 E2} = P{E1} + P{E2}'dir.
Bu üç kural kabul edildiğinde kümeler teorisi, permütasyon, kombinasyon gibi işlemlerle tüm olasılk formülleri elde edilebilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Olasılıkta ve istatistikte en çok kullanılan temel kavramlardan biri "rassal değişken (random variable)" denilen kavramdır.
Her ne kadar "rassal değişken" teriminde bir "değişken" sözcüğü geçiyorsa da aslında rassal değişken bir fonksiyon belirtmektedir.
Rassal değişken bir rassal deney ile ilgilidir. Bir rassal deneyde örnek uzayın her bir elemanını (yani basit olayını)
reel bir değere eşleyen bir fonksiyon belirtmektedir. Rassal değişkenler genellikle "sözel biçimde" ifade edilirler.
Ancak bir fonksiyon belirtirler. Rassal değişkenler matematiksel gösterimlerde genellikle büyük harflerle belirtilmektedir.
Örneğin:
- R rassal değişkeni "iki zar atıldığında zarların üzrindeki sayıların toplamını" belirtiyor olsun. Burada aslında R
bir fonksiyondur. Örnek uzayın her bir elemanını bir değere eşlemektedir. Matematiksel gösterimle R rassal değişkeni
şöyle belirtilebilir:
R: S -> R
Burada R'nin örnek uzayından R'ye bir fonksiyon belirttiği anlaşılmaıdır. Burada R fonksiyonu aşağıdaki gibi eşleme
yapmaktadır:
(1, 1) -> 2
(1, 2) -> 3
(1, 3) -> 4
...
(6, 5) -> 11
(6, 6) -> 12
K rassal değişkeni "rastgele seçilen bir kişinin kilosunu belirtiyor" olsun. Bu durumda örnek uzayı aslında dünyaki
tüm insanlardır. Burada K fonksiyonu da her insanı onun kilosuna eşleyen bir fonksiyondur.
C rassal değişkeni "rastgele seçilen bir rengin RGB değerlerinin ortalamasını" belirtiyor olsun. Bu durumda her rengin
bir RGB ortalaması vardır. Bu fonksiyon belli bir rengi alıp onun ortalamasını belirten bir sayıya eşlemektedir.
Rassal değişkenler kümeler üzerinde işlemler yapmak yerine gerçek sayılar üzerinde işlem yapmamızı sağlayan, anlatımlarda
ve gösterimlerde kolaylık sağlayan bir kavramdır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Her rassal değişkenin belli bir değer almasının bir olasılığı vardır. Örneğin "iki zarın atılması deneyinde üste gelen sayılar
toplamına ilişkin" R rassal değişkenini düşünelim. P(R = 5) demek S örnek uzayındaki 5 değerini veren kümenin olsılığı demektir.
Yani P(R = 5) ile aslında P({(1, 4), (4, 1) (2, 3), (3, 2)}) aynı anlamdadır. Buradaki P({(1, 4), (4, 1) (2, 3), (3, 2)}) olasılığın
"bu değerlerden herhangi birinin oluşmasına yönelik" olasılık olduğunu anımsayınız. Bu olasılığın değeri 1/9'dur.
Bir rassal değişkenin olasılığı belirtilirken P harfi olsılığı anlatmaktadır. Ancak bu P harfinden sonra bazı kişiler normal
parantezleri bazı kişiler küme parantezlerini tercih ederler. Yani örneğin bazı kişiler P(R = 5) gibi bir gösterimi tercih
derken bazı kişiler P{R = 5} gösterimini tercih etmektedir. Küme parantezli gösterim aslında "R = 5" ifadesinin bir küme
belirttiğini ve bu kümenin olasılığının hesaplanmak istediğini" belirtmesi açısından daha doğal gibi gözükmektedir.
K rassal değişkeni rastgele seçilen bir insanın kilosunu belirtiyor olsun. Bu durumda K aslında tüm insanları tek tek
onların kilolarına eşleyen bir fonksiyondur. O halde P{K < 60} olasılığı aslında "rastgele seçilen bir kişinin kilosunun
60'tan küçük olması olasılığı" anlamına gelmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Rassal değişkenler tıpkı matematiksel diğer fonksiyonlarda olduğu gibi "kesikli (discrete)" ya da "sürekli (continuous)"
olabilmektedir. Eğer bir rassal değişken (yani fonksiyon) teorik olarak belli bir aralıkta tüm gerçek sayı değerlerini
alabiliyorsa böyle rassal değişkenlere "sürekli (continous)" rassal değişkenler denilmektedir. Ancak bir rassal değişken
belli bir aralıkta yalnızca belli gerçek sayı değerlerini alabiliyorsa bu rassal değişkenlere "kesikli (discrete)" rassal
değişkenler denilmektedir. Örneğin "iki zarın atılmasında üste gelen sayılar toplamını belirten R rassal değişkeni" kesiklidir.
Çünkü yalnızca belli değerleri alabilmektedir. Ancak "rastgele seçilen bir kişinin kilosunu belirten" K rassal değişkeni
süreklidir. Çünkü teorik olarak belli bir aralıkta tüm gerçek değerleri alabilir. (Biz kişilerin kilolarını yuvarlayarak ifade
etmekteyiz. Ancak aslında onların kiloları belli aralıktaki tüm gerçek değerlerden biri olabilir.)
Sürekli rassal değişkenlerin noktasal olasılıkları 0'dır. Örneğin "rastgele seçilen kişinin kilosunu" belirten K rassal
değişkeni söz konusu olsun. P{K = 67} gibi bir olasılık aslında 0'dır. Çünkü gerçek sayı ekseninde sonsuz tane nokta vardır.
67 yalnızca bu sonsuz noktadan bir tanesini belirtir. Sayı / sonsuz da 0'dır. Ancak sürekli rassal değişkenlerin aralıksal
olasılıkları 0 olmak zorunda değildir. Yani örneğin P{66 < K < 67} olasılığı 0 değildir. Burada {66 < K < 67} kümesinin de
elemanlarının sonsuz sayıda olduğuna dikkat ediniz. Artık bu hesap matematikte "limit, türev, integral" konularıyla
ilişkili hale gelmektedir. Sürekli rassal değişkenlerin aralıksal olasılıklarını belirtirken aralıklardaki '=' sembolünün
bir anlamının olmayacağına dikkat ediniz. Örneğin P{66 < K < 67} ile P{66 <= K <= 67} arasında aslında bir fark yoktur.
Bu konuda da kişiler farklı gösterimleri tercih edebilmektedir. Bazı kişiler bir tarafa '=' sembolünü koyup diğer tarafa
koymamaktadır. Örneğin P{66 <= K < 67} gibi. Ancak bu '=' sembollerinin sürekli rassal değişkenlerde bir etkisi yoktur.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay zeka ve makine öğrenmesinde sürekli rassal değişkenler daha fazla karşımıza çıkmaktadır. Bu nedenle biz sürekli rassal
değişkenler ve onların olasılıkları üzerinde biraz daha duracağız.
Sürekli bir rassal değişkenin aralıksal olasılıklarını hesaplama aslında bir "intergral" hesabı akla getirmektedir. İşte
sürekli rassal değişkenlrin aralıksal olasılıklarının hesaplanması için kullanılan fonksiyonlara "olasılık yoğunluk
fonksiyonları (probability density functions)" denilmektedir. Birisi bize bir rassal değişkenin belli bir aralıktaki
olasılığını soruyorsa o kişiin bize o rassal değişkene ilişkin "olasılık yoğunluk fonksiyonunu" vermiş olması gerekir.
Biz de örneğin P{x0 < X < x1} olasılığını x0'dan x1'e f(x)'in integrali ile elde ederiz.
Bir fonksiyonun olasılık yoğunluk fonksiyonu olabilmesi için eksi sonsuzdan artı sonsuze integralinin (yani tüm eğri altında
kalan alanın) 1 olması gerekir. Bir rassal değişkenin olasılık yoğunluk fonksiyonuna "o rassal değişkenin dağılımı" da
denilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi mademki bizim bir rassal değişkenin aralıksal olasılığını elde etmek için bir olasılık yoğunluk fonksiyonuna ihtiyacımız
var o halde bu fonksiyonu nasıl elde edeceğiz? İşte pek çok rassal değişkenin olasılık yoğunluk fonksiyonunun bazı kalıplara
uyduğu görülmektedir. Örneğin doğadaki pek çok olgunun (boy gibi, kilo gibi, zeka gibi) olasılık yoğunluk fonksiyonu
"normal dağılım" denilen dağılıma uymaktadır. Normal dağılım eğrisine "Gauss eğrisi" ya da "çan eğrisi" de denilmektedir.
Ancak yukarıda da belirttiğimiz gibi aslında çeşitli olaylara uygulanabilecek değişik olasılık yoğunluk fonksiyonu kalıpları
belirlenmiştir. Tabii bu fonksiyon eğrileri birer kalıptır. Dolayısıyla bazı parametrelere de sahiptir. Örneğin sonsuz sayıda
Gauss eğrisi oluşturulabilir. Gauss eğtisinin iki öenmli parametrik bilgisi "orta noktasını belirten "ortalama" ve zayıflığını
ya da şişmanlığını belirten "standart sapma" değeridir.
Bu durumda örneğin sürekli rassal değişkenlerle ilgili bir soru şöyle olabilir: "Kişlerin zekaları ortalaması 100 standart
sapması 15 olan normal dağılıma uygundur. Rastgele seçilen bir kişinin zekasının 120 ile 130 arasında olma olasılığı nedir?"
Burada bize rassal değişkenin olasılık yoğunluk fonksiyonu parametreleriyle verilmiştir. Bizim de bu olasılığı hesaplamak
için tek yapacağımız şey 120'den 130'a fonksiyonun integralini hesaplamaktır.
Pekiyi rassal değişkenimiz başkaları tarafından belirlenen herhangi bir kalıba uymuyorsa ne yapabiliriz? Bir rassal
değişkenin olasılık yoğunluk fonksiyonunun sıfırdan çıkarılması biraz zahmetli bir işlemdir. Kümülatif olasılıklar yardımıyla
olasılık yoğunluk fonksiyonları tatmin edici bir biçimde elde edilebilmektedir. Ancak yukarıda da belirttiğimiz gibi
aslında pek çok olay zaten tespit edilmiş çeşitli kalıplara uymaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
14. Ders - 11/02/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Doğada en fazla karşılaşılan sürekli dağılım "normal dağılım (normal distribution)" denilen dağılımdır. Bu dağılımın olasılık
yoğunluk fonksiyonu çan eğrisine benzemektedir. Normal dağılımın iki parametresi vardır: "Ortalama" ve "standart sapma".
Ortalama çan eğrisinin orta noktasını belirler. Standart sapma ise eğrinin zayıf ya da şişman olması üzerinde etkili olur.
Çan eğrisine teknik olarak "Gauss Eğrisi", Gauss eğrisine ilişkin olasılık yoğunluk fonksiyonuna da "Gauss fonksiyonu"
denilmektedir.
Gauss fonksiyonu simetrik bir fonksiyondur. Bu fonksiyon ortalama etrafında büyük bir alan kaplar, iki uca gidildikçe
hızlı bir düşüş yaşanır. Ancak eğri hiçbir zaman X ekseni ile kesişmemektedir. Yani eğri iki taraftan y değeri olarak
sıfıra oldukça yaklaşır, ancak sıfır olmaz.
Gauss fonksiyonu bir olasılık yoğunluk fonksiyonu belirttiğine göre fonksiyonun toplam eğri altında alanı 1'dir. Fonksiyon
simetirk olduğuna göre ortalamanın iki yanında eğri altında kalan alan 0.5'tir.
Değişik ortalama ve standart sapmaya ilişkin sonsuz sayıda Gauss eğrisi çizilebilir. Ortalaması 0, standart sapması 1 olan
normal dağılıma "standart normal dağılım" da denilmektedir. Genellikle istatistiktre standart normal dağılımdaki X değerlerine
"Z değerleri" denilmektedir.
Aşağıdaki örnekte Gauss eğrisi çizdirilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
def gauss(x, mu = 0, std = 1):
return 1 / (std * np.sqrt(2 * np.pi)) * np.e ** (-0.5 * ((x - mu) / std) ** 2)
x = np.linspace(-5, 5, 1000)
y = gauss(x)
plt.title('Gauss Function')
plt.plot(x, y)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Yukarıdaki çizimde eksenleri kartezyen koordinat sistemindeki gibi de gösterebiliriz.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
def gauss(x, mu = 0, std = 1):
return 1 / (std * np.sqrt(2 * np.pi)) * np.e ** (-0.5 * ((x - mu) / std) ** 2)
def draw_gauss(mu = 0, std = 1):
x = np.linspace(-5 * std + mu, 5 * std + mu, 1000)
y = gauss(x, mu, std)
mu_y = gauss(mu, mu, std)
plt.figure(figsize=(10, 4))
plt.title('Gauss Function', pad=10, fontweight='bold')
axis = plt.gca()
axis.set_ylim([-mu_y * 1.1, mu_y * 1.1])
axis.set_xlim([-5 * std + mu, 5 * std + mu])
axis.set_xticks(np.arange(-4 * std + mu, 5 * std + mu, std))
# axis.set_yticks(np.round(np.arange(-mu_y, mu_y, mu_y / 10), 2))
axis.spines['left'].set_position('center')
axis.spines['top'].set_color(None)
axis.spines['bottom'].set_position('center')
axis.spines['right'].set_color(None)
axis.plot(x, y)
plt.show()
draw_gauss(100, 15)
#----------------------------------------------------------------------------------------------------------------------------
Normal dağılımda eğri altında kalan toplam alanın 1 olduğunu belirtmiştik. Bu dağılımda toplaşmanın ortalama civarında
olduğu eğirinin şeklinden anlaşılmaktadır. Gerçektende normal dağılımda ortalamadan bir standart sapma soldan ve sağdan
kaplanan alan yani P{mu - std < X < mu + std} olasılı 0.68.27, ortalamadan iki standart sapma soldan ve sağdan kaplanan
alan yani P{mu - std * 2 < X < mu + std * 2} olasılığı 95.45 biçimindedir.
Matplotlib'te bir eğrinin altındaki alanı boyamak için fill_between isimli fonksiyon kullanılmaktadır. Bu fonksiyon axis
sınıfının bir metodu olarak da bulundurulmuştur. Aşağıdaki örnekte eğrinin altındaki belli bir alan fill_between metodu
ile boyanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
def gauss(x, mu = 0, std = 1):
return 1 / (std * np.sqrt(2 * np.pi)) * np.e ** (-0.5 * ((x - mu) / std) ** 2)
def draw_gauss(mu = 0, std = 1, fstart= 0, fstop = 0):
x = np.linspace(-5 * std + mu, 5 * std + mu, 1000)
y = gauss(x, mu, std)
mu_y = gauss(mu, mu, std)
plt.figure(figsize=(10, 4))
plt.title('Gauss Function', pad=10, fontweight='bold')
axis = plt.gca()
axis.set_ylim([-mu_y * 1.1, mu_y * 1.1])
axis.set_xlim([-5 * std + mu, 5 * std + mu])
axis.set_xticks(np.arange(-4 * std + mu, 5 * std + mu, std))
# axis.set_yticks(np.round(np.arange(-mu_y, mu_y, mu_y / 10), 2))
axis.spines['left'].set_position('center')
axis.spines['top'].set_color(None)
axis.spines['bottom'].set_position('center')
axis.spines['right'].set_color(None)
axis.plot(x, y)
x = np.linspace(fstart, fstop, 1000)
y = gauss(x, mu, std)
axis.fill_between(x, y)
plt.show()
draw_gauss(100, 15, 85, 115)
#----------------------------------------------------------------------------------------------------------------------------
Kümülatif dağılım fonksiyonu (cummulative distribution function) belli bir değere kadar tüm birikimli olsılıkları veren
fonksiyondur. Genellikle F harfi gösterilmektedir. Örneğin F(x0) aslında P{X < x0} anlamına gelmektedir. Normal dağılımda
F(x0) değeri aslında eğride X değerinin x0 olduğu noktanın solundaki tüm eğri altında kalan alanı belirtmektedir. (Başka bir
deyişle sürekli dağılımlarda F(x0) değeri "-sonsuzdan x0'a kadar olasılık yoğunluk fonksiyonunun integraline eşittir)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda da belirttiğimiz gibi normal dağılım istatistikte çok önemli bir sürekli dağılımdır. Normal dağılımın önemi
"merkezi limit teoremi (central limit theorem)" ile daha iyi anlaşılabilir. Merkezi limit teoremini izleyen paaragraflarda
ele alacağız. Çıkarımsal istatistiğin dayandığı temel merkezi limit teoremi olduğu için normal dağılım da çok önemli olmaktadır.
Bizim de Python programcısı olarak normal dağılım üzerinde aşağıdaki dört işlemi yapabiliyor olmamız gerekir:
1) Belli bir x değeri için eksi sonsuzdan o x değerine kadar eğri altında kalan alanı bulmak. Yukarıda da belirttiğimiz gibi
aslında bu alanı veren fonksiyona istatistikte "kümaltif dağılım fonksiyonu (cummulative distribution function)" denmektedir.
O halde aslında P{x0 < X < x1} olasılığı da F(x1) - F(x0) ile aynıdır.
2) Yukarıdaki işlemin tersi olan işlem. Yani bize kümülatif dağılım fonksiyonundan elde edilen değer verilmiş olabilir. Bizden
buna ilişkin x değeri istenebilir.
3) Belli bir x değeri için Gauss fonksiyon değerinin elde edilmesi. (Yani belli bir x değeri için olasılık yoğunluk fonksiyonunun
değerinin elde edilmesi)
4) Normal dağılıma uygun rastgele sayıların üretilmesi. Aslında bunun için 0 ile 1 arasında rasgele sayı üretilip onu 2'inci
maddede belirtelen işleme sokarsak normal dağılmış rastgele sayı elde edebiliriz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Normal dağılımla ilgili işlemleri yapabilmek için Python standart kütüphanesinde statistics modülü içerisinde NormalDist
isimli bir sınıf bulundurulmuştur. Programcı bu sınıf türünden bir nesne yaratır. İşlemlerini bu sınıfın metotlarıyla yapar.
NormalDist nesnesi yaratılırken __init__ metodu için ortalama ve standart sapma değerleri girilir. (Bu değerler girilmezse
ortalama için sıfır, standart sapma için 1 default değerleri kullanılmaktadır.) Örneğin:
import statistics
nd = statistics.NormalDist(100, 15)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
NormalDist sınıfının cdf (cummulative distribution function) isimli metodu verilen x değeri için eğrinin solunda kalan
toplam alanı yani kümülatif olasılığı bize vermektedir. Örneğin standart normal dağılımda x = 0'ın solunda alan 0.5'tir.
#----------------------------------------------------------------------------------------------------------------------------
import statistics
nd = statistics.NormalDist()
result = nd.cdf(0)
print(result) # 0.5
#----------------------------------------------------------------------------------------------------------------------------
Örneğin biz ortalaması 100, standart sapması 15 olan bir normal dağılımda P{130 < X < 140} olasılığını aşağıdaki gibi
elde edebiliriz:
nd = statistics.NormalDist(100, 15)
result = nd.cdf(140) - nd.cdf(130)
Şöyle bir soru sorulduğunu düşünelim: "İnsanların zekaları ortalaması 100, standart sapması 15 olan normal dağılıma uygundur.
Bu durumda zeka puanı 140'ın yukarısında olanların toplumdaki yüzdesi nedir?". Bu soruda istenen şey aslında normal dağılımdaki
P{X > 140} olasılığıdır. Yani x ekseninde belli bir noktanın sağındaki alan sorulmaktadır. Bu alanı veren doğrudan bir fonksiyon
olmadığı için bu işlem 1 - F(140) biçiminde ele alınarak sonuç elde edilebilir. Yani örneğin:
nd = statistics.NormalDist(100, 15)
result = 1 - nd.cdf(140)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
import statistics
nd = statistics.NormalDist(100, 15)
result = nd.cdf(140) - nd.cdf(130)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda da belirttiğimiz gibi bir normal dağılımda iki x arasındaki alanı yani P{x1 < X <= x2} olasılığını biz cdf
fonksiyonuyla F(x2) - F(x1) işlemi ile elde edebiliriz. Örneğin ortalaması 100 standart sapması 15 olan normal doğılımda
120 ile 130 arasındaki olasılık aaşağıdaki gibi hesaplanıp grafiği çizdirilebilir.
#----------------------------------------------------------------------------------------------------------------------------
import statistics
import numpy as np
nd = statistics.NormalDist(100, 15)
result = nd.cdf(130) - nd.cdf(120)
print(result)
import matplotlib.pyplot as plt
plt.title('P{120 < x <= 130} Olasılığı ', pad=20, fontsize=14, fontweight='bold')
x = np.linspace(40, 160, 1000)
y = [nd.pdf(val) for val in x]
axis = plt.gca()
axis.set_ylim(-0.030, 0.030)
axis.spines['left'].set_position('center')
axis.spines['bottom'].set_position('center')
axis.spines['top'].set_color(None)
axis.spines['right'].set_color(None)
axis.set_xticks(range(40, 170, 10))
plt.plot(x, y)
x = np.linspace(120, 130, 100)
y = [nd.pdf(val) for val in x]
axis.fill_between(x, y)
plt.text(120, -0.01, f'P{{120 < x <= 130}} = {result:.3f}', color='blue', fontsize=14)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Eskiden bilgisayarların bu kadar yoğun kullanılmadığı zamanlarda standart normal dağılım için "Z Tabloları" düzenlenmekteydi.
Bu tablolar belli bir Z değeri için (standart normal dağılımdaki X değerlerine Z değeri dendiğini anımsayınız) kümülatif dağılım
fonksiyonun değerini vermektedir. Örnek bir Z tablosu aşağıdakine benzemektedir:
0.00 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09
0.0 0.5000 0.5040 0.5080 0.5120 0.5160 0.5199 0.5239 0.5279 0.5319 0.5359
0.1 0.5398 0.5438 0.5478 0.5517 0.5557 0.5596 0.5636 0.5675 0.5714 0.5753
0.2 0.5793 0.5832 0.5871 0.5910 0.5948 0.5987 0.6026 0.6064 0.6103 0.6141
0.3 0.6179 0.6217 0.6255 0.6293 0.6331 0.6368 0.6406 0.6443 0.6480 0.6517
0.4 0.6554 0.6591 0.6628 0.6664 0.6700 0.6736 0.6772 0.6808 0.6844 0.6879
0.5 0.6915 0.6950 0.6985 0.7019 0.7054 0.7088 0.7123 0.7157 0.7190 0.7224
0.6 0.7257 0.7291 0.7324 0.7357 0.7389 0.7422 0.7454 0.7486 0.7517 0.7549
0.7 0.7580 0.7611 0.7642 0.7673 0.7704 0.7734 0.7764 0.7794 0.7823 0.7852
0.8 0.7881 0.7910 0.7939 0.7967 0.7995 0.8023 0.8051 0.8078 0.8106 0.8133
0.9 0.8159 0.8186 0.8212 0.8238 0.8264 0.8289 0.8315 0.8340 0.8365 0.8389
1.0 0.8413 0.8438 0.8461 0.8485 0.8508 0.8531 0.8554 0.8577 0.8599 0.8621
1.1 0.8643 0.8665 0.8686 0.8708 0.8729 0.8749 0.8770 0.8790 0.8810 0.8830
1.2 0.8849 0.8869 0.8888 0.8907 0.8925 0.8944 0.8962 0.8980 0.8997 0.9015
......................................................................................
Bu tabloda ilgili hücredeki değer o Z değerinin kümülatif olasılığını vermektedir. Tabii artık bir bilgisayar programcısının
böyle bir tablo kullanmasına gerek yoktur. Yukarıdaki gibi tablolar yalnızca belli kesikliği değerileri yuvarlayarak vermektedir
Pekiyi bu Z tabloları standart normal dağılıma göre hazırlandığına göre biz belli bir ortalama ve standart sapmaya ilişkin
kümülatif olaılığı bu tablo yoluyla nasıl elde edebilmekteyiz? İşte aslında herhangi bir normal dağılım standart normal dağılıma
transpoze edilebilmektedir. Ortalaması mu standart sapması std olan normal dağılımdaki x değerinin standart normal dağılımdaki
Z değeri şöyle hesaplanmaktadır:
Z = (x - mu) / std
Örneğin ortalaması 100, standart sapması 15 olan normal dağılımdaki x = 140 değerinin standart normal dağılımdaki Z
değeri 2.66'dır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Belli bir kümülatif olasılık değeri için x değerinin bulunması işlemi de NormalDist sınıfının inv_cdf metoduyla yapılmaktadır.
Örneğin standart normal dağılımda 0.99 olan kümülatif olasılığın Z değeri aşağıdaki gibi bulunabilir:
nd = statistics.NormalDist()
result = nd.inv_cdf(0.99)
#----------------------------------------------------------------------------------------------------------------------------
import statistics
nd = statistics.NormalDist()
result = nd.inv_cdf(0.99)
print(result) # 2.32
#----------------------------------------------------------------------------------------------------------------------------
Belli bir x değeri için Gauss fonksiyonunda ona karşı gelen y değeri sınıfın pdf metduyla elde edilmektedir. Örneğin x = 0
için standart normal dağılımda Gauss fonksiyonu değerini aşağıdaki gibi elde edebiliriz:
nd = statistics.NormalDist()
result = nd.pdf(0)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
import statistics
nd = statistics.NormalDist()
result = nd.pdf(0)
print(result) # 0.3989422804014327
#----------------------------------------------------------------------------------------------------------------------------
Normal dağılmış rasgele n tane sayı üretmek için NormalDist sınıfının samples isimli metodu kullanılmaktadır. Bu metot
bize bir liste olarak n tane float değer verir. Örneğin:
nd = statistics.NormalDist()
result = nd.samples(10)
Bu işlemden biz normal dağılmış 10 tane rasgele değerden oluşan bir liste elde ederiz.
#----------------------------------------------------------------------------------------------------------------------------
import statistics
nd = statistics.NormalDist()
result = nd.samples(10)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Biz normal dağılmış rastgele sayılardan histogram çizersek histogramımızın Gauss eğrisine benzemesi gerekir.
#----------------------------------------------------------------------------------------------------------------------------
import statistics
nd = statistics.NormalDist()
result = nd.samples(10000)
import matplotlib.pyplot as plt
plt.hist(result, bins=20)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi normal dağılmış rastgele sayı üretme işlemini samples metodu nasıl yapmaktadır? Aslında klasik yöntem önce [0, 1]
aralığında rastgele sayı üretim bunu kümülatif dağılım olarak kabul etmek ve bu kümülatif dağılımın x değerini hesaplamaktır.
#----------------------------------------------------------------------------------------------------------------------------
import random
import statistics
nd = statistics.NormalDist(100, 15)
result = [nd.inv_cdf(random.random()) for _ in range(10000)]
import matplotlib.pyplot as plt
plt.hist(result, bins=30)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aslında normal dağılmış rasgele sayı üretmek için standart random modülü içerisinde de gauss isimli bir fonksiyon
bulundurulmuştur. Ancak bu fonksiyon bir tane rastgele sayı vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
import random
result = [random.gauss(100, 15) for _ in range(10000)]
import matplotlib.pyplot as plt
plt.hist(result, bins=30)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Python'ın statistics modülündeki NormalDist sınıfı vektörel işlemler yapamamaktadır. Maalesef NumPy ve Pandas kütüphanelerinde
normal dağılım üzerinde vektörel işlem yapan öğeler yoktur. Ancak SciPy kütüphanesi içerisinde pek çok dağılım üzerinde
vektörel işlemler yapan sınıflar bulunmaktadır. Bu nedenle pratikte Python kütüphanesi yerine bu tür işlemler için SciPy
kütüphanesi tercih edilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
scipy.stats modülü içerisindeki "norm" isimli singleton nesne normal dağılım üzerinde vektörel işlem yapan metotlara sahiptir.
norm bir sınıf nesnesidir ve zaten yaratılmış bir biçimde bulunmaktadır. Dolayısıyla programcı doğrudan bu nesne ile ilgili
sınıfın metotlarını çağırabilir. Genellikle programcılar bu tür nesneleri kullanmak için "from import" deyimini tercih ederler:
from scipy.stats import norm
norm nesnesine ilişkin sınıfın cdf isimli metodu üç parametre almaktadır:
cdf(x, loc=0, scale=1)
Buradaki x bir NumPy dizisi ya da Python dolaşılabilir nesnesi olabilir. Bu durumda tüm x değerlerinin kümülatif olasılıkları
hesaplanıp bir NumPy dizisi olarak verilmektedir. Burada loc ortalamayı, scale ise standart sapmayı belirtmektedir. Örneğin:
result = norm.cdf([100, 130, 140], 100, 15)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
from scipy.stats import norm
result = norm.cdf([100, 130, 140], 100, 15)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
norm nesnesinin ilişkin olduğu sınıfın ppf (percentage point function) isimli metodu cdf işleminin tersini yapmaktadır.
Yani kümülatif olasılığı bilindiği durumda bize bu kümalatif olasılığa karşı gelen x değerini verir. (Yani ppf NormalDist
sınıfındaki inv_cdf metoduna karşılık gelmektedir.):
ppf(q, loc=0, scale=1)
ppf (percentage point function) ismi size biraz tuhaf gelebilir. Bu isim birikimli dağılım fonksiyonunun tersini belirtmek
için kullanılmaktadır. ppf aslında "medyan (median)" kavramının genel biçimidir. Anımsanacağı gibi medyan ortadan ikiye bölen
noktayı belirtiyordu. Örneğin standart normal dağılımda medyan 0'dır. Yani ortalamaya eşittir. İstatistikte tam ortadan
bölen değil de diğer noktalardan bölen değerler için "percentage point" de denilmektedir. Örneğin normal dağılımda 1/4
noktasından bölen değer aslında birikimli dağılm fonksiyonunun 0.25 için değeridir.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.stats import norm
result = norm.ppf([0.50, 0.68, 0.95], 100, 15)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
norm nesnesinin ilişkin olduğu sınıfın pdf (probability density function) isimli metodu yine x değerlerinin Gaus eğrisindeki
y değerlerini vermektedir. Metodun parametrik yapısı şöyledir:
pdf(x, loc=0, scale=1)
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt
x = np.linspace(40, 160, 1000)
y = norm.pdf(x, 100, 15)
plt.plot(x, y)
x = np.full(200, 100) # 200 tane 100'lerden oluşan dizi
yend = norm.pdf(100, 100, 15)
y = np.linspace(0, yend, 200)
plt.plot(x, y, linestyle='--')
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
norm nesnesinin ilişkin olduğu sınıfın rvs metodu ise normal dağılıma ilişkin rassal sayı üretmek için kullanılmaktadır.
Metodun parametrik yapısı şöyledir:
rvs(loc=0, scale=1, size=1)
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt
x = norm.rvs(100, 15, 10000)
plt.hist(x, bins=20)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Normal dağılımda ortalamadan birer standart sapma arasındaki bölgenin olasılığı, yani P{mu - sigma < X < mu + sigma} olasılığı
0.68 civarındadır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt
result = norm.cdf(1) - norm.cdf(-1)
print(result)
x = np.linspace(-5, 5, 1000)
y = norm.pdf(x)
plt.title('Ortalamadan 1 Standart Sapma Arası Bölge', fontweight='bold')
axis = plt.gca()
axis.set_ylim(-0.5, 0.5)
axis.spines['left'].set_position('center')
axis.spines['bottom'].set_position('center')
axis.spines['top'].set_color(None)
axis.spines['right'].set_color(None)
axis.set_xticks(range(-4, 5))
axis.text(2, 0.3, f'{result:.3f}', fontsize=14, fontweight='bold')
plt.plot(x, y)
x = np.linspace(-1, 1, 1000)
y = norm.pdf(x)
plt.fill_between(x, y)
axis.arrow(2.5, 0.25, -2, -0.1, width=0.0255)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Normal dağılımda ortalamadan iki standart sapma arasındaki bölgenin olasılığı, yani P{mu - 2 * sigma < X < mu + 2 * sigma}
olasılığı 0.95 civarındadır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt
result = norm.cdf(2) - norm.cdf(-2)
print(result)
x = np.linspace(-5, 5, 1000)
y = norm.pdf(x)
plt.title('Ortalamadan 2 Standart Sapma Arası Bölge', fontweight='bold')
axis = plt.gca()
axis = plt.gca()
axis.set_ylim(-0.5, 0.5)
axis.spines['left'].set_position('center')
axis.spines['bottom'].set_position('center')
axis.spines['top'].set_color(None)
axis.spines['right'].set_color(None)
axis.set_xticks(range(-4, 5))
axis.text(2, 0.3, f'{result:.3f}', fontsize=14, fontweight='bold')
plt.plot(x, y)
x = np.linspace(-2, 2, 1000)
y = norm.pdf(x)
plt.fill_between(x, y)
axis.arrow(2.5, 0.25, -2, -0.1, width=0.0255)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Normal dağılımda ortalamadan üç standart sapma arasındaki bölgenin olasılığı, yani P{mu - 3 * sigma < X < mu + 3 * sigma}
olasılığı 0.997 civarındadır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt
result = norm.cdf(3) - norm.cdf(-3)
print(result)
x = np.linspace(-5, 5, 1000)
y = norm.pdf(x)
axis = plt.gca()
axis.set_ylim(-0.5, 0.5)
axis.spines['left'].set_position('center')
axis.spines['bottom'].set_position('center')
axis.spines['top'].set_color(None)
axis.spines['right'].set_color(None)
axis.set_xticks(range(-4, 5))
axis.text(2, 0.3, f'{result:.3f}', fontsize=14, fontweight='bold')
plt.plot(x, y)
x = np.linspace(-3, 3, 1000)
y = norm.pdf(x)
plt.fill_between(x, y)
axis.arrow(2.5, 0.25, -2, -0.1, width=0.0255)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Diğer çok karşılaşılan sürekli dağılım "sürekli düzgün dağılım (continous uniform distribution)" denilen dağılımdır.
Burada dağılımı temsil eden a ve b değerleri vardır. Sürekli düzgün dağılımın olasılık yoğunluk fonksiyonu dikdörtgensel
bir alandır. Dolayısıyla kümülatif dağılım fonksiyonu x değeriyle orantılı bir değer vermektedir. Sürekli düzgün dağılımın
olasılık yoğunluk fonksiyonu şöyle ifade edilebilir:
f(x) = {
1 / (b - a) a < x < b
0 diğer durumlarda
}
Sürekli düzgün dağılım için Python'ın standart kütüphanesinde bir sınıf bulunmamaktadır. NumPy'da da böyle bir sınıf yoktur.
Ancak SciPy içerisinde stats modülünde uniform isimli bir singleton nesne bulunmaktadır. Bu nesneye ilişkin sınıfın yine
cdf, ppf, pdf ve rvs metotları vardır. Bu metotlar sırasıyl a değerini ve a'dan uzunluğu parametre olarak almaktadır.
Örneğin:
result = uniform.pdf(15, 10, 10)
Burada aslında a = 10, b = 20 olan bir sürekli düzgün dağılımdaki olasılık yoğunluk fonksiyon değeri elde edilmektedir.
Tabii aslında 10 ile 20 arasındaki tüm olasılık yoğunluk fonksiyon değerleri 1 / 10 olacaktır. Örneğin:
result = uniform.cdf(15, 10, 10)
Burada a = 10, b = 20 olan bir sürekli düzgün dağılımda 15'in solundaki alan elde edilecektir. 15 burada orta nokta olduğunda
göre elde edilecek bu değer 0.5'tir. uniform nesnesinin metotlarındaki ikinci parametrenin (loc) a değeri olduğuna ancak üçüncü
parametrenin a'dan uzaklık belirttiğine (scale) dikkat ediniz. Bu dağılım parametrik bilgileri a ve b olsa da SciPy stats modülünde
tüm dağılımlar için ortak bir parametrik yapı tercih edildiğinden dolayı böyle bir yola gidilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import uniform
import matplotlib.pyplot as plt
A = 10
B = 20
x = np.linspace(A - 5, B + 5, 1000)
y = uniform.pdf(x, A, B - A)
plt.title('Continupos Uniform Distribution', fontweight='bold')
plt.plot(x, y)
x = np.linspace(10, 12.5, 1000)
y = uniform.pdf(x, A, B - A)
plt.fill_between(x, y)
plt.show()
result = uniform.cdf(12.5, A, B - A)
print(result) # 0.25
result = uniform.ppf(0.5, A, B - A)
print(result) # 15
#----------------------------------------------------------------------------------------------------------------------------
Düzgün dağılmış rastgele sayı aslında bizim aşina olduğumuz klasik rastgele sayı üretimidir. Örneğin Python standart kütüphanesindeki
random modülünde bulnan random fonksiyonu 0 ile 1 arasında rastgele bir sayı veriyordu. Aslında bu fonksiyon a = 0, b = 1 olan
düzgün dağılımda rastegele sayı veren fonksiyonla tamamne aynıdır. Bnezer biçimde NumPy'daki random modülündeki random fonksiyonu
0 ile 1 arasında düzgün dağılmış rastgele sayı üretmektedir. Örneğin:
result = uniform.rvs(10, 10, 10)
Burada a = 10, b = 20 olan sürekli düzgün dağılımda 10 tane rastgele noktalı sayı elde edilecektir.
Örneğin 100 ile 200 arasında rastgele 10 tane gerçek sayı üretmek istesek bu işlemi şöyle yapmalıyız:
uniform.rvs(100, 100, 10)
#----------------------------------------------------------------------------------------------------------------------------
from scipy.stats import uniform
import numpy as np
x = np.random.random(10)
print(x)
x = uniform.rvs(0, 1, 10) # yukarıdakiyle tamamen aynı biçimde rassal sayı üretir
print(x)
#----------------------------------------------------------------------------------------------------------------------------
Özellikle güven aralıklarında (confidence intervals) ve hipotez testlerinde (hypothesis testing) kullanılan diğer önemli
bir sürekli dağılım da "t dağılımı (t distribution)" denilen dağılımdır. t dağılımını bulan kişi makalesini "Student" takma
ismiyle yayınladığından dolayı bu dağılıma "Student's t distribution" da denilmektedir.
t dağılımı standart normal dağılıma oldukça benzemektedir. Bu dağılımın ortalaması 0'dır. Ancak standart sapması "serbestlik derecesi
(degrees of freedom)" denilen bir değere göre değişir. t dağılımının standart sapması sigma = karekök(df / (df - 2)) biçimindedir.
t dağılımın olasılık yoğunluk fonksiyonu biraz karmaşık bir görüntüdedir. Ancak fonksiyon standart normal dağılıma göre
"daha az yüksek ve biraz daha şişman" gibi gözükmektedir. t dağılımının serbestlik derecesi artırıldığında dağılım
standart normal dağılıma çok benzer hale gelir. Serbestlik derecesi >= 30 durumunda standart normal dağılımla oldukça örtüşmektedir.
Yani serbestlik derecesi >= 30 durumunda artık t dağılımı kullanmakla standart normal dağılım kullanmak arasında önemli bir
farklılık kalmamaktadır.
t dağılımı denildiğinde her zaman ortalaması 0 olan standart sapması 1 olan (bu konuda bazı ayrıntılar vardır) dağılım
anlaşılmaktadır. Tabii t dağılımı da eksende kaydırılabilir ve standart sapma değiştirilebilir.
t dağılımı teorik bir dağılımdır. Yukarıda da belirttiğimiz gibi özellikle "güven aralıklarının oluşturulması" ve "hipotez
testlerinde" kullanım alanı bulmaktadır. Bu tür durumlarda anakütle standart sapması bilinmediği zaman örnek standart sapması
anakütle standart sapması olarak kullanılmakta ve t dağılımından faydalanılmaktadır. Eskiden t dağılımı özellikle "anakütle
standart sapmasının bilinmediği ve örneklem büyüklüğünün 30'dan küçük olduğu durumlarda" kullanılıyordu. (Çünkü örneklem
büyüklüğü >= 30 olduğu durumda zaten t dağılımı standart normal dağılıma çok yaklaşmaktadır.) Ancak artık bilgisayarların
yoğun kullanıldığı bu zamanlarda örnek büyüklüğü >= 30 olsa bile t dağılımını kullanmak toplamda çok küçük bir farklılık
oluştursa tercih edilebilmektedir.
t dağılımının önemli bir parametresi "serbestlik derecesi (degrees of freedom)" denilen parametresidir. Serbestlik derecesi
"örneklem büyüklüğünden bir eksik değeri" belirtir. Örneğin örneklem büyüklüğü 10 ise serbestlik derecesi 9'dur. Serbestlik
derecesi (dolayısıyla örneklem büyüklüğü) artırıldıkça t dağılımı standart normal dağılıma benzer. Yukarıda da belirttiğimiz
gibi n >= 30 durumunda artık yavaş yavaş t dağılımının standart normal dağılımdan önemli bir farkı kalmamaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
t dağılımına ilişkin Python standart kütüphanesinde bir sınıf yoktur. NumPy kütüphanesinde de t dağılımına ilişkin bir öğe
bulunmamaktadır. Ancak SciPy kütüphanesindeki stats modülünde t isimli singleton nesne t dağılımı ile işlem yapmak için
kullanılmaktadır. t isimli singleton nesnenin metotları norm nesnesinin metotlarıyla aynıdır. Bu fonksiyonlar genel olarak
önce x değerlerini sonra serbestlik derecesini, sonra da ortalama değeri ve standart sapma değerini parametre olarak almaktadır.
Ancak yukarıda da belirttiğimiz gibi t dağılımı denildiğinde genel olarak ortalaması 0, standart sapması 1 olan t dağılımı
anlaşılır.
Aşağıdaki programda standart normal dağılım ile 5 serbestlik derecesi ve 30 serbestlik derecesine ilişkin t dağılımlarının
olasılık yoğunluk fonksiyonları çizdirilmiştir. Burada özellikle 30 serbestlik derecesine ilişkin t dağılımının grafiğinin
standart normal dağılım grafiği ile örtüşmeye başladığına dikkat ediniz.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import norm, t
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 10))
x = np.linspace(-5, 5, 1000)
y = norm.pdf(x)
axis = plt.gca()
axis.set_ylim(-0.5, 0.5)
axis.spines['left'].set_position('center')
axis.spines['bottom'].set_position('center')
axis.spines['top'].set_color(None)
axis.spines['right'].set_color(None)
axis.set_xticks(range(-4, 5))
plt.plot(x, y)
y = t.pdf(x, 5)
plt.plot(x, y)
plt.legend(['Standart Normal Dağılım', 't Dağılımı (DOF = 5)', 't dağılımı (DOF = 30)'])
y = t.pdf(x, 30)
plt.plot(x, y, color='red')
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte standart normal dağılımla çeşitli serbestlik derecelerine ilişkin t dağılımlarının grafiği aynı eksen
üzerine çizilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.stats import norm, t
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-5, 5, 1000)
plt.figure(figsize=(15, 10))
plt.title('Değişik Serbestlik Derecelerine İlişkin t Dağılımı Grafikleri', fontweight='bold')
axis = plt.gca()
axis.set_ylim(-0.5, 0.5)
axis.spines['left'].set_position('center')
axis.spines['bottom'].set_position('center')
axis.spines['top'].set_color(None)
axis.spines['right'].set_color(None)
y_norm = norm.pdf(x)
plt.plot(x, y_norm, color='blue')
df_info = [(2, 'red'), (5, 'green'), (10, 'black')]
for df, color in df_info:
y_t = t.pdf(x, df)
plt.plot(x, y_t, color=color)
plt.legend(['Standart Normal Dağılım'] + [f'{t[0]} Serbestlik Derecesi' for t in df_info], fontsize=14)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Tabii standart normal dağılımla t dağılımının olasılık yoğunluk fonksiyonları farklı olduğuna göre aynı değerlere ilişkin
kümülatif olasılık değerleri de farklı olacaktır.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.stats import norm, t
x = [-0.5, 0, 1, 1.25]
result = norm.cdf(x)
print(result) # [0.30853754 0.5 0.84134475 0.89435023]
x = [-0.5, 0, 1, 1.25]
result = t.cdf(x, 5)
print(result) # [0.31914944 0.5 0.81839127 0.86669189]
#----------------------------------------------------------------------------------------------------------------------------
Biz bu konuda sürekli dağılım olarak yalnızca "normal dağılımı", "düzgün dağılımı" ve ""t dağılımını" inceledik. Aslında
değişik olayları modellemek için başka dağılımlar da kullanılmaktadır. Bunlardan bazılarını ileride başka konular içerisinde
ele alacağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Biz yukarıda bazı sürekli dağılımları inceledik. Kursumuzda genellikle sürekli dağılımları kullanacağız. Ancak bu noktada
kesikli (discrete) dağılımlar üzerinde de bazı bilgiler vermek istiyoruz.
Kesikli dağılımlarda X değerleri her gerçek değeri almamaktadır. Dolayısıyla bunların fonksiyonları çizildiğinde sürekli
fonksiyonlar elde edilemeyecek yalnızca noktalar elde edilecektir. Kesikli dağılımlarda X değerlerini onların olasılıklarına
eşleyen fonksiyonlara "olasılık kütle fonksiyonu (probability mass function)" denilmektedir. Sürekli rassal değişkenlerin
olasılık yoğunluk fonksiyonları integral hesap için kullanılırken kesikli rassal değişkenler için olasılık kütle fonksiyonları
doğrudan rassal değişkeninin ilgili noktadaki olasılığını elde etmek için kullanılmaktadır. Tabii olasılık kütle fonksiyonu
f(x) olmak üzere her x için f(x) değerlerinin toplamının yine 1 olması gerekmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Tıpkı sürekli rassak değişkenlerde olduğu gibi kesikli rassal değişkenlerde de çok sık karşılaşılan bazı olasılık dağılımları
vardır. En çok karşılaşılan kesikli dağılımlardan biri "poisson (genellikle "puason" biçiminde okumuyor)" dağılımıdır. Bu
kesikli dağılım adeta normal dağılımın kesikli versiyonu gibidir. Poisson dağılımının olasılık kütle fonksiyonu şöyledir:
P(X = x) = (e^-lambda * lambda^x) / x!
Dağılımın olasılık kütle fonksiyonu size biraz karşıkı gelebilir. Buradaki x olasılığını hesaplamak istediğimiz kesikli
değeri belirtmektedir. Lamda ise ortalama olay sayısını belirtmektedir. Lambda değeri ortalama belirttiği için gerçek
bir değer olabilir. Ancak x değerleri 0, 1, 2, ... n biçiminde 0 ve pozitif tamsayılardan oluşmaktadır.
Yukarıda da belirttiğimiz gibi poisson dağılımı adeta normal dağılımın kesikli hali gibidir. Dolayısıyla doğada da çok
karşılaşılan kesikli dağılımlardandır. Buradaki lamda değeri "ortalama olay sayısını" belirtmektedir. Örneğin lamda değeri
"bir futbol maçındaki ortalama gol sayısını" ya da "İstanbul'da meydana gelen ortalama trafik kazası sayısını" belirtiyor
olabilir. Bir olgunun poisson dağılımı ile temsil edilmesi aslında "ortalama civarında yüksek olasıkların bulunduğu, ortalamadan
iki yandan uzaklaştıkça olasılıkların çan eğrisi gibi düştüğü" bir durumu belirtmektedir.
Poisson dağılımı için de Python standart kütüphanesinde ya da Numpy ve Pandas kütüphanelerinde özel fonksiyonlar ve sınıflar
bulunmamaktadır. Ancak SciPy kütüphanesinin stats modülü içerisinde poisson isimli bir single nesne ile bu dağılımla ilgili
işlemler kolaylıkla yapılabilmektedir.
SciPy'da kesikli dağılımlar üzerinde işlemler yapan singleton nesneler sürekli dağılımlarla işlemler yapan single nesnelere
kullanım bakımından oldukça benzemektedir. Ancak kesikli dağılımlar için fonksiyonun ismi "pdf değil pmf" biçimindedir.
Burada "pmf" ismi "probability mass function" sözcükleridnen kısaltılmıştır.
SciPy'daki poisson nesnesinin fonksiyonları genel olarak bizden x değerini ve lambda değerini parametre olarak istemektedir.
Örneğin maçlardaki gol sayısının poisson dağılıma uyduğunu varsayalım. Maçlardaki ortalama gol sayısının 2 olduğunu kabul edelim.
Bu durumda bir maçta 5 gol olma olasılığı aşağıdaki gibi elde edilebilir:
result = poisson.pmf(5, 2)
print(result) # 0.03608940886309672
Yukarıdaki gibi poisson dağılımı sorularında genellikle soruyu soran kişi belli bir olayın ortalama gerçekleşme sayısını verir.
Sonra kişiden bazı değerleri bulmasını ister. Pekiyi soruda "bir maçta ikiden fazla gol olma olasılığı sorulsaydı biz
soruyu nasıl çözerdik? poisson nesnesi ile cdf fonksiyonunu çağırdığımızda bu cdf fonksiyonu bize x değerine kadarki
(x değeri de dahil olmak üzere) kümülatif olasılığı verecektir. Bu değeri de 1'den çıkartırsak istenen olasılığı elde edebiliriz:
result = 1 - poisson.cdf(2, 2)
print(result) # 0.3233235838169366
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda lamda değeri (ortalaması) 2 olan poisson dağılımı için saçılma grafiği çizdirilmiştir. Bu grafiğin normal dağılım
grafiğini andırmakla birlikte sağdan çarpık olduğuna dikkat ediniz.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.stats import poisson
import matplotlib.pyplot as plt
plt.title('Poisson Distribution with Lambda 2', fontweigght='bold')
x = range(0, 40)
y = poisson.pmf(x, 2)
plt.scatter(x, y)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Poisson dağılımında lamda değeri yüksek tutulursa saçılma grafiğinin Gauss eğrisine benzediğine dikkat ediniz.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.stats import poisson
import matplotlib.pyplot as plt
plt.title('Poisson Distribution with Lambda 100', fontweight='bold')
x = range(0, 200)
y = poisson.pmf(x, 100)
plt.scatter(x, y)
plt.show()
result = poisson.pmf(3, 4)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Bernoulli dağılımında X değeri 0 ya da 1 olabilir. X = 0 durumu bir olayın olumsuz olma ya da gerçekleşmeme olasılığını,
X = 1 durumu ise bir olayın olumlu olma ya da gerçekleşme olasılığını belirtmektedir. Bu rassal değişkenin yalnızca iki
değer aldığına dikkat ediniz. Sonucu iki değerden biri olan rassal deneylere "Bernoulli deneyleri" de denilmektedir. Örneğin
bir paranın atılması durumunda yazı ya da tura gelmesi iki durumlu bir deneydir. Dolayısıyla bir Bernoulli deneyidir.
Bernouli deneylerinde genellikle X = 1 durumundaki olasılık verilir. Biz de bu olasılığı 1'den çıkartarak X = 0 durumundaki
olasılığı elde ederiz. Bernoulli dağılımının olasılık kütle fonksiyonu şöyle ifade edilebilir:
P{X = x} = {
p X = 1 ise
1 - p X = 0 ise
}
Ya da bu olasılık kütle fonksiyonunu aşağıdaki gibi de ifade edebiliriz:
P{X = x} = p^x * (1 - p)^(1 - x)
Burada X = 0 için 1 - p değerinin X = 1 için p değerinin elde edildiğine dikkat ediniz.
Bernoulli dağılımı için de SciPy kütüphanesinde stats modülü içerisinde bernoulli isimli bir singleton nesne bulundurulmuştur.
Tabii bu dağılım çok basit olduğu için bu nesnenin kullanılması da genellikle gereksiz olmaktadır. bernoulli nesnesinin
ilikin olduğu sınıfın metotları bizden X değerini (0 ya da 1 olabilir) ve X = 1 için p değerini almaktadır. Örneğin:
bernoulli.pmf(0, 0.7)
buradan 0.3 değeri elde edilecektir.
----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Diğer çok karşılaşılan bir kesikli dağılımda "binom dağılımı" denilen dağılımdır. Binom dağılımında bir Bernoulli deneyi
(yani iki sonucu olan bir deney) toplam n defa yinelenmektedir. Bu n defa yinelenmede olayın tam olarak kaç defa olumlu
sonuçlanacağının olasılığı hesaplanmak istenmektedir. Dolayısıyla binom dağılımının olasılık kütle fonksiyonunda X değerleri
0, 1, 2, ... gibi tamsayı değerler alır. Örneğin bir para 5 kez atılıyor olsun. Biz de bu 5 kez para atımında tam olarak
3 kez Tura gelme olasılığını hesaplamak isteyelim. Burada paranın atılması bir Bernoulli deneyidir. Bu deney 5 kez yinelenmiş
olumlu kabul ettiğimiz Tura gelme durumunun toplamda 3 kez olmasının olasılığı elde edilmek istenmiştir.
Aslında binom dağılımının olasılık kütle fonksiyonu kombinasyon hesabıyla basit bir biçimde oluşturulabilir. Yukarıda
bahsettiğimiz paranın 5 kez atılması durumunda 3 Tura gelmesi aşağıdakiler gibi olabilir:
T Y T Y T
T T T Y Y
Y T T Y T
...
Burada toplamda 3 kez Tura gelme durumu aslında C(5, 3) kadardır. Bu olaylar bir arada gerçekleşeceğine göre bu değerle
p^3 değerini çarpmamız gerekir. Öte yandan Tura gelmeyen olasılıkların toplamı de 5 - 3 = 2 kadardır. O halde bunun da
(1 - p)^2 ile çarpılması gerekir. Binom dağılımının olasılık kütle fonksiyonu şöyledir:
P{X = x} = C(n, x)p^x * (1 - p)^(n - x)
Binom dağılımı çin SciPy kütüphanesindeki stats modülünde bulunan binom isimli singleton nesne kullanılabilir. Nesnenin
kullanılması diğer singleton nesnelere benzemektedir. Örneğin ilgili sınıfın pmf fonksiyonu olasılık kütle fonlsiyonunu
belirtmektedir. Bu fonksiyonlarda birinci parametre "olumlu gerçekleşme sayısını", ikinci parametre "toplam deney sayısını"
ve üçüncü parametre de "olumlu gerçekleşme olsaılığını" belirtmektedir. Örneğin yukarıda belirttiğimiz paranın 5 kez
atılmasında tam olarak 3 kez tura gelme olsaılığı şöyle elde edilebilir:
binom.pmf(3, 5, 0.5)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şüphesiz "sonuç çıkartıcı istatistiğin (inferential statistics)" en önemli teoremi "merkezi linmit teoremi (central limit
theorem)" denilen teoremdir. Bu teorem George Pólya tarafından 1920 yılında ilk kez resmi bir biçimde ifade edilmiştir.
Bu teoreme göre bir "anakütleden (population)" çekilen belli büyüklükteki örneklerin (samples) ortalamaları normal dağılmaktadır.
Örneğin elimizde 1000,000 elemanlı bir anakütle olsun. Bu anakütleden 50'lik tüm alt kümeleri yani örnekleri elde edip bunların
ortalamalarını heaplayalım. İşte bu ortalamalar normal dağılmaktadır. Bir anakütleden alınan alt kümelere "örnek (sample)"
denilmektedir. Bu işleme de genel olarak "örnekleme (sampling)" denir. Örneğimizdeki 1000,000 elemanın 50'li alt kümelerinin
sayısı çok fazladır. O halde deneme için 1000,000 elemanlı anakütlenin tüm alt kümelerini değil belli sayıda alt kümelerini
elde ederek histogram çizebiliriz. Bu histogramın teoreme göre Gauss eğrisine benzemesi gerekir.
Aşağıdaki örnekte 0 ile 1,000,000,000 arasında düzgün dağılmış rastgele sayılardan oluşan 1,000,000 elemanlık bir anakütle
oluşturulmuştur. Sonra bu anakütle içerisinden 10,000 tane 50'lik rastgele örnekler seçilmiştir. Sonra bu seçilen örneklerin
ortalamaları elde edilmiş ve histogramı çizilmiştir. Örnek ortalamalarının dağılımına ilişkin histogramın normal dağılıma
benzemesi gerekmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
POPULATION_RANGE = 1_000_000_000
POPULATION_SIZE = 1_000_000
NSAMPLES = 10000
SAMPLE_SIZE = 50
population = np.random.randint(0, POPULATION_RANGE, POPULATION_SIZE)
samples = np.random.choice(population, (NSAMPLES, SAMPLE_SIZE))
samples_means = np.mean(samples, axis=1)
plt.hist(samples_means, bins=50)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Tabii yukarıdaki örneği hiç NumPy kullanmadan tamamen Python standart kütüphanesi ile de yapabilirdik.
#----------------------------------------------------------------------------------------------------------------------------
import random
import statistics
POPULATION_RANGE = 1_000_000_000
POPULATION_SIZE = 1_000_000
NSAMPLES = 10000
SAMPLE_SIZE = 50
population = random.sample(range(POPULATION_RANGE), POPULATION_SIZE)
samples_means = [statistics.mean(random.sample(population, SAMPLE_SIZE)) for _ in range(NSAMPLES)]
samples_means_mean = statistics.mean(samples_means)
import matplotlib.pyplot as plt
plt.title('Central Limit Theorem', fontweight='bold')
plt.hist(samples_means, bins=50)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Merkezi limit teoremine göre örnek ortalamalarına ilişkin normal dağılımın ortalaması anakütle ortalaması ile aynıdır.
(Yani bir anakütleden çekilen örnek ortalamalarının ortalaması anakütle ortalaması ile aynıdır.) İstatistiksel sembollerle
bu durum aşağıdki gibi ifade edilmektedir:
Mu = Mu-xbar (Mu yerine mu sembolü xbar yer yerine de x üstü çizgi olduğunu varsayınız)
Aşağıdaki programda bu durum gösterilmiştir. Ancak aşağıdaki örnekte anakütle ortalaması ile örnek ortalamalarının
ortalaması arasında nispeten küçük bir fark oluşabilecektir. Bunun nedeni bizim tüm örnekleri değil yalnızca 10,000
örneği almamızdan kaynaklanmaktadır. 1_000_000 tane elemanın 50'li altküme sayıları Python'da math.comb fonksiyonu ile
elde edilebilir. Bu aşağıdaki gibi bir değerdir:
328392407820232468120659145980537063071733850478231049855714233423603813357488386226050559849769116542462344220085901
45569291064005813572729171162888456999821437660641232861427083961136203001102459836375149013720622748800690778924980000
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.pyplot as plt
POPULATION_RANGE = 1_000_000_000
POPULATION_SIZE = 1_000_000
NSAMPLES = 10000
SAMPLE_SIZE = 50
population = np.random.randint(0, POPULATION_RANGE, POPULATION_SIZE)
samples = np.random.choice(population, (NSAMPLES, SAMPLE_SIZE))
population_mean = np.mean(population)
samples_means = np.mean(samples, axis=1)
samples_means_mean = np.mean(samples_means)
plt.hist(samples_means, bins=50)
plt.show()
print(f'Anakütle ortalaması: {population_mean}')
print(f'Örnek ortalamalarının Ortalaması: {samples_means_mean}')
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda bir anakütleden alınan örnek ortalamaları normal dağıldığını ve bu normal dağılımın ortalamasının da anakütle
ortalamasına eşit olduğunu söyledik. Pekiyi örnek ortalamalarına ilişkin normal dağılımın standart sapması nasıldır? İşte
merkezi limit teoremine göre örnek ortalamalarının standart sapması "anakütle kütle standart sapması / karekök(n)" biçimindedir.
Yani örnek ortalamalarının standart sapması anakütle sapmasından oldukça küçüktür ve örnek büyüklüğüne bağlıdır. Buradaki n
örnek büyüklüğünü belirtmektedir. Örnek ortalamalarının standart sapmasına "standart hata (standard error)" da denilmektedir.
Görüldüğü gibi eğer örnek ortalamalarına ilişkin normal dağılımın standart sapması düşürülmek isteniyorsa (başka bir deyişle
standart hata azaltılmak isteniyorsa) örnek büyüklüğü artırılmalıdır.
Aşağıdaki örnekte örneklem ortalamalarına ilişkin dağılımın standart sapmasının merkezi limit teoreminde belirtilen durum ile
uygunluğu gösterilmektedir. Bu bu örnekte de aslında biz anakütle içerisinden az sayıda örnek aldığımız için olasmı gereken
değerlerden bir sapma söz konusu olacaktır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
POPULATION_RANGE = 1_000_000_000
POPULATION_SIZE = 1_000_000
NSAMPLES = 10000
SAMPLE_SIZE = 50
population = np.random.randint(0, POPULATION_RANGE, POPULATION_SIZE)
samples = np.random.choice(population, (NSAMPLES, SAMPLE_SIZE))
population_mean = np.mean(population)
population_std = np.std(population)
samples_means = np.mean(samples, axis=1)
samples_means_mean = np.mean(samples_means)
sample_means_std = np.std(samples_means)
plt.hist(samples_means, bins=50)
plt.show()
print(f'Anakütle ortalaması: {population_mean}')
print(f'Örnek ortalamalarının Ortalaması: {samples_means_mean}')
print(f'Fark: {np.abs(population_mean - samples_means_mean)}')
print(f'Merkezi limit teroreminden elde edilen örnek ortalamalarının standart sapması: {population_std / np.sqrt(50)}')
print(f'Örnek ortalamalarının standart sapması: {sample_means_std}')
#----------------------------------------------------------------------------------------------------------------------------
Merkezi limit teoremine göre eğer anakütleden çekilen örnekler büyükse yani tipik olarak n örnek büyüklüğü, N ise anakütle
büyüklüğü olmak üzere n / N >= 0.05 ise bu durumda örneklem dağılımın standart sapması için "anakütle standart sapması / kök(n)"
değeri "düzeltme faktörü (correction factor)" denilen bir çarpanla çarpılmalıdır. Düzeltme faktörü karekök((N - n)/(N -1))
biçimindedir. Bu konunun ayrıntıları için başka kaynaklara başvurabilirsiniz. Örneğin ana kütle 100 elemandan oluşuyor olsun.
Biz 30 elemanlı örnekler çekersek bu düzeltme faktörünü kullanmalıyız. Tabii paratikte genellikle n / N değeri 0.05'ten
oldukça küçük olma eğilmindedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yukarıdaki örneklerde anakütlenin normal dağılmadığına düzgün (uniform) dağıldığına dikkat ediniz. Çünkü biz 0'dan
1.000,000,000'a kadar rastgele değerlerden anakütleyi oluşturduk. Yukarıdaki örneklerde oluşturduğumuz anakütlenin histogramı
aşağıda çizdirilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
POPULATION_RANGE = 1_000_000_000
POPULATION_SIZE = 1_000_000
population = np.random.randint(0, POPULATION_RANGE, POPULATION_SIZE)
plt.hist(population, bins=50)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Merkezi limit teoreminde anakütlenin normal dağılmış olması gerekmez. Nitekim yukarıdaki örneklerimizde bir anakütleyi
"düzgün dağılıma (uniform distribution)" ilişkin olacak biçimde oluşturduk. Ancak eğer anakütle normal dağılmamışsa
örneklem ortalamalarının dağılımının normal olması için örnek büyüklüklerinin belli bir değerden büyük olması gerekmektedir.
Bu değer tipik olarak >= 30 biçimindedir. Tabii örnek büyüklüğü 30'dan küçükse de dağılım yine normal dağılıma benzemektedir.
Ancak anakütlenin normal dağılmadığı durumda örnek ortalamalarının normal dağılması için örnek büyüklüğü >= 30 biçiminde
alınmalıdır. Bu nedenle eğer anakütle "normal dağılmamışsa ve örnek büyüklüğü < 30" ise parametre tahmininde merkezi
limit teoreminden faydalanılması tatminkar sonuç elde edilmesini engelleyebilmektedir. Bu durumda "parametrik olmayan
(non-paranmetric) yöntemler" denilen yöntemler tercih edilmelidir.
Buradan çıkan sonuçlar özetle şöyledir:
- Eğer anakütle normal dağılmışsa örnek büyüklüğü ne olursa olsun örneklem ortalamalarının dağılımı normaldir.
- Eğer anakütle normal dağılmamışsa örnek ortalamalarının dağılımının normal olması için örnek büyüklüğünün >= 30
olması gerekir. Tabii n < 30 durumunda yine örneklem dağılımı normale benzemektedir ancak kusurlar oluşmaktadır.
Özetle anakütle normal dağılmamışsa örnek büyüklüklerinin artırılması gerekmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Sonuç çıkartıcı istatistikte (inferential statistics) bazen anakütlenin normal dağılıp dağılmadığını örneğe dayalı olarak
test etmek gerekebilir. Bunun için anakütleden bir örnek alınır. Sonra bu örneğe bakılarak anakütlenin normal dağılıp dağılmadığı
belli bir "güven düzeyinde (confidence level)" belirlenir. Buna "normallik testleri" denilmektedir. Aslında normallik testi
gözle de üstünkörü yapılabilmektedir. Anakütle içerisinden bir örnek çekip onun histogramını çizersek eğer bu histogram Gauss
eğrisine benziyorsa biz anakütlenin de normal dağılmış olduğu sonucunu gözle tespit edebiliriz. Ancak anakütlenin normal dağılıp
dağılmadığının tespit edilmesi için aslında "hipotez testleri (hypothesis testing)" denilen özel testler kullanılmaktadır. Normal
dağılıma ilişkin iki önemli hipotez testi vardır: "Kolmogorov-Smirnov" testi ve "Shapiro-Wilk" testi. Bu testlerin istatistiksel
ıklaması biraz karmaşıktır ve bizim konumuz içerisinde değildir. Ancak bu testler SciPy kütüphanesindeki stats modülü içerisinde
bulunan fonksiyonlarla yapılabilmektedir.
Hipotez testleri kursumuzun ikinci bölümde ele alınacaktır. Ancak hipotez testlerinde bir hipotez öne sürülür ve bu hipotezin
belirli güven düzeyi içerisinde doğrulanıp doğrulanmadığına bakılır. Genel olarak bizim doğrulanmasını istediğimiz hipoteze H0
hipotezi, bunun tersini belirten, yani arzu edilmeyen durumu belirten hipoteze de H1 hipotezi denilmektedir. Örneğin normallik
testindeki H0 ve H1 hipotezleri şöyle oluşturulabilir:
H0: Seçilen örnek normal bir anakütleden gelmektedir.
H1: Seçilen örnek normal dağılmış bir anakütleden gelmemektedir.
scipy.stats modülündeki kstest fonksiyonunun ilk iki parametresi zorunlu parametrelerdir. Birinci parametre anakütleden rastgele
seçilen örneği alır. İkinci parametre testi yapılacak dağılımın kümülatif dağılım fonksiyonunu parametre olarak almaktadır.
Ancak bu parametre kolaylık olsun diye yazısal biçimde girilebilmektedir. (Kolmogorov-Simirnov testi aslında başka dağılımları
test etmek amacıyla da kullanılabilmektedir.) Normallik testi için bu parametre 'norm' ya da norm.cdf biçiminde girilebilir.
Fonksiyonun diğer parametrelerini SciPy dokümanlarından inceleyebilirsiniz. Biz burada bu fonksiyonu hedefe yönelik bir
biçimde kullanacağız.
kstest fonksiyonu çağrıldığktan sonra bize "isimli bir demet (named tuple)" verir. Demetin birinci elemanı test istatistiğini,
ikinci elemanı p değerini belirtir. Bizim burada yapmamız gereken bu p değerinin kendi seçtiğimiz belli bir kritik değerden
büyük olup olmadığına bakmaktır. Bu kritik değer tipik olarak 0.05 olarak alınmaktadır. Ancak testi daha katı yapacaksanız bu
değeri 0.01 gibi küçük tutabilirsiniz. Yukarıda da belirttiğimiz gibi bu testte iki hipoztez vardır:
H0: Seçilen örnek normal dağılmış bir anakütleden gelmektedir.
H1: Seçilen örnek normal dağılmış bir anakütleden gelmemektedir.
Eğer "p değeri belirlediğimiz kritik değereden (0.05) büyükse H0 hipotezi kabul edilir, H1 hipotezi reddedilir. Eğer p değeri
bu kritik değerden küçükse H0 hipotezi reddelip, H1 hipotezi kabul edilmektedir. Yani özetle bu p değeri 0.05 gibi bir kritik
değerden büyükse örnek normal dağılmış bir anakütleden gelmektedir, 0.05 gibi bir kritik değereden küçükse örnek normal
dağılmamış bir anakütleden gelmektedir.
Aşağıdaki örnekte normal dağılmış bir anakütleden ve düzgün dağılmış bir anakütleden rastgele örnekler çekilip kstest
fonksiyonuna sokulmuştur.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.stats import norm, uniform, kstest
sample_norm = norm.rvs(size=1000)
result = kstest(sample_norm, 'norm')
print(result.pvalue) # 1'e yakın bir değer
sample_uniform = uniform.rvs(size=1000)
result = kstest(sample_uniform, norm.cdf)
print(result.pvalue) # 0'a çok yakın bir değer
#----------------------------------------------------------------------------------------------------------------------------
17. Ders - 24/02/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
kstest fonksiyonunda ikinci parametreye 'norm' girildiğinde birinci parametredeki değerler ortalaması 0, standart sapması
1 olan standart normal dağılıma uygun değerler olmalıdır. Eğer ortalama ve standart sapması farklı bir normal dağılım için
test yapılacaksa dağılımın parametreleri de ayrıca args parametresiyle verilmelidir. Örneğin biz ortalaması 100 standart sapması
15 olan bir dağılıma ilişkin test yapmak isteyelim. Bu durumda test aşağıdaki gibi yapılmalıdır.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.stats import norm, uniform, kstest
sample_norm = norm.rvs(100, 15, size=1000)
result = kstest(sample_norm, 'norm', args=(100, 15))
print(result.pvalue)
sample_uniform = uniform.rvs(100, 100, size=1000)
result = kstest(sample_uniform, norm.cdf, args=(100, 100))
print(result.pvalue)
#----------------------------------------------------------------------------------------------------------------------------
Shapiro-Wilk testi de tamamen benzer biçimde uygulanmaktadır. Ancak bu fonksiyonun kullanılması daha kolaydır. Bu fonksiyonun
tek bir parametresi vardır. Bu parametre anakütleden çekilen örneği belirtir. Buradaki normal dağılım herhangi bir ortalama ve
standart sapmaya ilişkin olabilir. Yani bizim dağılım değerlerini ortalaması 0, standart sapması 1 olacak biçimde ölçeklendirmemiz
gerekmemektedir.
Aşağıdaki örnekte ortalaması 100, standart sapması 15 olan normal dağılmış ve düzgün dağılmış bir anakütleden 100'lük bir örnek
seçilip Shapiro-Wilk testine sokulmuştur. Buradan elde edine pvalue değerlerine dikkat ediniz.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.stats import norm, uniform, shapiro
sample_norm = norm.rvs(100, 15, size=1000)
result = shapiro(sample_norm)
print(result.pvalue)
sample_uniform = uniform.rvs(100, 100, size=1000)
result = shapiro(sample_uniform)
print(result.pvalue)
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi Kolmogorov-Simirnov testi ile Shapiro-Wilk testi arasında ne farklılık vardır? Aslında bu iki test arasında bazı
spesifik farklılıklar bulunmaktadır. Ancak bunların her ikisi de normalliğin test edilmesi için kullanılabilmektedir.
Shapiro-Wilk testinin kullanımı daha kolaydır. Ayrıca anakütleden çekilen örnekler küçükse (tipik olarak <= 50) Shapiro-Wilk
testi Kolmogorov-Simirnov testine göre daha iyi bir sonunun elde edilmesine yol açmaktadır. Yani örneğiniz küçükse Shapiro-Wilk
testini tercih edebilirsiniz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
İstatistikte örneğe dayalı olarak anakütlenin ortalamasını (ve/veya standart sapmasını) tahmin etmeye "parametre tahmini
(parameter estimation)" denilmektedir. Parametre tahmini "noktasal olarak (point estimate)" ya da "aralıksal olarak
(interval estimate)" yapılabilmektedir. Örnekten hareketle anakütle ortalamasının belli bir aralıkta ve belli bir güven düzeyinde
tahmin edilmesi önemli bir konudur. Böylece biz anakütlenin tamamını gözden geçirmeden, oradan aldığımız bir örneğe bakarak
anakütle ortalamasını belli bir güven düzeyinde (confidence level) aralıksal olarak tahmin edebiliriz. Anakütle ortalamasının
aralıksal tahminine "güven aralıkları (confidence interval)" da denilmektedir. Güven aralıkları tamamen merkezi limit teroremi
kullanılarak oluşturulmaktadır. Yani güven aralıkları merkezi limit teoreminin en açık uygulamalarından biridir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bizim anakütle ortalamasını bilmediğimizi ancak anakütle standart sapmasını bildiğimizi varsayalım. (Genellikle aslında anakütle
standart sapmasını da bilmeyiz. Ancak burada bildiğimizi varsayıyoruz.) Bu anakütleden rastgele bir örnek seçtiğimizde
o örneğin ortalamasına bakarak anakütle ortalamasını belli bir aralıkta belli bir güven düzeyinde tahmin edebiliriz. Şöyle ki:
Örneğin seçtiğimiz güven düzeyi %95 olsun. Bu durumda bizim örneğimiz örnek ortalamalarının dağılımında en kötü olasılıkla
soldan 0.025 ve sağdan 0.975 kümülatif olasılığa karşı gelen x değerlerinden biri olabilir. Tabii seçtiğimiz örneğin ortalamasının
bu aralıkta olma olasılığı %95'tir Bu durumda yapacağımız şey örnek ortalamasının örneklem dağılımına göre seçtiğimiz örneğin
ortalamasının %47.5 soluna ve %47.5 sağına ilişkin değerlerin elde edilmesidir. Bu durumda anakütle ortalaması %95 güven düzeyi
içerisinde bu aralıkta olacaktır. Tabii aslında bu işlemi daha basit olarak "rastgele elde ettiğimiz örneğin ortalamasını normal
dağılımın merkezine alarak soldan 0.025 ve sağdan 0.975 kümülatif olasılık değerlerine karşı gelen noktaların elde edilmesi
yoluyla" da yapabiliriz.
Örneğin standart sapması 15 olan bir anakütleden rastgele 60 elemanlık bir örnek elde etmiş olalım. Bu örneğin ortalamasının
109 olduğunu varsayalım. Bu durumda %95 güven düzeyi içerisinde anakütle ortalamasına ilişkin güven aralıkları aşağıdaki gibi
elde edilebilir:
import numpy as np
from scipy.stats import norm
sample_size = 60
population_std = 15
sample_mean = 109
sampling_mean_std = population_std / np.sqrt(sample_size)
lower_bound = norm.ppf(0.025, sample_mean, sampling_mean_std)
upper_bound = norm.ppf(0.975, sample_mean, sampling_mean_std)
print(f'{lower_bound}, {upper_bound}') # 105.20454606435501, 112.79545393564499
Burada biz anakütlenin standart sapmasını bildiğimiz için örnek ortalamalarına ilişkin normal dağılımın standart sapmasını
hesaplayabildik. Buradan elde ettiğimiz güven aralığı şöyle olmaktadır:
105.20454606435501, 112.79545393564499
Güven düzeyini yükseltirsek güven aralığının genişleyeceği açıktır. Örneğin bu problem için güven düzeyini %99 olarak belirlemiş
olalım:
import numpy as np
from scipy.stats import norm
sample_size = 60
population_std = 15
sample_mean = 109
sampling_mean_std = population_std / np.sqrt(sample_size)
lower_bound = norm.ppf(0.005, sample_mean, sampling_mean_std)
upper_bound = norm.ppf(0.995, sample_mean, sampling_mean_std)
print(f'{lower_bound}, {upper_bound}') 104.01192800234102, 113.98807199765896
Burada güven aralığının aşağıdaki gibi olduğunu göreceksiniz:
104.01192800234102, 113.98807199765896
Gördüğünüz gibi aralık büyümüştür.
Aşağıdaki programda %95 güven düzeyi için güven aralığı hesaplanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import norm
sample_size = 60
population_std = 15
sample_mean = 109
sampling_mean_std = population_std / np.sqrt(sample_size)
lower_bound = norm.ppf(0.025, sample_mean, sampling_mean_std)
upper_bound = norm.ppf(0.975, sample_mean, sampling_mean_std)
print(f'{lower_bound}, {upper_bound}') # 105.20454606435501, 112.79545393564499
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki programda ise %99 güven düzeyi için güven aralığı elde edilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import norm
sample_size = 60
population_std = 15
sample_mean = 109
sampling_mean_std = population_std / np.sqrt(sample_size)
lower_bound = norm.ppf(0.005, sample_mean, sampling_mean_std)
upper_bound = norm.ppf(0.995, sample_mean, sampling_mean_std)
print(f'{lower_bound}, {upper_bound}') # 104.01192800234102, 113.98807199765896
#----------------------------------------------------------------------------------------------------------------------------
Anımsanacağı gibi örnek ortalamalarına ilişkin dağılımın standart sapmasın "standart hata (standard error)" deniliyordu.
Örnek ortalamalarına ilişkin dağılımın standart sapması azaltılırsa (yani standart hata düşürülürse) değerler ortalamaya
yaklaşacağına göre güven aralıkları da daralacaktır. O halde anakütle ortalamasını tahmin ederken büyük örnek seçmemiz
güven aralıklarını daraltacaktır. Aşağıdaki örnekte yuklarıdaki problemin 30'dan 100'e kadar beşer artırımla örnek
büyüklükleri için %99 güven düzeyinde güven aralıkları elde edilmiştir. Elde edilen aralıklar şöyledir:
sample size: 30: [103.63241756884852, 114.36758243115148]
sample size: 35: [104.03058429805395, 113.96941570194605]
sample size: 40: [104.35153725771579, 113.64846274228421]
sample size: 45: [104.61738729711709, 113.38261270288291]
sample size: 50: [104.84228852695097, 113.15771147304903]
sample size: 55: [105.03577765357056, 112.96422234642944]
sample size: 60: [105.20454606435501, 112.79545393564499]
sample size: 65: [105.3534458105975, 112.6465541894025]
sample size: 70: [105.48609245861904, 112.51390754138096]
sample size: 75: [105.60524279777148, 112.39475720222852]
sample size: 80: [105.71304047283782, 112.28695952716218]
sample size: 85: [105.81118086644236, 112.18881913355763]
sample size: 90: [105.9010248384772, 112.0989751615228]
sample size: 95: [105.98367907149301, 112.01632092850699]
sample size: 100: [106.06005402318992, 111.93994597681008]
Buradan da gördüğünüz gibi örneği büyüttüğümüzde güven aralıkları daralmakta ve anakütle ortalaması daha iyi tahmin
edilmektedir. Örnek büyüklüğünün artırılması belli bir noktaya kadar aralığı iyi bir biçimde daraltıyorsa da belli bir
noktadan sonra bu daraltma azalmaya başlamaktadır. Örneklerin elde edilmesinin belli bir çaba gerektirdiği durumda örnek
büyüklüğünün makul seçilmesi önemli olmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import norm
population_std = 15
sample_mean = 109
for sample_size in range(30, 105, 5):
sampling_mean_std = population_std / np.sqrt(sample_size)
lower_bound = norm.ppf(0.025, sample_mean, sampling_mean_std)
upper_bound = norm.ppf(0.975, sample_mean, sampling_mean_std)
print(f'sample size: {sample_size}: [{lower_bound}, {upper_bound}]')
#----------------------------------------------------------------------------------------------------------------------------
Anımsanacağı gibi anakütle normal dağılmamışsa merkezi limit teoreminin yeterli bir biçimde uygulanabilmesi için örneklerin
büyük olması (tipik olarak >= 30) gerekiyordu. O halde güven aralıklarını oluştururken eğer anakütle normal dağılmamışsa
bizim örnekleri >= 30 biçiminde seçmemiz uygun olur.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Daha önceden de belirttiğimiz gibi eskiden bilgisayarların yoğun olarak kullanılmadığı zamanlarda hesaplamaları pratik
hale getirmek için ortalaması 0, standart sapması 1 olan "standart normal dağılım" tabloları kullanılıyrodu. Bunlara Z
tablosu dendiğini anımsayınız. Bu Z tablolarında belli bir Z değeri için (standart normal dağılımdaki X değerlerine Z
değerleri dendiğini anımsayınız) kümülatif olasılıklar bulundurulmaktaydı. Tabii artık bilgisayarların bu kadar yoğun
kullanıldığı günümüzde okul sınavları dışında Z tablolarının kullanımı ortadan kalkmıştır. İşte bu eski devirlerde
güven aralıkları da bu Z tablolarına bakılarak oluşturulordu. Herhangi bir standart sapma ve ortalamaya ilişkin X değerinin
Z değerine aşağıdaki gibi dönüştürüldüğünü anımsayınız:
Z = (X - mu) / sigma
O halde buaradki X örneğin ortalaması, sigma ise örnek ortalamalarına ilişkin örneklem dağılımının standart sapması olmak
üzere Z tablosuna dayalı olarak güven aralıkları aşağıdaki gibi oluşturulabilir:
xbar ⩲ Z * sigma_xbar
Aşağıda bu yöntemle Z değerleri kullanılarak güven aralığının oluşturulmasına bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import norm
sample_size = 60
population_std = 15
sample_mean = 109
sampling_mean_std = population_std / np.sqrt(sample_size)
upper_bound = sample_mean + norm.ppf(0.975) * sampling_mean_std
lower_bound = sample_mean - norm.ppf(0.975) * sampling_mean_std
print(f'{lower_bound}, {upper_bound}') # 105.20454606435501, 112.79545393564499
#----------------------------------------------------------------------------------------------------------------------------
Örnekten hareketle anakütle ortalamaları aslında tek hamlede norm nesnesinin ilişkin olduğu sınıfın interval metoduyla da
elde edilebilmektedir. interval metodunun parametrik yapısı şöyledir:
interval(confidence, loc=0, scale=1)
Metodun birinci parametresi olan confidence güven düzeyini belirtmektedir. Örneğin %95 güven düzeyi için bu parametre 0.95
girilmelidir. Metodun ikinci ve üçüncü parametreleri örnek ortalamalarının dağılımına ilişkin ortalama ve standart sapmayı
belirtir. Tabii ikinci parametre elde etmiş olduğumuz örneğin ortalaması olarak girilmelidir. Metot güven aralığını belirten
bir demetle geri döner. Demetin ilk elemanı lower_bound ikinci elemanı upper_bound değerlerini vermektedir.
Bu durumda yukarıdaki problemi interval metoduyla aşağıdaki gibi de çözebiliriz:
import numpy as np
from scipy.stats import norm
sample_size = 60
population_std = 15
sample_mean = 109
sampling_mean_std = population_std / np.sqrt(sample_size)
lower_bound, upper_bound = norm.interval(0.95, sample_mean, sampling_mean_std)
print(f'{lower_bound}, {upper_bound}')
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import norm
sample_size = 60
population_std = 15
sample_mean = 109
sampling_mean_std = population_std / np.sqrt(sample_size)
lower_bound = norm.ppf(0.025, sample_mean, sampling_mean_std)
upper_bound = norm.ppf(0.975, sample_mean, sampling_mean_std)
print(f'{lower_bound}, {upper_bound}')
lower_bound, upper_bound = norm.interval(0.95, sample_mean, sampling_mean_std)
print(f'{lower_bound}, {upper_bound}')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte ortalaması 100, standart sapması 15 olan normal dağılıma uygun rastgeele 1,000,000 değer üretilmiştir.
Bu değerlerin anakütleyi oluşturduğu varsayılmıştır. Sonra bu anakütle içerisinden rastgele 60 elemanlık bir örnek elde
edilmştir. Bu örneğe dayanılarak ana kütle ortalaması norm nesnesinin interval metoduyla 0.95 güven düzeyiyle elde edilip
ekrana yazdırılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import norm
POPULATION_SIZE = 1_000_000
SAMPLE_SIZE = 60
population = norm.rvs(100, 15, POPULATION_SIZE)
population_mean = np.mean(population)
population_std = np.std(population)
print(f'population mean: {population_mean}')
print(f'population std: {population_std}')
sample = np.random.choice(population, SAMPLE_SIZE)
sample_mean = np.mean(sample)
sampling_mean_std = population_std / np.sqrt(SAMPLE_SIZE)
print(f'sample mean: {sample_mean}')
lower_bound, upper_bound = norm.interval(0.95, sample_mean, sampling_mean_std)
print(f'[{lower_bound}, {upper_bound}]')
#----------------------------------------------------------------------------------------------------------------------------
Biz yukarıdaki örneklerde güven aralıklarını oluştururken anakütle standart sapmasının bilindiğini varsaydık. Halbuki genellikle
anakütle ortalamasının bilinmediği durumda anakütle standart sapması da bilinmemektedir. Pekiyi bu durumda örnekten hareketle
anakütle ortalamasının aralık tahmini nasıl yapılacaktır? İşte bu durumda çektiğimiz örneğin standart sapması anakütlenin
standart sapması gibi işleme sokulmaktadır. Ancak dağılım olarak normal dağılım değil t dağılımı kullanılmaktadır. Zaten Gosset
t dağılımını tamamen böyle bir problemi çözerken geliştirmiştir. Yani t dağılımı zaten "anakütle standart sapmasının bilinmediği
durumda örneğin standart sapmasının anakütle standart sapması olarak alınmasıyla" elde edilen bir dağılımdır. t dağılımının
serbestlik derecesi denilen bir değere sahip olduğunu anımsayınız. Serbestlik derecesi örnek büyüklüğünün bir eksik değeridir.
Ayrıca 30 serbestlik derecesinden sonra zaten t dağılımının normal dağılıma çok benzediğini de belirtmiştik. Çektiğimiz örneğin
standart sapmasını anakütle standart sapması olarak kullanırken örneğin standrat sapması N'e değil (N - 1)'e bölünerek hesaplanmalıdır.
Burada bölmenin neden (N - 1)'e yapıldığının açıklaması biraz karmaşıktır. Burada bu konu üzerinde durmayacağız. Ancak Internet'te
bu konuyu açıklayan pek çok kaynak bulunmaktadır.İstatistikte çekilen örneklerin standart sapmaları genellikle sigma sembolü ile
değil s harfiyle belirtilmektedir.
Anımsanacağı gibi pek çok kütüphanede standart sapma ya da varyans hesaplanırken bölmenin neye yapılacağına "ddof (delta degrees of
freedom)" deniyordu. Standart sapma ya da varyans hesabı yapan fonksiyonların ddof parametreleri vardı. NumPy'da bu ddof parametresi
default 0 iken Pandas'da 1'dir. Bu ddof parametresi (N - değer)'deki değeri belirtmektedir. Yani ddof = 0 ise bölme N'e ddof = 1 ise
bölme (N - 1)'e yapılmaktadır.
Şimdi anakütleden aşağıdaki gibi 35'lik bir örnek çekmiş olalım:
sample = np.array([101.93386212, 106.66664836, 127.72179427, 67.18904948, 87.1273706 , 76.37932669, 87.99167058, 95.16206704,
101.78211828, 80.71674993, 126.3793041 , 105.07860807, 98.4475209 , 124.47749601, 82.79645255, 82.65166373, 92.17531189,
117.31491413, 105.75232982, 94.46720598, 100.3795159 , 94.34234528, 86.78805744, 97.79039692, 81.77519378, 117.61282039,
109.08162784, 119.30896688, 98.3008706 , 96.21075454, 100.52072909, 127.48794967, 100.96706301, 104.24326515, 101.49111644])
Anakütlenin standart sapmasının da bilinmediğini varsayalım. Bu değerlerden hareketle %95 güven düzeyinde güven aralığını
şöyle oluşturabiliriz:
import numpy as np
from scipy.stats import t
sample = np.array([101.93386212, 106.66664836, 127.72179427, 67.18904948, 87.1273706 , 76.37932669,
87.99167058, 95.16206704, 101.78211828, 80.71674993, 126.3793041 , 105.07860807,
98.4475209 , 124.47749601, 82.79645255, 82.65166373, 92.17531189, 117.31491413,
105.75232982, 94.46720598, 100.3795159 , 94.34234528, 86.78805744, 97.79039692,
81.77519378, 117.61282039, 109.08162784, 119.30896688, 98.3008706 , 96.21075454,
100.52072909, 127.48794967, 100.96706301, 104.24326515, 101.49111644])
sample_mean = np.mean(sample)
sample_std = np.std(sample, ddof=1)
sampling_mean_std = sample_std / np.sqrt(len(sample))
lower_bound = t.ppf(0.025, len(sample) - 1, sample_mean, sampling_mean_std)
upper_bound = t.ppf(0.975, len(sample) - 1, sample_mean, sampling_mean_std)
print(f'[{lower_bound}, {upper_bound}]')
Burada örneğin standart sapmasını hesaplarken ddof=1 kullandığımıza dikkat ediniz. Güven aralıkları normal dağılım kullanılarak
değil t dağılımı kullanılarak elde edilmiştir. t dağılımındaki serbestlik derecesinin (ppf fonksiyonun ikinci parametresi)
örnek büyüklüğünün bir eksik değeri olarak alındığını anımsayınız.
Örneklem dağılımının standart sapmasına standart hata dendiğini anımsayınız. Aslında scipy.stats modülünde bu değeri hesaplayan
sem isimli bir fonksiyon da bulunmaktadır. Yukarıdaki örneğimizde bu standart hata değerini iki satırda bulmuşturk:
sample_std = np.std(sample, ddof=1)
sampling_mean_std = sample_std / np.sqrt(len(sample))
Bu işlem tek hamlede sem fonksiyonuyla da yapılabilmektir:
sampling_mean_std = sem(sample)
Serbestlik derecesi 30'dan sonra artık t dağılımın normal dağılımla örtüşmeye başladığını anımsayınız. Buradaki örneğimizde
örnek büyüklüğü 35'tir. Örnek büyüklüğü >= 30 durumunda t dağılı ile normal dağılım birbirine çok benzediği için aslında
bu örnekte t dağılımı yerine normal dağılım da kullanabilirdi. Eskiden bilgisayarların yoğun kullanılmadığı zamanlarda
t dağılımı için de "t tabloları" kullanılıyordu. t tablolarında her bir serbestlik derecesi için ayrı girişler bulunduruluyordu.
Genellikle t tabloları çok büyük olmasın diye 30'a kadar serbestlik derecesini içeriyordu. Dolayısıyla eskiden N >= 30
durumunda t tablosu yerine Z tablolarının kullanılması çok yaygındı. Ancak günümüzde her şeyi bilgisayarlarla yaptığımız
için N >= 30 durumunda da t dağılımını kullanmak daha uygundur.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import t
sample = np.array([101.93386212, 106.66664836, 127.72179427, 67.18904948, 87.1273706 , 76.37932669,
87.99167058, 95.16206704, 101.78211828, 80.71674993, 126.3793041 , 105.07860807,
98.4475209 , 124.47749601, 82.79645255, 82.65166373, 92.17531189, 117.31491413,
105.75232982, 94.46720598, 100.3795159 , 94.34234528, 86.78805744, 97.79039692,
81.77519378, 117.61282039, 109.08162784, 119.30896688, 98.3008706 , 96.21075454,
100.52072909, 127.48794967, 100.96706301, 104.24326515, 101.49111644])
sample_mean = np.mean(sample)
sample_std = np.std(sample, ddof=1)
sampling_mean_std = sample_std / np.sqrt(len(sample))
lower_bound = t.ppf(0.025, len(sample) - 1, sample_mean, sampling_mean_std)
upper_bound = t.ppf(0.975, len(sample) - 1, sample_mean, sampling_mean_std)
print(f'[{lower_bound}, {upper_bound}]')
#----------------------------------------------------------------------------------------------------------------------------
18. Ders - 25/02/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Tıpkı sscipy.stats modülündeki norm nesnesinde olduğu gibi t nesnesinin de ilişkin olduğu snıfın interval isimli bir metodu
bulunmaktadır. Bu metot zaten doğrudan t dağılımını kullanarak güven aralıklarını hesaplamaktadır. interval metodunun parametrik
yapısı şöyledir:
interval(confidence, df, loc=0, scale=1)
Buradaki confidence parametresi yine "güven düzeyini (confidence level)" belirtmektedir. df parametresi serbestlik derecesini
belirtir. loc ve scale parametreleri de sırasıyla ortalama ve standart sapma değerlerini belirtmektedir. Burada loc
parametresine biz örneğimizin ortalamasını, scale parametresine de örneklem dağılımının standart sapmasını girmeliyiz.
Tabii örneklem dağılımının standart sapması yine örnekten hareketle elde edilecektir. Metot yine güven aralığının alt ve üst
değerlerini bir demet biçiminde geri döndürmektedir. Örneğin:
sample_mean = np.mean(sample)
sample_std = np.std(sample, ddof=1)
sampling_mean_std = sample_std / np.sqrt(len(sample))
lower_bound, upper_bound = t.interval(0.95, len(sample) - 1, sample_mean, sampling_mean_std)
print(f'[{lower_bound}, {upper_bound}]')
Burada sample örneğine dayanılarak %95 güven düzeyinde güven aralıkları oluşturulmuştur.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.stats import t
sample = np.array([101.93386212, 106.66664836, 127.72179427, 67.18904948, 87.1273706 , 76.37932669,
87.99167058, 95.16206704, 101.78211828, 80.71674993, 126.3793041 , 105.07860807,
98.4475209 , 124.47749601, 82.79645255, 82.65166373, 92.17531189, 117.31491413,
105.75232982, 94.46720598, 100.3795159 , 94.34234528, 86.78805744, 97.79039692,
81.77519378, 117.61282039, 109.08162784, 119.30896688, 98.3008706 , 96.21075454,
100.52072909, 127.48794967, 100.96706301, 104.24326515, 101.49111644])
sample_mean = np.mean(sample)
sample_std = np.std(sample, ddof=1)
sampling_mean_std = sample_std / np.sqrt(len(sample))
lower_bound = t.ppf(0.025, len(sample) - 1, sample_mean, sampling_mean_std)
upper_bound = t.ppf(0.975, len(sample) - 1, sample_mean, sampling_mean_std)
print(f'[{lower_bound}, {upper_bound}]')
lower_bound, upper_bound = t.interval(0.95, len(sample) - 1, sample_mean, sampling_mean_std)
print(f'[{lower_bound}, {upper_bound}]')
#----------------------------------------------------------------------------------------------------------------------------
Ana kütlenin standart sapmasının bilinmediği durumda yine eğer örnek yeteri büyük değilse (tipik olarak < 30 biçimindeyse)
ana kütlenin normal dağılmış olması gerekmektedir. Aksi takdirde yapılan hesaplarda hataler ortaya çıkabilmektedir. Eğer
örnek yeteri kadar büyükse (tipik olarak >= 30) ana kütlenin hesaplamalarda normal dağılmış olması gerekmemektedir. Tabii
t dağılımın asıl kullanılma nedeni örneğin küçük olduğu (tipik olarak < 30) durumlardır. Zaten örnek büyüdükçe (tipik olarak
>= 30) t dağılımı normal dağılıma benzemektedir.
Pekiyi örneğimiz küçükse (tipik oalrak < 30) ve ana kütle normal dağılmamışsa güven aralıklarını oluşturamaz mıyız? İşte
bu tür durumlarda güven aralıklarının oluşturulması ve bazı hipotez testleri için "parametrik olmayan (nonparametric)
yöntemler kullanılmaktadır. Ancak genel olarak parametrik olmayan yöntemler parametrik yöntemlere göre daha daha az güvenilir
sonuçlar vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Makine öğrenmesi ve veri bilimi uygulamalarında öncelikle verilerin elde edilmesi ve kullanıma hazır hale getirilmesi
gerekmektedir. Kursumuzun bu bölümünde biz "verilerin kullanıma hazır hale getirilmesi (data preparation)" süreci üzerinde
duracağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Her türlü veri analizi, veri bilimi ve makine öğrenmesi uygulamasında ilk aşama "verilerin toplanması (data collection)"
aşamasıdır. Verilerin toplanması çeşitli yöntemlerle yapılabilmektedir. Örneğin "anket (survey)" yoluyla veriler toplanabilir.
Günümüzde sensör teknolojilerinin de gelişmesiyle verilerin otomatik olarak toplanmasıyla da sık karşılaşılmaktadır. Veriler
birtakım faaliyet sonucunda kendiliğin de oluşabilmektedir. Örneğin sosyal medyadaki yazışmalarda oluşan veriler zaten sürecin
doğal akışı içerisinde elde edilmektedir. Bazen veriler birtakım kurumlar tarafından zaten oluşturulmuş durumdadır. Bazı veriler
eğitim amacıyla ya da benchmark amacıyla da oluşturulmuş olabilir. Bu tür verileri kullanabilmek için bu tür verilerin bulunduğu
sitelere üye olmanız gerekebilmektedir. Örneğin bunlardan en ünlüsü "kaggle.com" denilen sitedir. Veriler toplandığında daha
önceden de belirttiğimiz gibi en yaygın olarak CSV formatında saklanmaktadır. Tabii bazen veriler veritabanlarında da bulunabilir.
Bu durumda verileri kullanırken o veritabanlarına erişimemeiz gerekebilir. Veriler bazen tamamen başka formatlarda da karşımıza
çıkabilir. Örneğin büyük m,ktarda veriler için "HDF" formatı tercih edilebilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Veriler toplandıktan sonra hemen işleme sokulamayabilir. Veriler üzerinde çeşitli önişlemler yapmak gerekebilir. Bu önişlemlere
"verilerin hazır hale getirilmesi (data preparation)" denilmektedir. Biz bu bölümde verilerin hazır hale getirilmesi için bazı
temel işlemler üzerinde duracağız. Diğer bazı işlemler başka bölümlerde ele alınacaktır. Ancak verilerin kullanıma hazır hale
getirilmesi tipik olarak aşağıdaki gibi süreçleri içermektedir:
1) Verilerin Temizlenmesi (Data Cleaning): Veriler eksiklikler içerebilir ya da geçersiz değerler içerebilir. Bazen de aşırı
değerlerden (outliers) kurtulmak gerekebilir. Bu faaliyetlere verilerin temizlenmesi denilmektedir. Kusurlu veriler yapılan
analizleri olumsuz yönde etkilemektedir.
2) Özellik seçimi (Feature Selection): Veri kümelerindeki tüm sütunlar bizim için anlamlı ve gerekli olmayabilir. Gereksiz
sütunların atılıp gereklilerin alınması faaliyetine "özellik seçimi" denilmektedir. Örneğin bir veri tablosundaki kişinin
"AdıSoyadı" sütunu veri analizi açısından genellikle (ama her zaman değil) bir fayda sağlamamaktadır. Bu durumda bu sütunların
atılması gerekir. Bazen veriler tamamen geçersiz bir durum halinde de karşımıza gelebilmektedir. Örneğin oransal bir ölçeğe
sahip sütunda yanlışlıkla kategorik bir veri bulunabilir.
3) Verilerin Dönüştürülmesi (Data Transformation): Kategorik veriler, tarih ve zaman verileri gibi veriler, resimler gibi
veriler doğrudan işleme sokulamazlar. Bunların sayısal biçime dönüştürülmesi gerekir. Bazen veri kümesindeki sütunlarda önemli
skala farklılıkları olabilmektedir. Bu skala farklılıkları algoritmaları olumsuz etkileyebilmektedir. İşte sütunların skalalarını
birbirine benzer hale getirme sürecine "özellik ölçeklemesi (feature scaling)" denilmektedir.
4) Özellik Mühendisliği (Feature Engineering): Özellik mühendisliği veri tablosundaki sütunlardan olmayan başka sütunların
oluşturulması sürecine denilmektedir. Yani özellik mühendisliği var olan bilgilerden hareketle önemli başka bilgilerin elde
edilmesidir. Örneğinin kişinin boy ve kilosu biliniyorsa biz vücut kitle endeksini tabloya ekleyebiliriz.
5) Boyutsal Özellik İndirgemesi (Dimentionality Feature Reduction): Veri kümesinde çok fazla sütun olmasının pek çeşitli
dezavantajı olabilmektedir. Örneğin bu tür durumlarda işlem yükü artabilir. Gereksiz sütunlar kestirimi sürecini olumsuz
biçimde etkileyebilir. Fazla sayıda sütun kursumuzun ilerleyen zamanalarında sıkça karşılaşacağımız "overfitting" denilen
yanlış öğrenmelere yol açabilir. O zaman sütunların sayısının azaltılması gerekebilir. İşte n tane sütunun k < n olmak üzere
k tane sütun haline getirilmesi sürecine boyutsal özellik indirgemesi denilmektedir. Bu konu kursumuzda ileride ayrı bir bölümde
ele alınacaktır.
6) Verilerin Çoğaltılması (Data Augmentation): Elimizdeki veriler (veri kümesindeki satırlar) ilgili makine öğrenmesi yöntemini
uygulayabilmek için sayı bakımından ya da nitelik bakımından yetersiz olabilir. Eldeki verilerle (satırları kastediyoruz)
yeni verilerin oluşturulması (yeni satırların oluşturulması) sürecine "verilerin çoğaltılması (data augmentation)" denilmektedir.
Örneğin bir resimden döndürülerek pek çok resim elde edilebilir. Benzer biçimde örneğin bir resmin çeşitli kısımlarından
yeni resimler oluşturulabilir. Özellik mühendisliğinin "sütun eklemeye yönelik", verilerin çoğaltılmasının ise "satır eklemeye
yönelik" bir süreç olduğuna dikkat ediniz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Eksik verilerin ortaya çıkma nedenleri çeşitli olabilir. Ancak veri bilimcisini bu noktada ilgilendiren en önemli durum
eksik verilerin bir kalıp izleyip izlemediğidir. Genellikle teorik kaynaklar eksik verilerin ortaya çıkma biçimlerini üç
grupta ele almaktadır.
1) Tamamen Rastgele Oluşan Eksik Veriler (Missing Completely At Random - MCAR): Burada eksik veriler rastgele satırların
rastgele sütunlarındadır. Bu nedenle eksik veri içeren satır ya da sütunların atılması geri kalan veri kümesini yanlı (biased)
hale getirmez.
2) Rastgele Oluşan Ekisik Veriler (Missing At Random - (MAR): Burada eksik veriler bir kalıp oluşturmaktadır. Bu kalıp tablonun
başka sütunları ile ilgilidir. Örneğin tablonun "Yaş" sütunundaki eksik verilerin çoğunun Cinsiyet sütunundaki Kadın'lara ilişkin
olması bu türden bir eksik veridir. Böylesi eksik verilerde eksik verinin bulunduğu satırın tamamen atılması geri kalan veriyi
yanlı hale getirebilmektedir.
3) Rastgele Oluşmayan Eksik Veriler (Missing Not At Random - MNAR): Burada eksik verilerde bir kalıp vardır ancak bu kalıp
tabloda sütunlarla açıklanamamaktadır. Dolayısıyla burada da eksik veriler atılırsa bir yanlılık oluşabilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
CSV dosyalarında iki virgül arasında hiçbir değer yoksa bu eksik veri anlamına geliyor olabilir. Böyle CSV dosyalarını
Pandas'ın read_csv fonksiyonuyla okursak NaN (Not a Number) denilen özel numpy.float64 değerini elde ederiz. Örneğin
"person.csv" isimli dosya şu içeriği sahip olsun:
AdıSoyadı,Kilo,Boy,Yaş,Cinsiyet
Sacit Bulut,78,172,34,Erkek
Ayşe Er,67,168,45,Kadın
Ahmet San,,182,32,Erkek
Macit Şen,98,156,65,Erkek
Talat Demir,85,,49,Erkek
Bu dosyayı şöyle okuyalım:
import pandas as pd
df = pd.read_csv('person.csv')
Şöyle bir çıktı elde ederiz:
AdıSoyadı Kilo Boy Yaş Cinsiyet
0 Sacit Bulut 78.0 172.0 34 Erkek
1 Ayşe Er 67.0 168.0 45 Kadın
2 Ahmet San NaN 182.0 32 Erkek
3 Macit Şen 98.0 156.0 65 Erkek
4 Talat Demir 85.0 NaN 49 Erkek
Bir CSV dosyasında özel bazı sözcükler de eksik veri anlamına gelebilmektedir. Ancak hangi özel sözcüklerin eksik veri
anlamına geldiği CSV okuyucları arasında farklılıklar gösterebilmektedir. Örneğin Pandas'ın read_csv fonksiyonu şu
özel sözcükleri "eksik veri" gibi ele almaktadır: NaN, '', '#N/A', '#N/A' 'N/A', '#NA', '-1.#IND', '-1.#QNAN', '-NaN',
'-nan', '1.#IND', '1.#QNAN', '<NA>', 'N/A', 'NA', 'NULL', 'NaN', 'None', 'n/a', 'nan', null. CSV dosyalarında en çok
karşımıza çıkan eksik veri gösterimlri şunlardır: '', NaN, nan, NA, null. read_csv fonksiyonu ayrıca na_values isimli
parametresi yoluyla programcınn istediği yazıları da eksik veri olarak ele alabilmektedir. Bu parametreye yazılardan
oluşan dolaşılabilir bir nesne girilmeldir. Örneğin:
df = pd.read_csv('person.csv', na_values=['NE'])
Burada read_csv yukarıdakilere ek olarak 'NE' yazısını da eksik olarak ele alacaktır. read_csv fonksiyonunun keep_default_na
parametresi False olarak girilirse (bu parametrenin default değeri True biçimdedir) bu durumda yukarıda belirttiğimiz eksik
veri kabul edilen yazılar artık eksik veri olarak kabul edilmeyecek onlara normal yazı muamalesi yapılacaktır. read_csv
fonksiyonu bir süredir yazısal olan sütunların dtype özelliğini "object" olarak tutmaktadır. Yani bu tür sütunların her elemanı
farklı türlerden olabilir. Bu tür sütnunlarda NaN gibi eksik veriler söz konusu olduğunda read_csv fonksiyonu yine onu
np.float64 NaN değeri olarak ele almaktadır.
Eksik veri işlemleri NumPy kütüphanesi ile loadtxt fonksiyonu kullanılarak da yapılabilir. Ancak loadtxt fonksiyonunun
eksik verileri ele almak için kullanılması çok zahmetlidir. Bu nedenle biz kursumuzda CSV dosyalarını genellikle
Pandas'ın read_csv fonksiyonu ile okuyacağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Eksik verilerle çalışırken ilk yapılması gereken şey "eksikliğin analiz edilmesidir". Yani kaç tane eksik veri vardır? Kaç
satırda ve hangi sütunlarda eksik veriler bulunmaktadır? Eksik veriler toplam verilerin yüzde kaçını oluşturmaktadır? Gibi
sorulara yanırlar aranmalıdır.
Eksik verilerin ele alınmasında iki temel strateji vardır:
1) Eksik verilerin bulunduğu satırı (nadiren de sütunu) tamamen atmek
2) Eksik verilerin yerine başka değerler yerleştirmek (imputation).
Eksik verilerin bulunduğu satırın atılması yönteminde şunlara dikkat edilmelidir:
a) Eksik verili satırlar atıldığında elde kalan veri kümesi çok küçülecek midir?
b) Eksik verili satırlar atıldığında elde kalan veri kümesi yanlı (biased) hale gelecek midir?
Eğer bu soruların yanıtı "hayır" ise eksik verilerin bulunduğu satırlar tamamen atılabilir.
Eksik veriler yerine bir verinin doldurulması işlemine İngilizce "imputation" denilmektedir. Eğer eksik verilerin bulunduğu
satır (nadiren de sütun) atılamıyorsa "imputation" uygulanmalıdır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
DataFrame nesnesi df olmak üzere, eksik veri analizinde şu kalıpları kullanabilirsiniz:
1) Sütunlardaki eksik verilerin miktarları şöyle bulunabilir:
df.isna().sum() ya da pd.isna(df).sum()
Pandas'taki isna fonksiyonu aynı zamanda DataFrame ve Series sınıflarında bir metot biçiminde de bulunmaktadır. isna bize
bool türden bir DataFrame ya da Series nesnesi vermektedir. bool üzerinde sum işlemi yapıldığında False değerler 0 olarak, True
değerler 1 olarak işleme girer. Dolayısıyla yularıdaki işlemlerde biz sütunlardaki eksik veri sayılarını elde etmiş oluruz.
isna fonksiyonunun diğer bir ismi isnull biçimindedir.
2) Eksik verilerin toplam sayısı şöyle bulunabilir:
df.isna().sum().sum() ya da pd.isna(df).sum().sum()
isna fonksiyonu (ya da metodu) sütunsan temelde eksik verilerin sayılarını verdiğine göre onların toplamı da toplam eksik verileri
verecektir.
3) Eksik verilerin bulunduğu satır sayısı şöyle elde edilebilir:
pd.isna(df).any(axis=1).sum() ya da df.any().any(axis=1).sum()
any fonksiyonu ya da metodu bir eksen parametresi alarak satırsal ya da sütunsal işlem yapabilmektedir. any "en az bir
True değer varsa True değerini veren hiç True değer yoksa False değerini veren" bir fonksiyondur. Yukarıdaki ifadede
biz önce isna fonksiyonu ile eksik verileri matrisel bir biçimde DataFrame olarak elds ettik. Sonra da onun satırlarına
any işlemi uyguladık. Dolayısıyla "en az bir eksik veri olan satırların" sayısını elde etmiş olduk.
4) Eksik verilerin bulunduğu satır indeksleri şöyle elde edilebilir:
df.index[pd.isna(df).any(axis=1)] ya da df.loc[df.isna().any(axis=1)].index
5) Eksik verilerin bulunduğu sütun isimleri şöyle elde edilebilir:
missing_columns = [name for name in df.columns if df[name].isna().any()]
Burada liste içlemi kullandık. Önce sütun isimlerini elde edip o sütun bilgilerini doğrudan indesklemeyle eld ettik. Sonra
o sütunda en az bir eksik veri varsa o sütunun ismini listeye ekledik.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
19. Ders - 02/03/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
"Melbourne Housing Snapshot (MHS)" veri kümesinin eksik veri bakımından incelenmesi ve eksik verilerin rapor edilmesi aşağıdaki
gibi yapılabilir. Veri kümesini aşağıdaki sayfadan indirebilirsiniz:
https://www.kaggle.com/datasets/dansbecker/melbourne-housing-snapshot?resource=download
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('melb_data.csv')
missing_columns = [colname for colname in df.columns if df[colname].isna().any()]
print(f'Eksik verilen bulunduğu sütunlar: {missing_columns}', end='\n\n')
missing_column_dist = df.isna().sum()
print('Eksik verilerin sütunlara göre dağılımı:')
print(missing_column_dist, end='\n\n')
missing_total = df.isna().sum().sum()
print(f'Eksik verilen toplam sayısı: {missing_total}')
missing_ratio = missing_total / df.size
print(f'Eksik verilen oranı: {missing_ratio}')
missing_rows = df.isna().any(axis=1).sum()
print(f'Eksik veri bulunan satırların sayısı: {missing_rows}')
missing_rows_ratio = missing_rows / len(df)
print(f'Eksik veri bulunan satırların oranı: {missing_rows_ratio}')
"""
Elde Edilen Çıktı
Eksik verilen bulunduğu sütunlar: ['Car', 'BuildingArea', 'YearBuilt', 'CouncilArea']
Eksik verilerin sütunlara göre dağılımı:
Suburb 0
Address 0
Rooms 0
Type 0
Price 0
Method 0
SellerG 0
Date 0
Distance 0
Postcode 0
Bedroom2 0
Bathroom 0
Car 62
Landsize 0
BuildingArea 6450
YearBuilt 5375
CouncilArea 1369
Lattitude 0
Longtitude 0
Regionname 0
Propertycount 0
dtype: int64
Eksik verilen toplam sayısı: 13256
Eksik verilen oranı: 0.04648292306613367
Eksik veri bulunan satırların sayısı: 7384
Eksik veri bulunan satırların oranı: 0.543740795287187
"""
#----------------------------------------------------------------------------------------------------------------------------
Eksik verileri DataFrame nesnesinden silmek için DataFrame sınıfının dropna metodu kullanılabilir. Bu metotta default
axis = 0'dır. Yani default durumda satırlar atılmaktadır. Ancak axis=1 parametresiyle sütunları da atabiliriz. Metot default
durumda bize eksik verilerin atıldığı yeni bir DataFrame nesnesi vermektedir. Ancak metodun inplace parametresi True yapılırsa
nesne üzerinde atım yapılmaktadır.
Aşağıdaki örnekte kaggle.com'daki "Melbourne Housing Snapshot (MHS)" verileri kullanılmıştır. Bu verileri aşağıdan indirebilirsiniz:
https://www.kaggle.com/datasets/dansbecker/melbourne-housing-snapshot?resource=download
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('melb_data.csv')
print(f'VEri kümesinin boyutu: {df.shape}')
df_deleted_rows = df.dropna(axis=0)
print(f'Satır atma sonucundaki yeni boyut: {df_deleted_rows.shape}')
df_deleted_cols = df.dropna(axis=1)
print(f'Sütun atma sonucundaki yeni boyut: {df_deleted_cols.shape}')
#----------------------------------------------------------------------------------------------------------------------------
Eksik verilerin yerine başka değerlerin yerleştirilmesi işlemine "imputation" denilmektedir. Kullanılan tipik imputation
stratejiler şunlardır:
- Sütun sayısal ise Eksik verileri sütun ortalaması ile doldurma
- Sütun kaegorik ya da sırasal ise eksik verileri mode değeri ile doldurma
- Eksik verilerin sütunlarda uç değeler varsa medyan değeri ile doldurulması
- Eksik verilerin yerine belli aralıkta ya da dağılımda rastgele değer yerleştirme yöntemi
- Eksik verilerin zaman serileri tarzında sütunlarda önceki ya da sonraki sütun değerleriyle doldurulması
- Eksik değerlerin regresyonla tahmin edilmesi yoluyla doldurulması
- Eksik değerlerin KNN (K-Nearest Neighbours) Yöntemi ile doldurulması
En çok uygulanan yöntem basitliği nedeniyle sütun ortalaması, sütun modu ya da sütun medyanı ile doldurma yöntemidir.
Yukarıda da belirttiğimiz gibi eksik verilerin söz konusu olduğu satır ya da sütunların atılması büyük ölçüde bizim veri
kümesi ile ne yapmak istediğimize bağlıdır. Yukarıdaki "Melbourne Housing Snapshot (MHS)" veri kümesinde eğer uygunsa eksik veri
içeren sütunlar atılabilir. Çünkü eksik veriler büyük ölçüde 4 sütunda toplanmıştır. Yine eksik veriler atıldığında kalan
veri kümesi yanlı değilse ve zaten veri kümesinde çok satır varsa eksik verilerin bulunduğu satırlar da atılabilir. Yine
"Melbourne Housing Snapshot" veri kümesimde satırların atılması veri kümesini %50 civarında azaltacaktır. Bu nedenle
burada "imputation" düşünülebilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şimdi imputation işlemine MHS veri kümesi üzerinde örnek verelim. Bu veri kümesinde eksik veriler şu sütunlarda bulunmaktaydı:
"Car", "BuildingArea", "YearBuilt", "CouncilArea". Inputation işlemi için bu vsütunların incelenmesi gerekir. "Car" sütunu ev
için ayrılan otopark alanının kaç arabayı içerdiğini belirtmektedir. Bu sütunda ayrık küçük tamsayı değerler vardır. Burada
imputation için sütun oralaması alınabilir. Ancak bunların yuvarlanması daha uygun olabilir. Bu işlem şöyle yapılabilir:
impute_val = df['Car'].mean().round()
df['Car'] = df['Car'].fillna(impute_val) # eşdeğeri df['Car'].fillna(impute_val, inplace=True)
DataFrame ve Series sınıflarının fillna metotları eksik verileri belli bir değerle doldurmak için kullanılmaktadır.
Bu metotta inplace parametresi True yapılırsa zaten doldurma doğrudan DataFrame ya da Series nesnesi güncellenecek biçimde
yapılmaktadır.
Eksik veri içeren diğer bir sütun da "BuildingArea" sütunudur. Bu sütun evin metrekare cinsindende büyüklüğünü belirtmektedir.
Her ne kadar bu sütun tamsayı değerlerinden oluşuyorsa da bir yuvarlamadan ortlaama değerle doldurma işlemi yapabiliriz. Tabii
yine yuvarlama da uygulayabiliriz. Örneğin:
impute_val = df['BuildingArea'].mean().round()
df['BuildingArea'] = df['BuildingArea'].fillna(impute_val) # eşdeğeri df['Car'].fillna(impute_val, inplace=True)
Veri kümesinin YearBuilt sütunu binanın yapım yılını belirtmektedir. Bu tür tarih bilgileri ya da yıl bilgileri hangi ölçeğe
sahiptir? Aslında bu tür bilgilerin ölçeklerini belirleme amaca da bağlıdır. Yıl bilgisi "kategorik", "sıralı" ya da aralıklı
ölçek olarak değerlendirilebilir. Ancak genellikle bu tür yıl bilgilerinin "sıralı (ordinal)" değerlendirilmesi daha uygun
olabilmektedir. O halde biz bu sütunun ortalama değeri olarak medyan işlemi uygulayabiliriz:
impute_val = df['YearBuilt'].median()
df['YearBuilt'] = df['YearBuilt'].fillna(impute_val) # eşdeğeri df['YearBuilt'].fillna(impute_val, inplace=True)
Eksik veri içeren diğer bir sütun da "CouncilArea" sütunudur. Bu sütun binanın içinde bulunduğu bölgeyi belirtmektedir.
Dolayısıyla kategorik bir sütundur. O halde mod işlemi ile "imputation" uygulayabiliriz:
impute_val = df['CouncilArea'].mode()
df['CouncilArea'] = df['CouncilArea'].fillna(impute_val[0]) # eşdeğeri df['CouncilArea'].fillna(impute_val, inplace=True)
Pandas'taki DataFrame ve Series sınıflarının mode metotları sonucu Series nesnesi biçiminde vermektedir. (Aynı miktarda yinelenen
birden fazla değer olabilecğei için bu değerlerin hepsinin verilmesi tercih edilmiştir.) Dolayısıyla biz bu Series ensnesinin
ilk elemanını alarak değeri elde ettik.
Aşağıda MHS veri kümesinde uygulanan imputation işlemini bir bütün olarak veriyoruz.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('melb_data.csv')
impute_val = df['Car'].mean().round()
df['Car'] = df['Car'].fillna(impute_val) # eşdeğeri df['Car'].fillna(impute_val, inplace=True)
impute_val = df['BuildingArea'].mean().round()
df['BuildingArea'] = df['BuildingArea'].fillna(impute_val) # eşdeğeri df['Car'].fillna(impute_val, inplace=True)
impute_val = df['YearBuilt'].median()
df['YearBuilt'] = df['YearBuilt'].fillna(impute_val) # eşdeğeri df['YearBuilt'].fillna(impute_val, inplace=True)
impute_val = df['CouncilArea'].mode()
df['CouncilArea'] = df['CouncilArea'].fillna(impute_val[0]) # eşdeğeri df['CouncilArea'].fillna(impute_val, inplace=True)
#----------------------------------------------------------------------------------------------------------------------------
Biz yukarıda ortalama, medyan ve mod değerleriyle imputation uyguladık. Gerçekten de en çok uygulanan imputation yöntemleri
bunlardır. Ancak bu yöntemler bazen yetersiz kalabilmektedir. Bu durumda daha karmaşık yöntemlerin uygulanması gerebilmektedir.
Örneğin MHS veri kümesinde eksik veri olan evin metrekaresini ortalama yoluyla doldurmak uygun olmayabilir. Çünkü bazı bölgeler
pahalı olduğu için oradaki evler büyük ya da küçük olabilmektedir. Bazı bölgelerin imarları farklı olabilmektedir. Yani
evin metrekaresi başka özelliklere göre (sütun bilgilerine göre) değişebilmektedir. Pekiyi bu tür durumlarda ne yapmak gerekir?
İşte sütun değerleri satırdaki diğer değerlerden hareketle regresyon modelleriyle tahmin edilebilir. Ancak uygulamada
genellikle bu tür durumlar göz ardı edilip temel imputation yöntemleri uygulanmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Makine öğrenmesi ve veri bilimi için NumPy, Pandas ve SciPy dışındaki en önemli yaygın kütüphanelerden biri de "scikit-learn"
denilen ve "sklearn" ya da "scikit-learn" isimli pakette bulunan kütüphanedir. Biz bu kütüphaneye bazen scikit-learn bazen de
sklearn diyeceğiz.
NumPy ve Pandas genel amaçlı kütüphanelerdir. SciPy ise matematik ve lineer cebir konularına odaklanmış genel bir kütüphanedir.
Oysa scikit-learn makine öğrenmesi amacıyla tasarlanmış ve bu amaçla kullanılan bir kütüphanedir. Kütüphanenin yüklenmesi
şöyle yapılabilir:
pip install scikit-learn
Ancak scikit-learn kütüphanesi yapay sinir ağları ve derin öğrenme ağlarına yönelik tasarlanmamıştır. Ancak kütüphane
verilerin kullanıma hazır hale getirilmesine ilişkin öğeleri de içermektedir. scikit-learn içerisindeki sınıflar matematiksel
ve istatistiksel ağırlıklı öğrenme yöntemlerini uygulamaktadır. scikit-learn kütüphanesinin import ismi sklearn biçimindedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
scikit-learn kütüphanesi genel olarak "nesne yönelimli" biçimde oluşturulmuştur. Yani kütüphane daha çok fonksiyonlar yoluyla
değil sınıflar yoluyla kullanılmaktadır. Kütüphanenin belli bir kullanım biçimi vardır. Öğrenme kolay olsun diye bu biçim
değişik sınıflarda uygulanmıştır. Kütüphanenin tipik kullanım biçimi şöyledir:
1) Önce ilgili sınıf türünden nesne yaratılır. Örneğin sınıf SimpleImputer isimli sınıf olsun:
from sklearn.impute import SimpleImputer
si = SimpleImputer(...)
2) Nesne yaratıldıktan sonra onun bir veri kümesi ile eğitilmesi gerekir. Buradaki eğitme kavramı genel amaçlı bir kavramdır.
Bu işlem sınıfların fit metotlarıyla yapılmaktadır. fit işlemi sonrasında metot birtakım değerler elde edip onu nesnenin
içerisinde saklar. Yani fit metotları söz konusu veri kümesini ele alarak oradan gerekli faydalı bilgileri elde etmektedir.
Örneğin:
si.fit(dataset)
fit işlemi genel olarak transform için kullanılacak bilgilerin elde edilmesi işlemini yapmaktadır. DOlayısıyla fit işleminden
sonra artık nesnenin özniteliklerini kullanabiliriz.
3) fit işleminden sonra fit işlemiyle elde edilen bilgilerin bir veri kümesine uygulanması gerekir. Bu işlem de transform
metotlarıyla yapılmaktadır. Bir veri kümesi üzerinde fit işlemi uygulayıp birden fazla veri kümesini transform edebiliriz.
result1 = si.transform(data1)
result2 = si.transform(data2)
...
fit ve transform metotları bizden bilgiyi NumPy dizisi olarak, Pandas, Series ya da DataFrame nesnesi olarak ya da Python
listesi olarak alabilmektedir. fit metotları nesnenin kendisine geri dönmekte ve transform metotları da transform edilmiş
NumPy dizilerine geri dönmektedir.
4) Bazen fit edilecek veri kümesi ile transform edilecek veri kümesi aynı olur. Bu durumda önce fit, sonra transform metotlarını
çağırmak yerine bu iki işlem sınıfların fit_transform metotlarıyla tek hamlede de yapılabilir. Örneğin:
result = si.fit_transform(dataset)
5) Ayrıca sınıfların fit işlemi sonucunda oluşan bilgileri almak için kullanılan birtakım örnek öznitelikleri ve metotları da
olabilmektedir.
6) Sınıfların bazen inverse_transform metotları da bulunmaktadır. inverse_transform metotları transform işleminin tersini
yapmaktadır. Yani transform edilmiş bilgileri alıp onları transform edilmemiş hale getirmektedir.
Genel olarak scikit-learn kütüphanesi hem NumPy hem de Pandas nesnelerini desteklemektedir. fit, transform ve fit_transform
metotları genel olarak iki boyutlu bir veri kümesini kabul etmektedir. Bunun amacı genelleştirmeyi sağlamaktadır. Bu nedenle
örneğin biz bu metotlara Pandas'ın Series nesnelerini değil DataFrame nesnelerini verebiliriz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
20. Ders - 03/03/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şimdi eksik verilerin doldurulması işleminde kolaylık sağlayan scikit-learn kütüphanesindeki SimpleImputer sınıfını görelim.
Sınıfın kullanılması yukarıda ele alınan kalıba uygundur. Önce nesne yaratılır. Sonra fit işlemi yapılır. Asıl dönüştürme
işlemini transform yapmaktadır. SimpluImputer sınıfı türünden nesne yaratılırken __init__ metodunda birtakım parametreler
belirtilebilmektedir. Bunların çoğu zaten default değer almış durumdadır. Örneğin strategy parametresi default olarak 'mean'
durumdadır. Bu impute işleminin sütunun hangi bilgisine göre yapılacağını belirtir. 'mean' dışında şu stratejiler bulunmaktadır:
'median'
'most_frequent'
'constant'
'constant' stratejisi belli bir değerle doldurma işlemini yapar. Eğer bu strateji seçilirse doldurulacak değerin fill_value
parametresiyle belirtilmesi gerekir. Diğer parametreler için sınıfın dokümantasyonuna bakabilirsiniz.
Aşağıda örnekte daha önce yapmış olduğumuz "Melbourne Housing Snapshot" veri kümesi üzerinde bu sınıfı kullanarak impute
işlemi uygulanmıştır.
SimpleImputer sınıfının fit, transform ve fit_transform metotları iki boyutlu bir dizi almaktadır. Yani bu metotlara bir NumPy
dizisi geçirecekseniz onun iki boyutlu olması gerekir. Pandas'ın Series nesnelerinin tek boyutlu bir dizi belirttiğini
anımsayınız. Bu durumda biz bu metotlara Series nesnesi geçemeyiz. Ancak DataFrame nesneleri iki boyutlu dizi belirttiği
için DataFrame nesnelerini geçebiliriz. Eğer elimizde tek boyutlu bir dizi varsa onu bir sütundan n satırdan oluşan iki
boyutlu bir diziye dönüştürmeliyiz. Bunun için NumPy ndarray sınıfının reshape metodunu kullanabilirsiniz.
SimpleImputer nesnesi yaratılırken doldurma stratejisi nesnenin yaratımı sırasında verilmektedir. Yani nesne başta belirtilen
stratejiyi uygulamaktadır. Ancak veri kümelerinin değişik sütunları değişik stratejilerle doldurulmak istenebilir. Bunun için
birden fazla SimpleImputer nesnesi yaratılabilir. Örneğin:
si1 = SimpleImputer(strategy='mean')
si2 = SimpleImputer(strategy='median')
...
Ancak bunun yerine SİmpleImputer sınıfının set_params metodu da kullanılabilir. Bu metot önceden belirlenmiş parametreleri
değiştirmekte kullanılmaktadır. Örneğin:
si = SimpleImputer(strategy='mean')
...
si.set_params(strategy='median')
...
SimpleImputer sınıfının __init__ metodunun missing_values parametresi eksik değerlerin özel değerler olarak değerlendirilmesini sağlamak için
kullanılabilmektedir. Yani örneğin eksik değerler NaN değerleir olmayabilir 0 değerleri olabilir. Bu durumda biizm bunu
bu parametreyle belirtmemiz gerekir. Örneğin:
si = SimpleImputer(strategy='mean')
SimpleImputer sınıfında yukarıda belirttiğimiz gibi fit metodu asıl doldurma işlemini yapmaz. Doldurma işlemi için gereken
bilgileri elde eder. Yani örneğin:
a = np.array([1, 1, None, 4, None]).reshape(-1, 1)
si = SimpleImputer(strategy='mean')
si.fit(a)
Burada fit metodu aslında yalnızca bu a dizisindeki sütunların ortalamalarını elde etmektedir. (Örneğimizde tek br sütun
var). Biz fit yaptığımız bilgiyi transform etmek zorunda değiliz. Örneğin:
b = np.array([1, 1, None, 4, None]).reshape(-1, 1)
result = si.transform(b)
Biz şimdi burada a'dan elde ettiğimiz ortalama 3 değeri ile bu b dizisini doldurmuş oluruz. Tabii genellikle bu tür durumlarda
fit yapılan dizi ile transform yapılan dizi aynı dizi olur. Örneğin:
a = np.array([1, 1, None, 4, None]).reshape(-1, 1)
si = SimpleImputer(strategy='mean')
si.fit(a)
result = si.transform(a)
İşte bu tür durumlarda fit ve transform bir arada fit_transform metoduyla yapılabilir. Örneğin:
a = np.array([1, 1, None, 4, None]).reshape(-1, 1)
si = SimpleImputer(strategy='mean')
result = si.fit_transform(a)
scikit-learn kütüphanesindeki pek çok sınıf aynı anda birden fazla sütun üzerinde işlem yapabilmektedir. Bu nedenle bu
sınıfların fit ve transform metotları bizden iki boyutlu dizi istemektedir. tranform metotları da bize iki iki boyutlu
dizi geri döndürmektedir. Örneğin SimpleImputer sınıfına biz fit işlemind eiki bıyutlu bir dizi veriririz. Bu drumda
fit metodu her sütunu diğerinden ayrı bir biçimde ele alır ve o sütunlara ilişkin bilgileri oluşturur. Örneğin biz fit
metoduna aşağıdaki gibi iki boyutlu bir dizi vermiş olalım:
1 4
None 7
5 None
3 8
9 2
Stratejinin "mean" olduğunu varsayalım. Bu durumda fit metodu her iki sütunun da ortalamasını alıp nesnenin içerisinde
saklayacaktır. Biz artık transform metoduna iki boyutlu iki sütundan oluşan bir dizi verebiliriz. Bu transform metodu
bizim verdiğimiz dizinin ilk sütunununu fit ettiğimiz dizinin ilk sütunundan elde ettiği bilgiyle, ikinci sütununu fit
ettiğimiz dizinin ikinci sütunundan elde ettiği bilgiyle dolduracakır. İşte sckit-learn sınıflarının fit ve transform
metotlarına biz iki boyutlu diziler veririz. O da bize iki boyutlu diziler geri döndürür. Eğer elimizde tek boyutlu
bir dizi varsa biz onu reshape metoduyla iki boyutlu hale getirerek fit ve transform metotlarına vermeliyiz. Örneğin:
>>> a = np.array([1, 2, 3, None, 5])
>>> si.fit_transform(a.reshape(-1, 1))
array([[1. ],
[2. ],
[3. ],
[2.75],
[5. ]])
Aşağıda MHS veri kümesi üzerinde eksik verilerin bulunduğu sütunlar sckit-learn SimpleImputer sınıfı kullanılarak
doldurulmuştur.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
import numpy as np
df = pd.read_csv('melb_data.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean')
df[['Car', 'BuildingArea']] = np.round(si.fit_transform(df[['Car', 'BuildingArea']]))
si.set_params(strategy='median')
df[['YearBuilt']] = np.round(si.fit_transform(df[['YearBuilt']]))
si.set_params(strategy='most_frequent')
df[['CouncilArea']] = si.fit_transform(df[['CouncilArea']])
#----------------------------------------------------------------------------------------------------------------------------
scikit-learn kütüphanesinde sklearn.impute modülünde aşağıdaki imputer sınıfları bulunmaktadır:
SimpleImputer
IterativeImputer
MissingIndicator
KNNImputer
Buradaki KNNImputer sınıfı "en yakın komşuluk" yöntemini kullanmaktadır. Bu konu ileride ele alınacaktır. IterativeImputer
sınıfı ise regresyon yaparak doldurulacak değerleri oluşturmaktadır. Örneğin biz bu sınıfa fit işleminde 5 sütunlu bir
dizi vermiş olalım. Bu sınıf bu sütunların birini çıktı olarak dördünü gidir olarak ele alıp girdilerden çıktıyı tahmin edecek
doğrusal bir model oluşturmaktadır. Yani bu sınıfta doldurulacak değerler yalnızca doldurmanın yapılacağı sütunlar dikkate
alınarak değil diğer sütunlar da dikkate alınarak belirlenmektedir. Örneğin MHS veri kümesinde evin metrakaresi bilinmediğinde
bu eksik veriyi ortalama metrakareyle doldurmak yerine "bölgeyi", "binanın yaşını" da dikkate alarak doldurmak isteyebiliriz.
Ancak yukarıda da belirttiğimiz gibi genellikle bu tür karmaşık imputation işlemleri çok fazla kullanılmamaktadır.
Aşağıda MHS veri kümesi için üç sütuna dayalı olarak tahminleme yöntemiyle doldurmaya örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('melb_data.csv')
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
si = IterativeImputer()
df[['Car', 'BuildingArea', 'YearBuilt']] = si.fit_transform(df[['Car', 'BuildingArea', 'YearBuilt']])
#----------------------------------------------------------------------------------------------------------------------------
Verilerin kullanıma hazır hale getirilmesi sürecinin en önemli işlemlerinden biri de "kategorik (nominal)" ve "sıralı (ordinal)"
sütunların sayısal biçime dönüştürülmesidir. Çünkü makine öğrenmesi algoritmaları veriler üzerinde "toplama", "çarpma" gibi
işlemler yaparlar. Dolayısıyla kategorik veriler böylesi işlemlere sokulamazlar. Bunun için önce onların sayısal biçime
dönüştürülmeleri gerekir. Genellikle bu dönüştürme "eksik verilerin ele alınması" işleminden daha sonra yapılmaktadır. Ancak
bazen önce kategorik dönüştürmeyi yapıp sonra imputation işlemi de yapılabilir.
Kategorik verilerin sayısal biçime dönüştürülmesi genellikle her kategori (sınıf) için 0'dan başlayarak artan bir tamsayı
kerşı düşürerek yapılmaktadır. Örneğin bir sütunda kişilerin renk tercihleri olsun. Ve sütun içeriği aşağıdaki gibi olsun:
Kırmızı
Mavi
Kırmızı
Yeşil
Mavi
Yeşil
...
Biz şimdi burada bu kategorik alanı Kırmızı = 0, Mavi = 1, Yeşil = 2 biçiminde sayısal hale dönüştürebiliriz. Bu durumda bu
sütun şu hale gelecektir:
0
1
0
2
1
2
....
Örneğin biz bu işlemi yapan bir fonksiyon yazabiliriz. Fonksiyonun birinci parametresi bir DataFrame olabilir. İkinci
parametresi ise hang, sütun sayısallaştıtılacağına ilişkin sütun isimlerini belirten dolaşılabilir bir nesne olabilir.
Fonksiyonu şöyle yazabiliriz:
def category_encoder(df, colnames):
for colname in colnames:
labels = df[colname].unique()
for index, label in enumerate(labels):
df.loc[df[colname] == label, colname] = index
Burada biz önce sütun isimlerini tek tek elde etmek için dış bir döngü kullandık. Sonra ilgili sütundaki "tek olan (unique)"
etiketleri (labels) elde ettik. SOnra bu etiketleri iç bir döngüde dolaşarak sütunda ilgili etiketin bulunduğu satırlara
onları belirten sayıları yerleştirdik. Test işlemi için aşağıdaki gibi "test.csv" isimli bir CSV dosyasını kullanabiliriz:
AdıSoyadı,Kilo,Boy,Yaş,Cinsiyet,RenkTercihi
Sacit Bulut,78,172,34,Erkek,Kırmızı
Ayşe Er,67,168,45,Kadın,Yeşil
Ahmet San,85,182,32,Erkek,Kırmızı
Macit Şen,98,192,65,Erkek,Mavi
Talat Demir,85,181,49,Erkek,Yeşil
Sibel Ünlü,72,172,34,Kadın,Mavi
Ali Serçe,75,165,21,Erkek,Yeşil
Test kodu da şöyle olabilir:
import pandas as pd
def label_encode(df, colnames):
for colname in colnames:
labels = df[colname].unique()
for index, label in enumerate(labels):
df.loc[df[colname] == label, colname] = index
label_encode(df, ['RenkTercihi', 'Cinsiyet'])
print(df)
Şöyle bir çıktı elde edilmiştir:
AdıSoyadı Kilo Boy Yaş Cinsiyet RenkTercihi
0 Sacit Bulut 78 172 34 0 0
1 Ayşe Er 67 168 45 1 1
2 Ahmet San 85 182 32 0 0
3 Macit Şen 98 192 65 0 2
4 Talat Demir 85 181 49 0 1
5 Sibel Ünlü 72 172 34 1 2
6 Ali Serçe 75 165 21 0 1
Aşağıda da MHS veri kümesi üzerinde aynı işlem yapılmıştır.
#---------------------------------------------------------------------------------------------------------------------------
import pandas as pd
import numpy as np
df = pd.read_csv('melb_data.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean')
df[['Car', 'BuildingArea']] = np.round(si.fit_transform(df[['Car', 'BuildingArea']]))
si.set_params(strategy='median')
df[['YearBuilt']] = np.round(si.fit_transform(df[['YearBuilt']]))
si.set_params(strategy='most_frequent')
df[['CouncilArea']] = si.fit_transform(df[['CouncilArea']])
def category_encoder(df, colnames):
for colname in colnames:
labels = df[colname].unique()
for index, label in enumerate(labels):
df.loc[df[colname] == label, colname] = index
category_encoder(df, ['Suburb', 'SellerG', 'Method', 'CouncilArea', 'Regionname'])
print(df)
#----------------------------------------------------------------------------------------------------------------------------
Aslında yukarıdaki işlem scikit-learn kütüphanesindeki preprocessing modülünde bulunan LabelEncoder sınıfıyla yapılabilmektedir.
LabelEncoder sınıfının genel çalışma biçimi scikit-learn kütüphanesinin diğer sınıflarındaki gibidir. Ancak bu sınıfın fit
ve transform metotları tek boyutlu bir numpy dizisi ya da Series nesnesi almaktadır. (Halbuki yukarıda da belirttiğimiz gibi
genel olarak fit ve transform metotlarının çoğu iki boyutlu dizileri ya da DataFrame nesnelerini alabilmektedir.) fit metodu
yukarıda bizim yaptığımız gibi unique elemanları tespit edip bunu nesne içerisinde saklamaktadır. Asıl dönüştürme işlemi
transform metoduyla yapılmaktadır. Tabii eğer fit ve transform metotlarında aynı veriler kullanılacaksa bu işlemler tek hamlede
fit_transform metoduyla da yapılabilir. Örneğin yukarıdaki "test.csv" veri kümesindeki "Cinsiyet" ve "RenkTercihi" sütunlarını
kategorik olmaktan çıkartıp sayısal biçime şöyel dönüştürebiliriz:
import pandas as pd
from sklearn.preprocessing import LabelEncoder
df = pd.read_csv('test.csv')
le = LabelEncoder()
transformed_data = le.fit_transform(df['RenkTercihi'])
df['RenkTercihi'] = transformed_data
transformed_data = le.fit_transform(df['Cinsiyet'])
df['Cinsiyet'] = transformed_data
print(df)
LabelEncoder sınıfının classes_ isimli örnek özniteliği fir metodu tarafından oluşturulmaktadır. Bu öznitelik "tek olan
(unique)" etiketleri bize NumPy dizisi olarak vermektedir.
Aşağıda MHS veri kümesindeki gerekli sütunlar LabelEncoder sınıfı ile sayısal biçime dönüştürülmüştür.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
import numpy as np
df = pd.read_csv('melb_data.csv')
print(df, end='\n\n')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean')
df[['Car', 'BuildingArea']] = np.round(si.fit_transform(df[['Car', 'BuildingArea']]))
si.set_params(strategy='median')
df[['YearBuilt']] = np.round(si.fit_transform(df[['YearBuilt']]))
si.set_params(strategy='most_frequent')
df[['CouncilArea']] = si.fit_transform(df[['CouncilArea']])
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
for colname in ['Suburb', 'SellerG', 'Method', 'CouncilArea', 'Regionname']:
df[colname] = le.fit_transform(df[colname])
print(df)
#----------------------------------------------------------------------------------------------------------------------------
LabelEncoder sınıfının inverse_transform metodu ters işlemi yapmaktadır. Yani bir kez fit işlemi yapıldıktan sonra nesne zaten
hangi etiketlerin hangi sayısal değerlere karşı geldiğini kendi içerisinde tutmaktadır. Böylece biz sayısal değer verdiğimizde
onun yazısal karşılığını inverse_transform ile elde edebiliriz. Örneğin:
some_label_numbers = [0, 1, 1, 2, 2, 1]
label_names = le.inverse_transform(some_label_numbers)
Aşağıda "test.csv" dosyasının "RenkTercihi" ve "Cinsiyet" sütunları üzerinde önce transform sonra inverse_transform işlemi
örneği verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
from sklearn.preprocessing import LabelEncoder
df = pd.read_csv('test.csv')
print(df, end='\n\n')
le = LabelEncoder()
transformed_data = le.fit_transform(df['RenkTercihi'])
df['RenkTercihi'] = transformed_data
some_label_numbers = [0, 1, 1, 2, 2, 1]
label_names = le.inverse_transform(some_label_numbers)
print(label_names, end='\n\n')
transformed_data = le.fit_transform(df['Cinsiyet'])
df['Cinsiyet'] = transformed_data
print(df)
some_label_numbers = [0, 1, 1, 1, 0, 1]
label_names = le.inverse_transform(some_label_numbers)
print(label_names)
#----------------------------------------------------------------------------------------------------------------------------
Aslında kategorik verilerin 0'dan itibaren birer tamsayı ile numaralandırılması iyi bir teknik değildir. Kategorik verilen
"one hot encoding" denilen biçimde sayısallaştırılması doğru tekniktir. Biz "one hot encoding" dönüştürmesini izleyen
paragraflarda ele alacağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Sıralı (ordinal) verilerin sayısal biçime dönüştürülmesi tipik olarak düşük sıranın düşük numarayla ifade edilmesi biçiminde
olabilir. Örneğin "EğitimDurumu", "ilkokul", "ortaokul", "lise", "üniversite" biçiminde dört kategoride sıralı bir bilgi
olabilir. Biz de bu bilgilere sırasıyla birer numara vermek isteyebiliriz. Örneğin:
İlkokul --> 0
Ortaokul --> 1
Lise --> 2
Üniversite --> 3
scikit-learn içerisindeki LabelEncoder sınıfı bu amaçla kullanılamamaktadır. Çünkü LabelEncoder etiketlere bizimn istediğimiz
gibi numara vermemektedir. scikit-learn içerisinde bu işlemi pratik bir biçimde yapan hazır bir sınıf bulunmamaktadır. Gerçi
scikit-learn içerisinde OrdinalEncoder isimli bir sınıf vardır ama o sınıf bu tür amaçları gerçekleştirmek için tasarlanmamıştır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
21. Ders - 09/03/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
OrdinalEncoder sınıfının kullanımı benzerdir. Önce fit sonra transform yapılır. Eğer fit ve transform metodunda aynı veri
kümesi kullanılacaksa fit_transform metodu ile bu iki işlem bir arada yapılabilir. OrdinalEncoder sınıfının categories_
örnek özniteliği oluşturulan kategorileri NumPy dizisi olarak vermektedir. Sınıfın n_features_in_ örnek özniteliği ise
fit işlemine sokulan sütunların sayısını vermektedir.
OrdinalEncoder sınıfı encode edilecek sütunları eğer onlar yazısal biçimdeyse "lexicographic" sıraya göre numaralandırmaktadır.
(Yani sözlükte ilk gördüğü kategoriye düşük numara vermektedir. Tabii bu işlem UNICODE tabloya göre yapılmaktadır.) Kategorilere
bizim istediğimiz numaraları vermemektedir. Ayrıca bu sınıfın fit ve transform metotları iki boyutlu nesneleri kabul etmektedir.
Bu bağlamda OrdinalEncoder sınıfının LabelEncoder sınıfındne en önemli farklılığı OrdinalEncoder sınıfının birden fazla sütunu
(özelliği) kabul etmesidir. Halbuki LabelEncoder sınıfı tek bir sütunu (özelliği) dönüştürmektedir.Örneğin "test.csv" veri
kümemiz şöyle olsun:
AdıSoyadı,Kilo,Boy,Yaş,Cinsiyet,RenkTercihi,EğitimDurumu
Sacit Bulut,78,172,34,Erkek,Kırmızı,İlkokul
Ayşe Er,67,168,45,Kadın,Yeşil,Ortaokul
Ahmet San,85,182,32,Erkek,Kırmızı,İlkokul
Macit Şen,98,192,65,Erkek,Mavi,Lise
Talat Demir,85,181,49,Erkek,Yeşil,Üniversite
Sibel Ünlü,72,172,34,Kadın,Mavi,Ortaokul
Ali Serçe,75,165,21,Erkek,Yeşil,İlkokul
Burada "Cinsiyet" ve "RenkTercihi" kategorik (nominal) ölçekte sütunlardır. "EğitimDurumu" sütunu kategorik ya da sıralı
olarak ele alınabilir. Eğer biz İlkokul = 0, Ortaokul = 1, Lise = 2, Üniversite = 3 biçiminde sıralı ölçeğe ilişkin bir
kodlama yapmak istersek bunu LabelEncoder ya da OrdinalEncoder ile sağlayamayız. Örneğin:
df = pd.read_csv('test.csv')
print(df, end='\n\n')
oe = OrdinalEncoder()
transformed_data = oe.fit_transform(df[['Cinsiyet', 'RenkTercihi', 'EğitimDurumu']])
df[['Cinsiyet', 'RenkTercihi', 'EğitimDurumu']] = transformed_data
Buradan şöyle bir DataFrame elde edilecektir:
AdıSoyadı Kilo Boy Yaş Cinsiyet RenkTercihi EğitimDurumu
0 Sacit Bulut 78 172 34 0.0 0.0 3.0
1 Ayşe Er 67 168 45 1.0 2.0 1.0
2 Ahmet San 85 182 32 0.0 0.0 3.0
3 Macit Şen 98 192 65 0.0 1.0 0.0
4 Talat Demir 85 181 49 0.0 2.0 2.0
5 Sibel Ünlü 72 172 34 1.0 1.0 1.0
6 Ali Serçe 75 165 21 0.0 2.0 3.0
Bunu OridinalEncoder sınıfı le kodlamaya çalışırsak muhtemelen tam istediğimiz gibi bir kodlama yapamayız.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
from sklearn.preprocessing import OrdinalEncoder
df = pd.read_csv('test.csv')
print(df, end='\n\n')
oe = OrdinalEncoder()
transformed_data = oe.fit_transform(df[['Cinsiyet', 'RenkTercihi', 'EğitimDurumu']])
df[['Cinsiyet', 'RenkTercihi', 'EğitimDurumu']] = transformed_data
print(df)
#----------------------------------------------------------------------------------------------------------------------------
Bu durumda eğer kategorilere istediğiniz gibi değer vermek istiyorsanız bunu manuel bir biçimde yapabilirsiniz. Örneğin
veri kümesi read_csv fonksiyonuyla okunurken converters parametresi yoluyla hemen dönüştürme yapılabilir. read_csv ve
load_txt fonksiyonlarında converters parametresi bir sözlük nesnesi almaktadır. Bu sözlük nesnesi hangi sütun değerleri
okunurken hangi dönüştürmenin yapılacağını belirtmektedir. Buradaki sözlüğün her elemanının anahtarı bir sütun indeksinden
ya da sütun indeksinden değeri ise o sütunun dönüştürülmesinde kullanılacak fonksiyondan oluşmaktadır. Tabii bu fonksiyon
lambda ifadesi olarak da girilebilir. Örneğin:
df = pd.read_csv('test.csv', converters={'EğitimDurumu': lambda s: {'İlkokul': 0, 'Ortaokul': 1, 'Lise': 2, 'Üniversite': 3}[s]})
Elde edilecek DataFrame nesnesi şöyle olacaktır:
AdıSoyadı Kilo Boy Yaş Cinsiyet RenkTercihi EğitimDurumu
0 Sacit Bulut 78 172 34 Erkek Kırmızı 0
1 Ayşe Er 67 168 45 Kadın Yeşil 1
2 Ahmet San 85 182 32 Erkek Kırmızı 0
3 Macit Şen 98 192 65 Erkek Mavi 2
4 Talat Demir 85 181 49 Erkek Yeşil 3
5 Sibel Ünlü 72 172 34 Kadın Mavi 1
6 Ali Serçe 75 165 21 Erkek Yeşil 0
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('test.csv', converters={'EğitimDurumu': lambda s: {'İlkokul': 0, 'Ortaokul': 1, 'Lise': 2, 'Üniversite': 3}[s]})
print(df, end='\n\n')
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Kategorik verilerin 0'dan itibaren LabelEncoder ya da OridnalEncoder sınıfı ile sayısallaştırılması iyi bir fikir değildir.
Çünkü bu durumda sanki veri "sıralı (ordinal)" bir biçime sokulmuş gibi olur. Pek çok algoritma bu durumdan olumsuz yönde
etkilenmektedir. Örneğin veri kümesinin bir sütununda "RenkTercihi" olsun. Bu RenkTercihi de "Kırmızı", "Mavi" ve Yeşil
renklerindne oluşuyor olsun. Biz bu sütunu LabelEncoder ile sayısal hale getirdiğimizde örneğin Kırmızı = 0, Mavi = 1,
Yeşil = 2 biçiminde renklere numara verilecektir. İşte burada makine öğrenmesi algoritmaları sanki "Mavi" > "Kırmızı",
"Yeşil > "Mavi" gibi bir durum varmış gibi sonuca yol açabilecektir. Hatta örneğin bu kodlamadan "Kırmızı" + "Mavi" = "Mavi"
gibi gerçekte var olmayan özellikler de ortaya çıkacaktır. İşte bu nedenle kategorik olguların birden fazla sütunla ifade
edilmesi yoluna gidilmektedir. Kategorik verilerin birden fazla sütunla ifade edilmesinde en yaygın kullanılan yöntem "one
hot encoding" denilen yöntemdir. Bu yöntemde sütundaki kategorilerin sayısı hesaplanır. Veri tablosuna bu sayıda sütun eklenir.
"One hot" terimi "bir grup bitten hepsinin sıfır yalnızca bir tanesinin 1 olma durumunu" anlatmaktadır. İşte bu biçimde n
tane kategori n tane sütunla ifade edilir. Her kategori yalnızca tek bir sütunda 1 diğer sütunlarda 0 olacak biçimde kodlanır.
Örneğin:
RenkTercihi
-----------
Kırmızı
Kırmızı
Mavi
Kırmızı
Yeşil
Mavi
Yeşil
...
Burada 3 renk olduğunu düşünelim. Bunun "one hot encoding" dönüştürmesi şöyle olacaktır:
Kırmızı Mavi Yeşil
1 0 0
1 0 0
0 1 0
1 0 0
0 0 1
0 1 0
0 0 1
...
Eğer sütundaki kategori sayısı 2 tane ise böyle sütunlar üzerinde "one hot encoding" uygulamanın bir faydası yoktur. Bu tür
ikili sütunlar 0 ve 1 biçiminde kodlanabilir. (Yani bu işlem LabelEncoder sınıfyla yapılabilir). Biz de kursumuzda yalnızca
iki kategori içeren sütunları "one hot encoding" ile kodlamayacağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
"One hot encoding" yapmanın çok çeşitli yöntemleri vardır. Örneğin scitkit-learn içerisindeki preprocessing modülünde bulunan
OneHotEncoder sınıfı bu işlem için kullanılabilir. Sınıfın genel kullanımı diğer sckit-learn sınıflarında olduğu gibidir.
Yani önce fit işllemi sonra transform işlemi yapılır. fit işleminden sonra sütunlardaki "tek olan (unique)" elemanlar sınıfın
categories_ örnek özniteliğine NumPy dizilerinden oluşan bir liste biçiminde kaydedilmektedir. Örneğimizde kullanacağımız
test.csv" dosyası şöyle olsun:
AdıSoyadı,Kilo,Boy,Yaş,RenkTercihi
Sacit Bulut,78,172,34,Kırmızı
Ayşe Er,67,168,45,Yeşil
Ahmet San,85,182,32,Kırmızı
Macit Şen,98,192,65,Mavi
Talat Demir,85,181,49,Yeşil
Sibel Ünlü,72,172,34,Mavi
Ali Serçe,75,165,21,Yeşil
"One hot encoding" yapılırken DataFrame içerisine eski sütunu silip yeni sütunlar eklenmelidir. DataFrame sütununa bir isim
vererek atama yapılırsa nesne o sütunu zaten otomatik eklemektedir. "One hot encoding" ile oluşturulan sütunların isimleri
önemli değildir. Ancak OneHotEncoder nesnesi önce sütunu np.unique fonksiyonuna sokmakta ondan sonra o sırada encoding işlemi
yapmaktadır. NumPy'ın unique fonksiyonu aynı zamanda sıraya dizme işlemini de zaten yapmaktadır. Dolayısıyla OneHotEncoder
aslında kategorik değerleri alfabetik sıraya göre sütunsal olarak dönüştürmektedir. Yukarıda da belirttiğimiz gibi OneHotEncoder
nesnesi fit işlemi yapıldığında zaten kategorileri categories_ isimli örnek özniteliği yoluyla bir NumPy dizi listesi olarak
bize vermektedir. Biz DataFrame eklemesi yaparken doğrudan bu isimleri kullanabiliriz. categories_ örnek özniteliğinin bir liste
olduğuna dikkat ediniz. Çünkü birden fazla sütun tek hamlede "one hot encoding" yapılabilmektedir. Dolayısıyla programcı bu
listenin ilgili elemanından kategorileri elde etmelidir.
Yukarıda da belirttiğimiz gibi OneHotEncoder sınıfının fit ve transform metotları çok boyutlu dizileri kabul etmektedir.
Bu durumda biz bu metotlara Pandas'ın Series nesnesini değil DataFrame nesnesini vermeliyiz.
OneHotEncoder nesnesini yaratırken "sparse_output" parametresini False biçimde vermeyi unutmayınız. (Bu parametrenin eski
ismi yalnızca "sparse" biçimindeydi). Çünkü bu sınıf default olarak transform edilmiş nesneyi "seyrek matris (sparse matrix)"
olarak vermektedir. Elemanlarının büyük çoğunluğu 0 olan matrislere "seyrek matris (sparse matrix)" denilmektedir. Bu tür
matrisler "fazla yer kaplamasın diye" sıkıştırılmış bir biçimde ifade edilebilmektedir. Seyrek matrisler üzerinde işlemler
kursumuzun sonraki bölümlerinde ele alınacaktır.
Yine OneHotEncoder nesnesi yaratılırken parametre olarak dtype türünü belirtebiliriz. Default dtype türü np.float64 alınmaktadır.
Matris seyrek formda elde edilmeyecekse bu dtype türünü "uint8" gibi en küçük türde tutabilirsiniz. max_categories parametresi
kategori sayısını belli bir değerde tutmak için kullanılmaktadır. Bu durumda diğer tüm kategoriler başka bir kategori oluşturmaktadır.
Örneğin:
df = pd.read_csv('test.csv')
ohe = OneHotEncoder(sparse_output=False, dtype='uint8')
transformed_data = ohe.fit_transform(df[['RenkTercihi']])
df.drop(['RenkTercihi'], axis=1, inplace=True)
df[ohe.categories_[0]] = transformed_data
Burada önce veri kümesi okunmuş sonra OneHotEncoder sınıfıyla fit_transform işlemi yapılmıştır. Daha sonra ise DataFrame
içerisindeki "RenkTercihi" sütunu silinip onun yerine categories_ örnek özniteliğindeki kategori isimleri nesneye eklenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
df = pd.read_csv('test.csv')
ohe = OneHotEncoder(sparse_output=False, dtype='uint8')
transformed_data = ohe.fit_transform(df[['RenkTercihi']])
df.drop(['RenkTercihi'], axis=1, inplace=True)
df[ohe.categories_[0]] = transformed_data
print(df)
#----------------------------------------------------------------------------------------------------------------------------
DataFrame nesnesine yukarıdaki gibi birden fazla sütun eklerken dikkat etmek gerekir. Çünkü tesadüfen bu kategori isimlerine
ilişkin sütunlardan biri zaten varsa o sütun yok edilip yerine bu kategori sütunu oluşturulacaktır. Bunu engellemek için
oluşturacağınız kategori sütunlarını önek vererek isimlendirebilirsiniz. Önek verirken orijinal sütun ismini kullanırsanız
bu durumda çakışma olmayacağı garanti edilebilir. Yani örneğin RenkTercihi sütunu için "Kırmızı", "Mavi" "Yeşil" isimleri
yerine "RenkTercihi_Kırmızı", "RenkTercihi_Mavi" ve "RenkTercihi_Yeşil" isimlerini kullanabilirsiniz. Bu biçimde isim elde
etmek "liste içlemiyle" oldukça kolaydır. Örneğin:
category_names = ['RenkTercihi_' + category for category in ohe.categories_[0]]
Aşağıda buna örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
df = pd.read_csv('test.csv')
ohe = OneHotEncoder(sparse_output=False, dtype='uint8')
transformed_data = ohe.fit_transform(df[['RenkTercihi']])
df.drop(['RenkTercihi'], axis=1, inplace=True)
category_names = ['RenkTercihi_' + category for category in ohe.categories_[0]]
df[category_names] = transformed_data
print(df)
#----------------------------------------------------------------------------------------------------------------------------
Veri kümesinde birden fazla sütunda kategorik bilgiler olabilir. Bunlar OneHotEncoder sınıfıyla tek tek kodlanabilirler.
Ancak aslında yukarıda da belirttiğimiz gibi OneHotEncoder sınıfı bir grup sütunu tek hamlede de kodlayabilmektedir.
Örneğin "test.csv" veri kümemiz şöyle olsun:
AdıSoyadı,Kilo,Boy,Yaş,RenkTercihi,Meslek
Sacit Bulut,78,172,34,Kırmızı,Mühendis
Ayşe Er,67,168,45,Yeşil,Mühendis
Ahmet San,85,182,32,Kırmızı,Avukat
Macit Şen,98,192,65,Mavi,Doktor
Talat Demir,85,181,49,Yeşil,Avukat
Sibel Ünlü,72,172,34,Mavi,Doktor
Ali Serçe,75,165,21,Yeşil,Mühendis
Burada "RenkTercihi"nin yanı sıra "Meslek" de kategorik bir sütundur. Bunun her ikisini birden tek hamlede "one hot encoding"
işlemine sokabiliriz:
ohe = OneHotEncoder(sparse_output=False, dtype='uint8')
transformed_data = ohe.fit_transform(df[['RenkTercihi', 'Meslek']])
df.drop(['RenkTercihi', 'Meslek'], axis=1, inplace=True)
categories1 = ['RenkTercihi_' + category for category in ohe.categories_[0]]
categories2 = ['Meslek_' + category for category in ohe.categories_[1]]
df[categories1 + categories2] = transformed_data
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
df = pd.read_csv('test.csv')
ohe = OneHotEncoder(sparse_output=False, dtype='uint8')
transformed_data = ohe.fit_transform(df[['RenkTercihi', 'Meslek']])
df.drop(['RenkTercihi', 'Meslek'], axis=1, inplace=True)
categories1 = ['RenkTercihi_' + category for category in ohe.categories_[0]]
categories2 = ['Meslek_' + category for category in ohe.categories_[1]]
df[categories1 + categories2] = transformed_data
print(df)
#----------------------------------------------------------------------------------------------------------------------------
Aslında makine öğrenmesi uygulamalarında veri kümesinin sütun isimlerinin hiçbir önemi yoktur. Dolayısıyla aslında biz
"one hot encoding" işleminden önce NumPy dizisine geçip hiç isimlerini oluşturmadan geri kalan kısmı NumPy üzerinde de
yapabiliriz. Aşağıda buna bir örnek verilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
df = pd.read_csv('test.csv')
ohe = OneHotEncoder(sparse_output=False, dtype='uint8')
transformed_data = ohe.fit_transform(df[['RenkTercihi', 'Meslek']])
df.drop(['AdıSoyadı', 'RenkTercihi', 'Meslek'], axis=1, inplace=True)
dataset = df.to_numpy()
dataset = np.concatenate((dataset, transformed_data), axis=1)
print(dataset)
#----------------------------------------------------------------------------------------------------------------------------
One hot encoding yapmanın diğer bir yolu Pandas kütüphanesindeki get_dummies fonksiyonunu kullanmaktadır. get_dummies fonksiyonu
bizden bir DataFrame, Series ya da dolaşılabilir herhangi bir nesneyi alır. Eğer biz get_dummies fonksiyonuna bütün bir DataFrame
geçirirsek fonksiyon oldukça akıllı davranmaktadır. Bu durumda fonksiyon FataFrame nesnesi içerisindeki yazısal sütunları tespit
eder. Yalnızca yazısal sütunları "one hot encoding" işlemine sokar ve bize yazısal sütunları dönüştürülmüş yeni bir DataFrame
nesnesi verir. Pandas ile çalışırken bu fonksiyon çok kolaylık sağlamaktadır. yine "test.csv" veri kümemiz şöyle olsun:
AdıSoyadı,Kilo,Boy,Yaş,RenkTercihi,Meslek
Sacit Bulut,78,172,34,Kırmızı,Mühendis
Ayşe Er,67,168,45,Yeşil,Mühendis
Ahmet San,85,182,32,Kırmızı,Avukat
Macit Şen,98,192,65,Mavi,Doktor
Talat Demir,85,181,49,Yeşil,Avukat
Sibel Ünlü,72,172,34,Mavi,Doktor
Ali Serçe,75,165,21,Yeşil,Mühendis
Biz aslında get_dummies fonksiyonu yoluyla yapmış olduğumuz işlemleri tek hamlede yapabiliriz:
df = pd.read_csv('test.csv')
transformed_df = pd.get_dummies(df, dtype='uint8')
Burada biz tek hamlede istediğimiz dönüştürmeyi yapabildik. Bu dönüştürmede yine sütun isimleri orijinal sütun isimleri
ve kategori simleriyle öneklendirilmiştir. Eğer isterse programcı "prefix" parametresi ile bu öneki değiştirebilir, "prefix_sep"
parametresiyle de '_' karakteri yerine başka birleştirme karakterlerini kullanabilir. get_dummies fonksiyonu default durumda
sparse olmayan bool türden bir DataFrame nesnesi vermektedir. Ancak get_dummies fonksiyonunda "dtype" parametresi belirtilerek
"uint8" gibi bir türden çıktı oluşturulması sağlanabilmektedir.
Biz bir DataFrame nesnesinin tüm yazısal sütunlarını değil bazı yazısal sütunlarını da "one hot encoding" işlemine sokmak
isteyebiliriz. Bu durumda fonksiyon DataFrame nesnesinin diğer sütunlarına hiç dokunmamaktadır. Örneğin:
transformed_df = pd.get_dummies(df, columns=['Meslek'], dtype='uint8')
Biz burada yalnızca DataFrame nesnesinin "Meslek" sütununu "one hot encoding" yapmış olduk. get_dummies fonksiyonun zaten
"one hot encoding" yapılan sütunu sildiğine dikkat ediniz. Bu bizim genellikle istediğimiz bir şeydir. Yukarıdaki örnekte
"test.csv" dosyasında "AdıSoyadı" sütunu yazısal bir sütundur. Dolayısıyla default durumda bu sütun da "one hot encoding"
işlmeine sokulacaktır. Bunu engellemek için "columns" parametresinden faydalanabiliriz ya da baştan o sütunu atabiliriz.
Örneğin:
transformed_df = pd.get_dummies(df.iloc[:, 1:], dtype='uint8')
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
22. Ders - 10/03/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('test.csv')
print(df, end='\n\n')
ohe_df = pd.get_dummies(df, columns=['RenkTercihi', 'Meslek'])
print(ohe_df)
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda da belirttiğimiz gibi get_dummies fonksiyonu default durumda oluşturduğu sütunlara kategorik sütun isimlerini ve
kategoroileri '_' ile birleştirerek vermektedir. Ancak bir istersek prefix parametresi ile bu önekleri değiştirebiliriz.
Örneğin:
df = pd.read_csv('test.csv')
transformed_df = pd.get_dummies(df, columns=['RenkTercihi', 'Meslek'], dtype='uint8', prefix=['R', 'M'])
Burada oluşturulacak sütunlar için önekler sırasıyla 'R' ve 'M' biçiminde verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('test.csv')
transformed_df = pd.get_dummies(df, columns=['RenkTercihi', 'Meslek'], dtype='uint8', prefix=['R', 'M'])
print(transformed_df)
#----------------------------------------------------------------------------------------------------------------------------
Yine yukarıda belirttiğimiz gibi sütun isimlerinin birleştirilmesi için kullanılan karakter prefix_sep isimli parametreyle
değiştirilebilmektedir. Örneğin:
df = pd.read_csv('test.csv')
transformed_df = pd.get_dummies(df, columns=['RenkTercihi', 'Meslek'], dtype='uint8', prefix=['R', 'M'], prefix_sep='-')
Burada artık önekler 'R-' ve 'M-' haline getirilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('test.csv')
transformed_df = pd.get_dummies(df, columns=['RenkTercihi', 'Meslek'], dtype='uint8', prefix=['R', 'M'], prefix_sep='-')
print(transformed_df)
#----------------------------------------------------------------------------------------------------------------------------
Diğer bir "one hot encoding" uygulama yöntemi de "tensorflow.keras" kütüphanesindeki "to_categorical" fonksiyonudur. Bazen
zaten Keras ile çalışıyorsak bu fonksiyonu tercih edebilmekteyiz. to_categorical fonksiyonunu kullanmadan önce kategorik sütunun
sayısal biçime dönüştürülmüş olması gerekmektedir. Yani biz önce sütun üzerinde eğer sütun yazısal ise LabelEncoder işlemini
uygulamalıız. to_categorical fonksiyonu aynı anda birden fazla sütunu "one hot encoding" yapamamaktadır. Bu nedenle diğer
seçeneklere göre kullanımı daha zor bir fonksiyondur. to_categorical fonksiyonu Keras kütüphanesindeki utils isimli modülde
bulunnaktadır. Fonksiyonu kullanmak için aşağıdaki gibi import işlemi yapabilirisniz:
from tensorflow.keras.utils import to_categorical
Tabii bu fonksiyonu kullanabilmeniz için tensorflow kütüphanesinin de yüklü olması gerekir. Biz zaten izleyen konularda
bu kütüphaneyi kullanacağız. Kütüphane şöye yüklenebilir:
pip install tensorflow
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('test.csv')
from tensorflow.keras.utils import to_categorical
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
transformed_color = le.fit_transform(df['RenkTercihi'])
transformed_occupation = le.fit_transform(df['Meslek'])
ohe_color = to_categorical(transformed_color)
ohe_occupation = to_categorical(transformed_occupation)
color_categories = ['RenkTercihi_' + color for color in df['RenkTercihi'].unique()]
occupation_categories = ['Meslek_' + occupation for occupation in df['Meslek'].unique()]
df.drop(['RenkTercihi', 'Meslek'], axis=1, inplace=True)
df[color_categories] = ohe_color
df[occupation_categories] = ohe_occupation
print(df)
#----------------------------------------------------------------------------------------------------------------------------
"One hot encoding" yapmanın diğer bir yolu da manuel yoldur. Bu işlemi manuel yapmanın çeşitli yöntemleri vardır. Ancak
en basit yöntemlerdne biri NumPy'ın eye fonksiyonundan faydalanmaktır. Bu fonksiyon bize birim matris verir. Bir NumPy
dizisi bir listeyle indekslenebildiğine göre bu birim matris LabelEncoder ile sayısal biçime dönüştürülmüş bir dizi ile
indekslenirse istenilen dönüştürme yapılmış olur. Örneğin dönüştürülecek sütun bilgisi şunlardan oluşsun:
RenkTercihi
-----------
Kırmızı
Mavi
Yeşil
Yeşil
Mavi
Kırmızı
Mavi
Burada sütunda üç farklı kategori vardır: Kırmızı, Mavi, ve Yeşil. Bunlar LabelEncoder yapılırsa sütun şu biçime dönüştürülmüştür
RenkTercihi
-----------
0
1
2
2
1
0
1
3 farklı kategori olduğuna göre 3X3'lük aşağıdaki gibi bir birim matris oluşturulabilir:
1 0 0
0 1 0
0 0 1
Sonrada bu birim matris bir dizi ile indekslenirse "one hot encoding" işlemi gerçekleştirilir.
Aşağıda buna ilişkin bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('test.csv')
print(df, end='\n\n')
import numpy as np
color_cats = np.unique(df['RenkTercihi'].to_numpy())
occupation_cats = np.unique(df['Meslek'].to_numpy())
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df['RenkTercihi'] = le.fit_transform(df['RenkTercihi'])
df['Meslek'] = le.fit_transform(df['Meslek'])
print(df, end='\n\n')
color_um = np.eye(len(color_cats))
occupation_um = np.eye(len(occupation_cats))
ohe_color = color_um[df['RenkTercihi'].to_numpy()]
ohe_occupation = occupation_um[df['Meslek'].to_numpy()]
df.drop(['RenkTercihi', 'Meslek'], axis=1, inplace=True)
df[color_cats] = ohe_color
df[occupation_cats] = ohe_occupation
print(df, end='\n\n')
#----------------------------------------------------------------------------------------------------------------------------
"One hot encoding" işleminin bir versiyonuna da "dummy variable encoding" denilmektedir. Şöyle ki: "One hot encoding" işleminde
n tane kategori için n tane sütun oluşturuluyordu. Halbuki "dummy variable encoding" işleminde n tane kategori için n - 1
tane sütun oluşturulmaktadır. Çünkü bu yöntemde bir kategori tüm sütunlardaki sayının 0 olması ile ifade edilmektedir. Örneğin
Kırmızı, Yeşil, Mavi kategorilerinin bulunduğu bir sütun şöyle "dummy variable encoding" biçiminde dönüştürülebilir:
Mavi Yeşil
0 0 (Kırmızı)
1 0 (Mavi)
0 1 (Yeşil)
Görüldüğü gibi kategorilerden biri (burada "Kırmızı") tüm elemanı 0 olan satırla temsil edilmiştir. Böylece sütun sayısı bir
eksiltilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
"Dummy variable encoding" işlemi için farklı sınıflar ya da fonksiyonlar kullanılmamaktadır. Bu işlem "one hot encoding"
yapan sınıflar ve fonksiyonlarda özel bir parametreyle gerçekleştirilmektedir. Örneğin scikit-learn kütüphanesindeki
OneHotEncoder sınıfının drop parametresi 'first' olarak geçilirse bu durumda transform işlemi "dummy variable encoding"
biçiminde yapılmaktadır. Örneğin "test.csv" dosyası aşağıdaki gibi olsun:
AdıSoyadı,Kilo,Boy,Yaş,RenkTercihi,Meslek
Sacit Bulut,78,172,34,Kırmızı,Mühendis
Ayşe Er,67,168,45,Yeşil,Mühendis
Ahmet San,85,182,32,Kırmızı,Avukat
Macit Şen,98,192,65,Mavi,Doktor
Talat Demir,85,181,49,Yeşil,Avukat
Sibel Ünlü,72,172,34,Mavi,Doktor
Ali Serçe,75,165,21,Yeşil,Mühendis
Biz de "RenkTercihi" sütununu "dummy variable encoding" ile dönüştürmek isteyelim. Bu işlemi şöyle yapabiliriz:
df = pd.read_csv('test.csv')
ohe = OneHotEncoder(sparse_output=False, drop='first')
transformed_data = ohe.fit_transform(df[['RenkTercihi']])
print(df['RenkTercihi'])
print(ohe.categories_)
print(transformed_data)
Buradan şu çıktılar elde edilmiştir:
0 Kırmızı
1 Yeşil
2 Kırmızı
3 Mavi
4 Yeşil
5 Mavi
6 Yeşil
Name: RenkTercihi, dtype: object
[array(['Kırmızı', 'Mavi', 'Yeşil'], dtype=object)]
[[0. 0.]
[0. 1.]
[0. 0.]
[1. 0.]
[0. 1.]
[1. 0.]
[0. 1.]]
Görüldüğü gibi burada "Kırmızı" kategorisi [0, 0] biçiminde kodlanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
from sklearn.preprocessing import OneHotEncoder
df = pd.read_csv('test.csv')
ohe = OneHotEncoder(sparse_output=False, drop='first')
transformed_data = ohe.fit_transform(df[['RenkTercihi']])
print(df['RenkTercihi'])
print(ohe.categories_)
print(transformed_data)
#----------------------------------------------------------------------------------------------------------------------------
Pandas'ın get_dummies fonksiyonunda drop_first parametresi True geçilirse "dummy variable encoding" uygulanmaktadır.
Örneğin:
df = pd.read_csv('test.csv')
transformed_df = pd.get_dummies(df, columns=['RenkTercihi', 'Meslek'], dtype='uint8', drop_first=True)
Burada veri kümesinin "RenkTercihi" ve "Meslek" sütunları "dummy variable encoding" olarak kodlanmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('test.csv')
transformed_df = pd.get_dummies(df, columns=['RenkTercihi', 'Meslek'], dtype='uint8', drop_first=True)
print(transformed_df)
#----------------------------------------------------------------------------------------------------------------------------
Sütundaki kategori sayısı çok fazla ise "One hot encoding" dönüştürmesi çok fazla sütunun veri tablosuna eklenmesine yol
açmaktadır. Aslında pek çok durumda bunun önemli bir sakıncası yoktur. Ancak sütun sayısının fazlalaşması veri kümesinin
bellekte çok yer kaplamasına yol açabilmektedir. Aynı zamanda bunların işlenmesi için gereken süre de uzayabilmektedir.
Pekiyi kategori çok fazla ise ve biz çok fazla sütunun veri kümesine eklenmesini istemiyorsak bu durumda ne yapabiliriz?
Yöntemlerden biri çeşitli kategorileri başka üst kategoriler içerisinde toplamak olabilir. Böylece aslında bir grup kategori
sınıflandırılarak bunlardan daha az kategori elde edilebilir. Yöntemlerden diğeri "one hot encoding" yerine alternatif
başka kodlamaların kullanılmasıdır. Burada da akla "binary encoding" yöntemi gelmektedir. Tabii kategorik veriler için en iyi
yöntem aslında "one hot encoding" yöntemidir. Uygulamacı mümkün olduğunca bu yöntemi kullanmaya çalışmalıdır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Binary encoding yönteminde her kategori "ikilik sistemde bir syayıymış" gibi ifade edilmektedir. Örneğin sütunda 256 tane
kategori olsun. Bu kategoriler 0'dan 255'e kadar numaralandırılabilir. 0 ile 255 arasındaki sayılar 2'lik sistemde 8 bit ile
ifade edilebilir. Örneğin bir kategorinin sayısal değeri (LabelEncoder yapıldığını düşünelim) 14 olsun. Biz bu kategoriyi
aşağıdaki gibi 8 bit'lik 2'lik sistemde bir sayı biçiminde kodlayabiliriz:
0 0 0 0 1 1 1 0
Tabii kategori sayısı tam 2'nin kuvveti kadar olmak zorunda değildir. Bu durumda kategori sayısı N olmak üzere gerkeli olan
bit sayısı (yani sütun sayısı) ceil(log2(N)) hesabı ile elde edilebilir.
Binary encoding işlemi manuel bir biçimde yapılabilir. Bunun için maalesef Pandas'ta ya da NumPy'da bir fonksiyon bulundurulmamıştır.
scikit-learn kütüphanesinin ana modülleri içerisinde de böyle bir sınıf yoktur. Ancak scikit-learn kütüphanesinin contribute
girişimlerinden birinde bu işlemi yapan bir BinaryEncoder isminde bir sınıf bulunmaktadır. Bu sınıf category_encoders isimli
bir paket içerisindedir ve bu paket ayrıca yüklenmelidir. Yükleme şöyle yapılabilir:
pip install category_encoders
BinaryEncoder sınıfının genel kullanımı diğer scikit-learn sınıflarında olduğu gibidir. Yani önce fit, sonra tra Yine
"test.csv" dosyasımız aşağıdaki gibi olsun:
AdıSoyadı,Kilo,Boy,Yaş,RenkTercihi,Meslek
Sacit Bulut,78,172,34,Kırmızı,Mühendis
Ayşe Er,67,168,45,Yeşil,Mühendis
Ahmet San,85,182,32,Kırmızı,Avukat
Macit Şen,98,192,65,Mavi,Doktor
Talat Demir,85,181,49,Yeşil,Avukat
Sibel Ünlü,72,172,34,Mavi,Doktor
Ali Serçe,75,165,21,Yeşil,Mühendis
BinaryEncoder sınıfı category_encoders paketinin Binary modülünün içerisinde bulunmaktadır. Dolayısıyla sınıfı kullanabilmek
için aşağıdaki gibi import işlemi yapabiliriz:
from category_encoders.binary import BinaryEncoder
BinaryEncoder sınıfının transform fonksiyonu default durumda Pandas DataFrame nesnesi vermektedir. Ancak nesne yaratılırken
return_df parametresi False geçilirse bu durumda transform fonksiyonları NumPy dizisi geri döndürmektedir. Örneğin:
df = pd.read_csv('test.csv')
be = BinaryEncoder()
transformed_data = be.fit_transform(df['Meslek'])
print(transformed_data)
Burada "Meslek" sütunu "binary encoding" biçiminde kodlanmıştır. BinaryEncode kodlamada değerleri 1'den başlatılmaktadır.
Yukarıdaki işlemden aşağıdaki gibi bir çıktı elde edilmiştir:
Meslek_0 Meslek_1
0 0 1
1 0 1
2 1 0
3 1 1
4 1 0
5 1 1
6 0 1
BinaryEncoder sınıfı ile biz tek boyutlu ya da çok boyutlu (yani tek sütunu ya da birden fazla sütunu) kodlayabiliriz. Örneğin:
be = BinaryEncoder()
transformed_data = be.fit_transform(df[['RenkTercihi', 'Meslek']])
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('test.csv')
from category_encoders.binary import BinaryEncoder
be = BinaryEncoder()
transformed_data = be.fit_transform(df[['RenkTercihi', 'Meslek']])
print(transformed_data)
df.drop(['RenkTercihi', 'Meslek'], axis=1, inplace=True)
df[transformed_data.columns] = transformed_data # pd.concat((df, transformed_data), axis=1)
print(df)
#----------------------------------------------------------------------------------------------------------------------------
Tıpkı get_dummies fonksiyonunda olduğu gibi aslında bir DataFrame bütünsel olarak da verilebilir. Yine default durumda
tüm yazısal sütunlar "binary encoding" dönüştürmesine sokulmaktadır. Ancak biz BinaryEncoding sınıfının __init__ metodunda
cols parametresi ile hangi sütunların dönüştürüleceğini belirleyebiliriz. Örneğin:
df = pd.read_csv('test.csv')
from category_encoders.binary import BinaryEncoder
be = BinaryEncoder(cols=['RenkTercihi', 'Meslek'])
transformed_df = be.fit_transform(df)
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('test.csv')
from category_encoders.binary import BinaryEncoder
be = BinaryEncoder(cols=['RenkTercihi', 'Meslek'])
transformed_df = be.fit_transform(df)
print(transformed_df)
#----------------------------------------------------------------------------------------------------------------------------
23. Ders - 16/03/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay zeka ve makine öğrenmesi alanının en önemli yöntemlerinin başında "yapay sinir ağları (artificial neural networks)" ve
"derin öğrenme (deep learning)" denilen yöntemler gelmektedir. Biz de bu bölümde belli bir derinlikte bu konuları ele alacağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay Sinir ağlarının teorisi ilk zamanlar sinir bilimle (neuroscience), psikolojiyle ve matematikle uğraşan bilim adamları
tarafından geliştirilmiştir. Yapay sinir ağları ilk kez Warren McCulloch ve Walter Pitts isimli kişiler tarafından 1943 yılında
ortaya atılmıştır. 1940'lı yılların sonlarına doğru Donald Hebb isimli psikolog da "Hebbian Learning" kavramıyla, 1950'li
yıllarda da Frank Rosenblatt isimli araştırmacı da "Perceptron" kavramıyla alana önemli katkılarda bulunmuştur. Bu yıllar
henüz elektronik bilgisayarların çok yeni olduğu yıllardı. Halbuki yapay sinir ağlarına yönelik algoritmalar için önemli
bir CPU gücü gerekmekteydi. Bu nedenle özellikle 1960 yıllarda bu konuda bir motivasyon eksikliği oluşmuştur. Yapay sinir
ağları sonraki dönemlerde yeniden popüler olmaya başlamıştır. Derin öğrenme konusunun gündeme gelmesiyle de popülaritesi
hepten artmıştır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay Sinir ağlarının pek çok farklı alanda uygulaması vardır. Örneğin:
- Metinlerin sınıflandırılması ve kategorize edilmesi (text classification and categorization)
- Ses tanıma (speech recognition)
- Karakter tanıma (character recognition)
- İsimlendirilmiş Varlıkların Tanınması (Named Entitiy Recognition)
- Sözcük gruplarının aynı anlama gelip gelmediğinin belirlenmesi (paraphrase detection and identification)
- Metin üretimi (text generation)
- Makine çevirisi (machine translation)
- Örüntü tanıma (pattern recognition)
- Yüz tanıma (face recognition)
- Finansal uygulamalar (portföy yönetimi, kredi değerlendirmesi, sahtecilik, gayrimenkul değerlemesi, döviz fiyatlarının tahmini vs.)
- Endüstriyel problemlerin çözümü
- Biyomedikal mühendisliğindeki bazı uygulamalar (Örneğin medikal görüntü analizi, hastalığa tanı koyma ve tedavi planı oluşturma)
- Optimizasyon problemlerinin çözümü
- Pazarlama süreçlerinde karşılaşılan problemlerin çözümü
- Ulaştırma problemlerinin çözümü
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay Sinir Ağları (Artificial Neural Networks) insanın sinir sisteminden esinlenerek geliştirilmiş bir tekniktir. Sinir
sisteminin temel yapı taşı "nöron (neuron)" denilen hücrelerdir. Bir nöron bir çekirdeğe sahiptir. Nöronon başka nöröndan
gelen iletileri alan "dendrid" denilen bir bölümü vardır. Pek çok nöronda bir dal biçiminde uzanan aksonlar bulunur.
Aksonların uçlarında küçük "düğmecikler (terminal buttons)" vardır. Bir nöron ateşlendiğinde bu düğmeciklerden "nörotransmitter
(neurotransmitter)" denilen kimyasallar zerk edilir. Bunlar diğer nöronun reseptörleri tarafından alınır. Nörondaki "aksiyon
potansiyeli (action potantial)" kritik bir düzeye geldiğinde ateşleme yapılmaktadır. Bir nöron duruma göre yüzlerce nörona bağlı
olabilmektedir. Bir nöronun akson ucu ile diğer nönronun dendrid reseptörleri arasındaki bölgeye "sinaps" denilmektedir. Çeşitli
nörotransmiterler vardır. Her nörotransmiter anahtarın kilide uyması gibi farklı reseptörler tarafından alınmaktadır. Bir
nörotransmiteri sinaplarda artıran maddelere "agonist", azaltan maddelere "antagonist" denilmektedir. Agonist etki çeşitli
biçimlerde sağlanabilmektedir. Örneğin presinaptik nöronda (nörotransmiterleri zert eden nöron) nörotransmiter miktarı artırılabilir.
Post sinaptik nörondaki (nörotransmiterleri alan nöron) reseptör duyarlılığı artırılabilir. Bir nörotransmiter zerk edildikten
sonra akson uçları tarafından geri alınmaktadır. Buna "geri alım (reuptake)" işlemi denir. Reuptake mekanizmasının inhibe
edilmesiyle de sinapstaki nörotransmiter etkinliği artırılabilmektedir. Örneğin SSRI (Selective Serotonin Reuptake Inhibitors)
denilen antideprasan ilaçlar bu mekanizmayla sinapslardaki serotonin miktarını artırma iddiasındadır.
Nöronların bir kısmına "duyusal nöronlar (sensory neurons)" denilmektedir. Bunlar dış fiziksel uyaranları alıp beynin onu
işleyen bölümüne iletmekte işlev görürler. İletiler nihai olarak beyindeki bazı bölümlerde işlenmektedir. Örneğin beynin
arka kısmına "oksipital lob" denilmektedir. Görsel iletiler buradaki nöron ağı tarafından işlenirler. Bazı nöronlara ise
"motor nöronlar (motor neurons)" denilmektedir. Motor nöronlar kaslara bağlıdır ve kasların kasılmalarını sağlarlar. Örneğin
el hareketi beynin emir vermesiyle nöral iletinin ele ulaşıp oradaki kasları hareket ettirmesiyle sağlanmaktadır Dolayısıyla
bu nöral ileti bozulursa felç durumu ortaya çıkmaktadır. Nöronlarda "miyelin kılıfı (myline sheet)" denilen özel bir kılıf
bulunabilmektedir. Bu kılıf nöral iletiyi hızlandırmaktadır. Beyinde bu kılıfın olduğu nöronlar beyaz renkte gözüktüğü için
bu bölgelere "beyaz madde (white matter)", miyelinsiz nöronlar da gri bir biçimde gözüktüğü için bu nöronların bulunduğu
bölgelere de "gri madde (gray matter)" denilmektedir.
Makine öğrenmesindeki yapay sinir ağları yukarıda da belirttiğimiz gibi insanın sinir sisteminden ilham alınarak tasarlanmıştır.
Ancak artık bu ilham noktası önemini kaybetmiş gibidir. Bu nedenle pek çok uzman artık bu konuyu "yapay sinir ağları"
yerine yalnızca "sinir ağları" biçiminde ifade etmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay sinir ağları yapay nöronların birbirlerine bağlanmasıyla oluşturulmaktadır. Bir nöronun gridileri vardır ve yalnızca
bir tane de çıktısı vardır. Nöronun girdileri veri kümesindeki satırları temsil eder. Yani veri kümesindeki satırlar nöronun
girdileri olarak kullanılmaktadır. Nöronun girdilerini xi temsil edersek her girdi "ağırlık (weight) değeri" denilen bir
değerler çarpılmır ve bu çarpımların toplamları elde edilir. Ağırlık değerlerini wi ile gösteribiliriz. Bu durumda xi'lerle
wi'ler karşılıklı olarak çarpılıp toplanmaktadır. Örneğin nöronun 5 tane girdisi olsun bu 5 girdi 5 ayrıırlık değerleriyle
çarpılıp toplanacaktır.
total = x1w1 + x2w2 + x3w3 + x4w4 + x5w5
İki vektörün karşılıklı elemanlarının çarpımlarının toplamına İngilizce "dot product" denilmektedir. (Dot product işlemi np.dot
fonksiyonuyla yapılabilmektedir.) Elde edilen dot product "bias" denilen bir değerle toplanır. Biz bias dieğerini b ile temsil
edeceğiz. Örneğin:
total = x1w1 + x2w2 + x3w3 + x4w4 + x5w5 + b
Bu toplam da "aktivasyon fonsiyonu (activation function)" ya da "transfer fonksiyonu (transfer function)" denilen bir fonksiyona
sokulmaktadır. Böylece o nöronun çıktısı edilmektedir. Örneğin:
out = activation(x1w1 + x2w2 + x3w3 + x4w4 + x5w5 + b)
Biz bu işlemi vektörel olarak şöyle de gösterebiliriz:
out = activation(XW + b)
Bir nöronun çıktısı başka nöronlar girdi yapılabilir. Böylece tüm ağın nihai çıktıları oluşur. Örneğin ağımızın bir katmanında
k tane nöron olsun. Tüm girdilerin (yani Xi'lerin) bu nöronların hepsine bağlandığını varsayalım. Her nöronun ağırlık değerleri
ve bias değeri diğerlerinden farklıdır. Dolayısıyla K tane nöronun çıktısı aşağıdaki gibi bir matrisle gösterilebilir:
outs = activation(XW + b)
Artık burada X 1XN boyutunda, W matrisi ise NxK boyutunda ve b matrisi de 1XK boyutundadır. Sonuç olarak buradan K tane çıkdı değeri elde
edilecektir. Buradaki XW işleminin artık dot product melirtmediğine matris çarpımı belirttiğine dikkat ediniz. Gösterimimizdeki
X matrisi bir satır vektörü durumundadır:
X = [x1, x2, x3, ..., xn]
W matrisi aşağıdaki görünümdedir:
w11 w21 w31 ... wk1
w12 w22 w31 ... wk2
w13 w23 w33 ... wk3
... ... ... ... ...
w1n w2n w3n ... wkn
Buradaki X matrisi ile W matrisi matrisi matris çarpımına sokuldupunda 1XK boyutunda bir matris elde edilecektir. Gösterimimizdeki
b matrisi şöyle temsil edilebilir:
b = [b1, b2, b2, ...., bk]
Böylece XW + b işleminden 1XK boyutunda bir matris elde edilecektir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bir sinir ağının aöacı bir "kestirimde" bulunmaktır. Yani biz ağa girdi olarak Xi değerlerini veririz. Ağdan hedeflediğimiz
çıktıyı elde etmeye çalışırız. Ancak bunu yapabilmemiz için nöronlardaki w değerlerinin ve b değerlerinin biliniyor olması
gerekir. Sinir ağları "denetimli (supervised)" bir öğrenme modeli sunmaktadır. Daha önceden de belirttiğimiz gibi denetimli
öğrenmede önce ağın mevcut verilerle eğitilmesi gerekmektedir. İşte bir sinir ağının eğitilmesi aslında nöronlardaki w
değerlerinin ve b değerlerinin uygun biçimde belirlenmesi anlamına gelmektedir. Başka bir deyişle önce biz ağımızı mevcut
verilerle eğitip bu w ve b değerlerinin uygun biçimde oluşturulmasını sağlarız. Ondan sonra kestirim yaparız. Tabii ağ ne
kadar iyi eğitilirse ağın yapacağı kestirim de o kadar isabetli olacaktır.
Örneğin biz bir dairenin fiyatını tahmin etmeye çalışalım. Bunun için öncelikle veri toplamamız gerekir. Çeşitli daireler için
şu verilerin toplandığını varsayalım:
- Dairenin büyüklüğü
- Dairenin kaçıncı katta olduğu
- Dairenin içinde bulunduğu binanın yaşı
- Dairenin yaşam alanına uzaklığı
- Dairenin ne kadar yakında metro durağı olduğu
- Dairenin içinde bulunduğu binanın otopark miktarı
- Apartman aidatı
İşte elimizde veri kümesinin sütunlarını (özelliklerini) bu bilgiler oluşturmaktadır. Biizm çeşitli dairelerin bu bilgilerini
elde etmiş olmamız ve onların satış fiyatlarını biliyor olmamız gerekir. Yani bizim sinir ağına girdi yapacağımız bilgilerle
ın vermesi gereken gerçek çıktılardan oluşan bir veri kğmesine gereksinimimiz vardır. İşte ağın eğitimi için vu veri kümesini
kullanırız. Ağımızı bu gerçek verilerle eğittikten sonra nöronlardaki w ve b değerleri konumlandırılmış olacaktır. Artık
kestirim yapmak istediğimizde ağımıza girdi olarak yukarıdaki bilgilere sahip bir dairenin özelliklerini veririz. Ağımız da
bize çıktı olarak o dairenin olması gereken fiyatını verir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Biz yukarıda bir sinir ağının temel çalışma mekanizmasınııkladık. Ancak bir sinir ağını oluşturabilmek için bu ağın
bileşenleri hakkında daha fazla bilgi sahibi olmamız gerekir. Ağın bileşenleri hakkında aklımıza gelen tipik sorular şunlar
olnmaktadır:
- Ağın nöron katmanlarının sayısı ne olamldır?
- Ağdaki nöronların sayısı ve bağlantı biçimi ne olmalıdır?
- Nöronlarda kullanılan aktivasyon fonksiyonları nasıl olmalıdır?
- Ağdaki w ve b değerlerini oluşturmak için kullanılan optimizasyon algoritmaları nelerdir ve nasıl çalışmaktadır?
Biz de bir süreç içerisinde bu sorulara yanıtlar vereceğiz. Ancak bir noktaya dikatinizi çekmek istiyoruz: Sinir ağlarında
önemli bir işlem yükü vardır. Buradaki işlemlerin programcılar tarafından manuel bir biçimde her defasında yeniden (add-hoc)
yapılması çok zahmetlidir. İşte zamanla bu sinir ağı işlemlerini kendi içlerinde yapan kütüphaneler geliştirilmiştir.
Bugün artık uygulamacılar bu işlemleri programlar yazarak değil zaten bu konuda çözünm üreten hazır kütüphaneleri kullanarak
yapmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
İstatistikte girdi değerlerinden hareketle çıktı değerinin belirlenmesine (tahmin edilmesine) yönelik süreçlere "regresyon
(regression)" denilmektedir. Regresyon işlemleri çok çeşitli biçimlerde sınıflandırılabilmektedir. Çıktıya göre regresyon
işlemleri istatistikte tipik olarak iki grupla sınıflandırılmaktadır:
1) (Lojistik Olmayan) Regresyon İşlemleri
2) Lojistik Regresyon İşlemleri
Aslında istatistikte "regresyon" denildiğinde çoğu kez default olarak zaten "lojistik olmayan regresyon" işlemleri anlaşılmaktadır.
Bu tür regresyonlarda girdilerden hareketle kategorik değil sayısal bir çıktı elde edilmektedir. Örneğin bir dairenin
yukarıda belirttiğimiz özellikleri girdiler olabilir, dairenin fiyatı da çıktı olabilir. Burada çıktı sürekli sayısal bir
değerdir. Lojistik regresyon çıktının bir kategori biçiminde elde edildiği regresyonlara denilmektedir. Örneğin girdiler
bir resmin pixel'leri olabilir. Çıktı da bu resmin elma mı, armut mu, kayısı mı olduğuna yönelik kategorik bir bilgi olabilir.
Bu tür regresyonlara istatistikte "lojistik regresyonlar" ya da "logit regresyonları" denilmektedir. Makine öğrenmesinde
lojistik regresyon terimi yerine daha çok "sınıflandırma (classification)" terimi kullanılmaktadır. Biz kursumuzda iki terimi
de kullanacağız. Kursumuzda kategorik olmayan sayısal çıktı veren regresyonlar için çoğu kez "lojistik olmayan regresyon"
terimini kullanacağız. Lojistik regresyondaki "lojistik" sözcüğü günlük hayatta çokça karşılaştığımız "lojistik hizmetleri"
ile bir ilgisi yoktur. Buradaki "lojistik" sözcüğü mantıktaki "lojikten" gelmektedir. İngilizce "logistic" terimi "logic" ve
"statistics" terimlerinin birleştirilmesiyle uydurulmuştur.
Lojistik olsun ya da olmasın aslında regresyon işlemlerinin hepsi girdiyi çıktıya dönüştüren bir f foonksiyonunun elde edilmesi
sürecidir. Örneğin:
y = f(x1, x2, ..., xn)
İşte makine öğrenmesinde bu f fonksiyonunun elde edilmesinin çeşitli yöntemleri vardır. Yapay sinir ağlarında da aslında
bu biçimde bir f fonksiyonu bulunmaya çalışılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Sınıflandırma sürecindeki çoktının olabileceği değerlere "sınıf (class)" denilmektedir. Sınıflandırma (lojistik regresyon)
problemlerinde eğer çıktı ancak iki değerden biri olabiliyorsa bu tür sınıflandırma problemlerine de "iki sınıflı sınıflandırma
(binary classification)" problemleri denilmektedir. Örneğin bir film hakkında yazılan yorum yazısının "olumlu" ya da "olumsuz"
biçiminde iki değerden oluştuğunu düşünelim. Bu sınıflandırma işlemi ikili sınıflandırmadır. Benzer biçimde bir biyomedikal
görüntüdeki kitlenin "iyi huylu (benign)" mu "kötü huylu (malign)" mu olduğuna yönelik sınıflandırma da ikili sınıflandırmaya
örnektir. Eğer sınıflandırmada çıktı sınıflarının sayısı ikiden fazla ise böyle sınıflandırma problemlerine "çok sınıflı
(multiclass)" sınıflandırma problemleri denilmektedir. Örneğin bir resmin hangi meyveye ilişkin olduğunun tespit edilmesi
için kullanılan sınıflandırma modeli "çok sınıflı" bir modeldir.
İstatistikte "lojistik regresyon" denildiğinde aslında default olarak "iki sınıflı (binary)" lojistik regresyon anlaşılmaktadır.
Çok sınıflı lojistik regresyonlara istatistikte genellikle İngilizce "multiclass logistic regression" ya da "multinomial
logistic regression" denilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
24. Ders - 17/03/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
En basit yapay sinir ağı mimarisi tek bir nörondan oluşan mimaridir. Buna "perceptron" denilmektedir. Böyle bir model ile
"doğrusal olarak ayrıştırılabilen (linearly separable)" ikili sınıflandırma problemleri ya da (lojistik olmayan) yalın
çoklu regresyon problemleri çözülebilmektedir. Her ne kadar tek bir nöron bile bazı problemleri çözebiliyorsa da problemler
karmaşıklaştıkça ağdaki nöron sayılarının ve katmanların artırılması gerekmektedir.
Aşağıda bir nöronun bir sınıfla temsil edilmesine ilişkin bir örnek veriyoruz.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
class Neuron:
def __init__(self, w, b):
self.w= w
self.b = b
def output(self, x, activation):
return activation(np.dot(x, self.w) + self.b)
def sigmoid(x):
return np.e ** x / (1 + np.e ** x)
w = np.array([1, 2, 3])
b = 1.2
n = Neuron(w, b)
x = np.array([1, 3, 4])
result = n.output(x, sigmoid)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Bir yapay sinir ağı modelinde "katmanlar (layers)" vardır. Katman aynı düzeydeki nöron grubuna denilmektedir. Yapya sinir
ı katmanları tipik olarak üçe ayırmaktadır:
1) Girdi Katmanı (Input Layer)
2) Saklı Katmanlar (Hidden Layers)
3) Çıktı Katmanı (Output Layer)
Girdi katmanı veri kümesindeki satırları temsil eden yani ağa uygulanacak verileri belirten katmandır. Aslında girdi katmanı
gerçek anlamda nöronlardan oluşmaz. Ancak anlatımları kolaylaştırmak için bu katmanın da nöronlardan oluştuğu varsayılmaktadır.
Başka bir deyişle girdi katmanının tek bir nöron girişi ve tek bir çıktısı vardır. Bunların w değerleri 1'dir. Aktivasyon
fonksiyonları f(x) = x biçimindedir. Yani girdi katmanı bir şey yapmaz, girdiyi değiştirmeden çıktıya verir. Girdi katmanındaki
nöron sayısı veri kümesindeki sütunların (yani özelliklerin) sayısı kadar olmalıdır. Örneğin 5 tane girdiye (özelliğe) sahip olan
bir sinir ağının girdi katmanı şöyle edilebilir:
x1 ---> O --->
x2 ---> O --->
x3 ---> O --->
x4 ---> O --->
x5 ---> O --->
Buradaki O sembolleri girdi katmanındaki nöronları temsil etmektedir. Girdi katmanındaki nöronların 1 tane girdisinin 1 tane de
çıktısınn olduğuna dikkat ediniz. Buradaki nöronlar girdiyi değiştirmediğine göre bunların w değerleri 1, b değerleri 0, aktivasyon
fonksiyonu da f(x) = x biçiminde olmalıdır.
Girdiler saklı katman denilen katmanlardaki nöronlara bağlanırlar. Modelde sıfır tane, bir tane ya da birden fazla saklı katman
bulunabilir. Saklı katmanların sayısı ve saklı katmanlardaki nöronların sayısı ve bağlantı biçimleri problemin niteliğine göre
değişebilmektedir. Yani saklı katmanlardcaki nöronların girdi katmanıyla aynı sayıda olması gerekmez. Her saklı katmandaki
nöron sayıları da aynı olmak zorunda değildir.
Çıktı katmanı bizim sonucu alacağımız katmandır. Çıktı katmanındaki nöron sayısı bizim kestirmeye çalıştığımız olgularla ilgilidir.
Örneğin biz bir evin fiyatını kestirmeye çalışıyorsak çıktı katmanında tek bir nöron bulunur. Yine örneğin biz ikili sınıflandırma
problemi üzerinde çalışıyorsak çıktı katmanı yine tek bir nörondan oluşabilir. Ancak biz evin fiyatının yanı sıra evin sağlamlığını
da kestirmek istiyorsak bu durumda çıktı katmanında iki nöron olacaktır. Benzer biçimde çok sınıflı sınıflandırma problemlerinde
çıktı katmanında sınıf sayısı kadar nöron bulunur.
Bir yapay sinir ağındaki katmanların sayısı belirtilirken bazıları girdi katmanını bu sayıya dahil ederken, bazıları etmemektedir.
Bu nedenle katman sayılarını konuşurken yalnızca saklı katmanları belirtmek bu bakımdan iki anlamlılığı giderebilmektedir.
Örneğin biz "modelimizde 5 katman var" dediğimizde birisi bu 5 katmanın içerisinde girdi katmanı dahil mi diye tereddütte
kalabilir. O halde iki anlamlılığı ortadan kaldırmak için "modelimizde 3 saklı katman var" gibi bir ifade daha uygun olacaktır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bir yapay sinir ağı modelinde katman sayısının artırılması daha iyi bir sonucun elde edileceği anlamına gelmez. Benzer
biçimde katmanlardaki nöron sayılarının artırılması da daha iyi bir sonucun elde edileceği anlamına gelmemektedir. Katmanların
sayısından ziyade onların işlevleri daha önemli olmaktadır. Ağa gereksiz katman eklemek, katmanlardaki nöronları artırmak
tam ters bir biçimde ağın başarısının düşmesine de yol açabilmektedir. Yani gerekmediği halde ağa saklı katman eklemek,
katmanlardaki nöron sayısını artırmak bir fayda sağlamamakta tersine kestirim başarısını düşürebilmektedir. Ancak görüntü tanıma
gibi özel ve zor problemlerde saklı katman sayılarının artırılması gerekebilmektedir.
Pekiyi bir sinir ağı modelinde kaç tane saklı katman olmalıdır? Pratik olarak şunları söyleyebiliriz:
- Sıfır tane saklı katmana sahip tek bir nörondan oluşan en basit modele "perceptron" dendiğini belirtmiştir. Bu perceptron
"doğrusal olarak ayrıştırılabilen (linearly separable)" sınıflandırma problemlerini ve yalın doğrusal regresyon problemlerini
çözebilmektedir.
- Tek saklı katmanlı modeller aslında pek çok sınıflandırma problemini ve (doğrusal olmayan) regresyon problemlerini belli bir
yeterlilikte çözebilmektedir. Ancak tek saklı katman yine de bu tarz bazı problemler için yetersiz kalabilmektedir.
- İki saklı katman pek çok karmaşık olmayan sınıflandırma problemi için ve regresyon problemi için iyi bir modeldir. Bu nedenle
karmaşık olmayan problemler için ilk akla gelecek model iki saklı katmanlı modeldir.
- İkiden fazla saklı katmana sahip olan modeller karmaşık ve özel problemleri çözmek için kullanılmaktadır. İki saklı
katmandan fazla katmana sahip olan modellere genel olarak "derin öğrenme ağları (deep learning networks)" denilmektedir.
Yukarıda da belirttiğimiz gibi "derin öğrenme (deep learning)" farklı bir yöntemi belirtmemektedir. Derin öğrenme özel ve
karmaşık problemleri çözebilmek için ikiden fazla saklı katman içeren sinir ağı modellerini belirtmek için kullanılan bir
terimdir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay sinir ağlarında çalışmak için "alçak seviyeli" ve "yüksek seviyeli" kütüpaheneler ve framework'ler bulunmaktadır.
Aşağı seviyeli üç önemli kütüphane şunlardır: TensorFlow, PyTotch ve Theno. Yüksek seviyeli kütüphane olarak en fazla Keras
tercih edilmektedir. Keras eskiden bağımsız bir kütüphaneydi ve kendi içinde TensorFlow, Theno, Microsoft Coginitive Toolkit,
PlaidML gibi kütüphaneleri "backend" olarak kullanbiliyordu. Ancak daha sonraları Keras TensorFlow bünyesine katılmıştır.
Dolayısıyla artık 2.3 ile birlikte Keras TensorFlow kütüphanesinin bir parçası haline gelmiştir. Biz kursumuzda önce Keras
üzerinde ilerleyeceğiz. Ancak sonra diğer aşağı seviyeli kütüphaneleri de gözden geçireceğiz. Keras'la çalışmak için
TensorFlow kütüphanesinin kurulması gerekmektedir. Kurulum şöyle yapılabilir:
pip install tensorflow
TensorFlow çok thread'li ve paralel işlem yapan bir kütüphanedir. Paralel işlemler sırasında grafik kartınının olanaklarını
da kullanmaktadır. Eğer Windows sistemlerinde NVidia kartlarını kullanıyorsanız TensorFlow kütüphanesinin performansını
artırabilmek için Cuda isimli kütüphaneyi ayrıca yüklemelisiniz. Yükleme aşağıdaki bağlantıdan yapılabilir:
https://developer.nvidia.com/cuda-downloads?target_os=Windows&target_arch=x86_64&target_version=10&target_type=exe_local
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bir veri kümesini CSV dosyasından okuduktan sonra onu Keras'ın kullanımına hazırlamak için bazı işlemlerin yapılması gerekir.
Yapılması gereken ilk işlem veri kümedinin dataset_x ve dataset_y biçiminde iki parçaya ayrılmasıdır. Çünkü ağın eğitilmesi,
sırasında girdilerle çıktıların ayrıştırılması gerekmektedir. Burada dataset_x girdileri dataset_y ise kestirilecek çıktıları
belirtmektedir.
Eğitim bittikten sonra genellikle ağın kestirimine hangi ölçüde güvenileceğini belirleyebilmek için bir test işlemi yapılır.
ın kestirim başarısı "test veri kümesi" denilen bir veri kümesi ile yapılmaktadır. Test veri kümesinin eğitimde kullanılmayan
bir veri kümesi biçiminde olması gerekir. (Örneğin bir sınavda yalnızca sınıfta çözülen sorular sorulursa test işleminin başarısı
düşebilecektir.) O halde bizim ana veri kümesini "eğitim veri kümesi" ve "test veri kümesi" biçiminde iki kısma ayırmamız gerekir.
Oranlar çeşitli koşullara göre değişebilirse de tipik olarak %80'lik verinin eğitim için %20'lik verinin test için kullanılması
tercih edilmektedir.
Eğitim ve test veri kümesini manuel olarak ayırabiliriz. Ancak ayırma işleminden önce veri kümesini satırsal bakımdan karıştırmak
uygun olur. Çünkü bazı veri kümeleri CSV dosyasına karışık bir biçimde değil yanlı bir biçimde kodlanmış olabilmektedir. Örneğin
bazı veri kümeleri bazı alanlara göre sıraya dizilmiş bir biçimde bulunabilmektedir. Biz onun baştaki belli kısmını eğitim, sondaki
belli kısmını test veri kümesi olarak kullanırsak eğitim ve test veri kümeleri yanlı hale gelebilmektedir.
Biz programlarımızda genellikle bir veri kümesinin tamamı için "dataset" ismini, onun girdi kısmı için "dataset_x" ismini,
çıktı kısmı için "dataset_y" ismini kullanacağız. Veri kümesini eğitim ve test olarak ayırdığımızda da isimleri şu biçimde
vereceğiz: "training_dataset_x", "training_dataset_y", "test_dataset_x", "test_dataset_y".
Aşağıda bir kişinin çeşitli biyomedikal bilgilerinden hareketle onun şeker hastası olup olmadığını anlamaya yönelik bir
veri kümesinin manuel ayrıştırılması örneği verilmiştir. Veri kümesini aşağıdaki bağlantıdan indirebilirsiniz:
https://www.kaggle.com/datasets/uciml/pima-indians-diabetes-database
Bu kümesinde bazı sütunlar eksik veri içermektedir. Bu eksik verile NaN biçiminde değil 0 biçiminde kodlanmıştır. Biz bu
eksik verileri ortaalama değerle doldurabiliriz. Eksik veri içeren sütunlar şunlardır:
Glucose
BloodPressure
SkinThickness
Insulin
BMI
#----------------------------------------------------------------------------------------------------------------------------
TRAINING_RATIO = 0.80
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
df[['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']] = si.fit_transform(df[['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']])
dataset = df.to_numpy()
import numpy as np
np.random.shuffle(dataset)
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
training_len = int(np.round(len(dataset_x) * TRAINING_RATIO))
training_dataset_x = dataset_x[:training_len]
test_dataset_x = dataset_x[training_len:]
training_dataset_y = dataset_y[:training_len]
test_dataset_y = dataset_y[training_len:]
#----------------------------------------------------------------------------------------------------------------------------
25. Ders - 23/03/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Veri kümesini eğitim ve test olarak ayırma işlemi için sklearn.model_selection modülündeki train_test_split isimli fonksiyon
sıkça kullanılmaktadır. Biz de programlarımızda çoğu zaman bu fonksiyonu kullanacağız. Fonksiyon NumPy dizilerini ya da Pandas
DataFrame ve Series nesnelerini ayırabilmektedir. Fonksiyon bizden dataset_x ve dataset_y değerlerini ayrı ayrı ister. test_size
ya da train_size parametreleri 0 ile 1 arasında test ya da eğitim verilerinin oranını belirlemek için kullanılmaktadır.
train_test_split fonksiyonu bize 4'lü bir liste vermektedir. Listenin elemanları sırasıyla şunlardır: training_dataset_x,
test_dataset_x, training_dataset_y, test_dataset_y. Örneğin:
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
Aslında fonksiyonun train_size ve test_size parametreleri float yerine int olarak da girilebilir. Bu parametreler için int
türden değer verilirse bu int değer eğitim ve test veri kümesinin sayısını belirtmektedir. Ancak genellikle uygulamacılar
bu parametreler için oransal değerler vermeyi tercih etmektedir.
train_test_split fonksiyonu veri kümesini bölmeden önce karıştırma işlemini de yapmaktadır. Fonksiyonun shuffle parametresi
False yapılırsa fonksiyon karıştırma yapmadan bölme işlemini yapar.
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
Burada fonksiyona dataset_x ve dataset_y girdi olarak verilmiştir. Fonksiyon bunları bölerek dörtlü bir listeye geri dönmüştür.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
df[['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']] = si.fit_transform(df[['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']])
dataset = df.to_numpy()
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
#----------------------------------------------------------------------------------------------------------------------------
train_test_split fonksiyonu ile biz doğrudan Pandas DataFrame ve Series nesneleri üzerinde de bölme işlemini yapabiliriz.
Bu durumda fonksiyon bize dörtlü listeyi DataFrame ve Series nesneleri biçiminde verecektir. Aşağıda bu biçimde bölmeye
bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1]
dataset_y = df.iloc[:, -1]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Keras'ta bir sinir ağı oluşturmanın çeşitli adımları vardır. Burada sırasıyla bu adımlardan ve adımlarla ilgili bazı olgulardan
bahsedeceğiz.
1) Öncelikle bir model nesnesi oluşturulmalıdır. tensorflow.keras modülü içerisinde çeşitli model sınıfları bulunmaktadır.
En çok kullanılan model sınıfı Sequential isimli sınıftır. Tüm model sınıfları Model isimli sınıftan türetilmiştir. Sequential
modelde ağa her eklenen katman sona eklenir. Böylece ağ katmanların sırasıyla eklenmesiyle oluşturulur. Sequential nesnesi
yaratılırken name parametresiyle modele bir isim de verilebilir. Örneğin:
from tensorflow.keras import Sequential
model = Sequential(name='Sample')
Aslında Sequential nesnesi yaratılırken katmanlar belirlendiyse layers parametresiyle bu katmanlar da verilebilmektedir. Ancak
sınıfın tipik kullanımında katmanlar daha sonra izleyen maddelerde ele alınacağı gibi sırasıyla eklenmektedir.
2) Model nesnesinin yaratılmasından sonra katman nesnelerinin oluşturulup model nesnesine eklenmesi gerekir. Keras'ta farklı
gereksinimler için farklı katman sınıfları bulundurulmuştur. En çok kullanılan katman sınıfı tensorflow.keras.layers modülündeki
Dense sınıfıdır. Dense bir katman modele eklendiğinde önceki katmandaki tüm nöronların çıktıları eklenen katmandaki nöronların
hepsine girdi yapılmaktadır. Bu durumda örneğin önceki katmanda k tane nöron varsa biz de modele n tane nörondan oluşan bir Dense
katman ekliyorsak bu durumda modele k * n + n tane yeni parametre (yani tahmin edilmesi gereken parametre) eklemiş oluruz. Burada
k * n tane ayarlanması gereken w (ağırlık) değerleri ve n tane de ayarlanması gereken bias değerleri söz konusudur. Bir nörondaki
w (ağırlık) değerlerinin o nörona giren nöron sayısı kadar olduğuna ve bias değerlerinin her nöron için bir tane olduğuna dikkat
ediniz.
Dense sınıfının __init__ metodunun ilk parametresi eklenecek katmandaki nöron sayısını belirtir. İkinci parametre olan activation
parametresi o katmandaki tüm nöronların aktivasyon fonksiyonlarının ne olacağını belirtmektedir. Bu parametreye aktivasyon
fonksiyonları birer yazı biçiminde isimsel olarak girilebilir. Ya da tensorflow.keras.activations modülündeki fonksiyonlar
olarak girilebilir. Örneğin:
from tensorflow.keras.layers import Dense
layer = Dense(100, activation='relu')
ya da örneğin:
from tensorflow.keras.activations import relu
layer = Dense(100, activation=relu)
Dense fonksiyonun use_bias parametresi default durumda True biçimdedir. Bu parametre katmandaki nöronlarda "bias" değerinin
kullanılıp kullanılmayacağını belirtmektedir. Metodun kernel_initializer parametresi katmandaki nöronlarda kullanılan w
parametrelerinin ilkdeğerlerinin rastgele biçimde hangi algoritmayla oluşturulacağını belirtmektedir. Bu parametrenin default
değeri "glorot_unfiorm" biçimindedir. Metodun bias_initializer parametresi ise katmandaki nöronların "bias" değerlerinin başlangıçta
nasıl alınacağını belirtmektedir. Bu parametrenin default değeri de "zero" biçimdedir. Yani bias değerleri başlangıçta 0
durumundadır. Dense sınıfının __init__ metodunun diğer parametreleri hakkında bu aşamada bilgi vermeyeceğiz.
Keras'ta Sequential modelde girdi katmanı programcı tarafından yaratılmaz. İlk saklı katman yaratılırken girdi katmanındaki
nöron sayısı input_dim parametresiyle ya da input_shape parametresiyle belirtilmektedir. input_dim tek boyutlu girdiler için
input_shape ise çok boyutlu girdiler için kullanılmaktadır. Örneğin:
layer = Dense(100, activation='relu', input_dim=8)
Tabii input_dim ya da input_shape parametrelerini yalnızca ilk saklı katmanda kullanabiliriz. Genel olarak ağın girdi katmanında
dataset_x'tekü sütun sayısı kadar nöron olacağına göre ilk katmandaki input_dim parametresini aşağıdaki gibi de girebiliriz:
layer = Dense(100, activation='relu', input_dim=training_dataset_x.shape[1])
Aslında Keras'ta girdi katmanı için tensorflow.keras.layers modülünde Input isminde bir karman da kullanılmaktadır. Tenseoflow'un
yeni versiyonlarında girdi katmanının Input katmanı ile oluşturulması istenmektedir. Aksi takdirde bu yeni versiyonlar uyarı
vermektedir. Girdi katmanını Input isimli katman sınıfıyla oluştururken bu Input sınıfının __init__ metodunun birinci parametresi
bir demet biçiminde (yani sahpe olarak) girilmelidir. Örneğin:
input = Input((8, ))
Burada 8 nöronşuk bir girdi katmanı oluşturulmuştur. Yukarıda da belirttiğimiz gibi eskiden ilk saklı katmanda girdi katmanı
belirtiliyordu. Ancak Tensorflow kütüphanesinin yeni verisyonlarında ilk saklı katmanda girdi katmanının belirtilmesi artık
uyarıya (warning) yol açmaktadır. Önceki kurslarda biz girdi katmanını ilk saklı katmanda belirtiyorduk. Ancak artık bu kursumuzda
girdi katmanını ayrı bir Input nesnesi ile oluşturacağız.
Her katmana istersek name parametresi ile bir isim de verebiliriz. Bu isimler model özeti alınırken ya da katmanlara erişilirken
kullanılabilmektedir. Örneğin:
layer = Dense(100, activation='relu', name='Hidden-1')
Oluşturulan katman nesnesinin model nesnesine eklenmesi için Sequential sınıfının add metodu kullanılmaktadır. Örneğin:
input = Input((8, ))
model.add(input)
layer = Dense(100, activation='relu', name='Hidden-1')
model.add(layer)
Programcılar genellikle katman nesnesinin yaratılması ve eklenmesini tek satırda aşağıdaki gibi yaparlar:
model.add(Dense(100, activation='relu', input_dim=9, name='Hidden-1'))
3) Modele katmanlar eklendikten sonra bir özet bilgi yazdırılabilir. Bu işlem Sequential sınıfının summary isimli metoduyla
yapılmaktadır.
Yukarıda da belirttiğimiz gibi bir katmandaki "eğitilebilir (trainable)" parametrelerin sayısı şöyle hesaplanmaktadır: Önceki
katmanın çıktısındaki nöron sayısı, bu katmana dense biçimde bağlandığına göre toplamda "önceki katmandaki nöron sayısı *
bu katmandaki nöron sayısı" kadar w parametresi ayarlanacaktır. Öte yandan bu katmandaki nöronların her birinde bir "bias"
değeri olduğuna göre bu değerler de bu sayıya toplanmalıdır. O zaman önceki katmandaki nöron sayısı k, bu katmandaki nöron
sayısı n ise eğitilebilir parametrelerin sayısı k * n + n tane olur. Aşağıdaki modeli inceleyiniz:
model = Sequential(name='Diabetes')
model.add(Input((training_dataset_x.shape[1],)))
model.add(Dense(16, activation='relu', name='Hidden-1'))
model.add(Dense(16, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
B modelde bir girdi katmanı, iki saklı katman (biz bunlara ara katman da diyeceğiz) bir de çıktı katmanı vardır.
summary metodundan elde edilen çıktı şöyledir.
Model: "Diabetes"
┌─────────────────────────────────┬────────────────────────┬───────────────┐
│ Layer (type) │ Output Shape │ Param # │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Hidden-1 (Dense) │ (None, 16) │ 144 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Hidden-2 (Dense) │ (None, 16) │ 272 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Output (Dense) │ (None, 1) │ 17 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 433 (1.69 KB)
Trainable params: 433 (1.69 KB)
Non-trainable params: 0 (0.00 B)
Trainable params: 433 (1.69 KB)
Non-trainable params: 0 (0.00 B)
Burada ağımızdaki girdi katmanında 8 nöron olduğuna göre ve ilk saklı katmanda da 16 nöron olduğuna göre ilk saklı katmana
8 * 16 nöron girmektedir. Öte yandan her nöronun bir tane bias değeri de olduğuna göre ilk katmandaki tahmin ayarlanması
gereken parametrelerin (trainable parameters) sayısı 8 * 16 + 16 = 144 tanedir. İkinci saklı katmana 16 nöron dense biçimde
bağlanmıştır. O halde ikinci saklı katmandaki ayarlanması gereken parametreler toplamda 16 * 16 + 16 = 272 tanedir. Modelimizin
çıktı katmanında 1 nöron vardır. Önceki katmanın 16 çıkışı olduğuna göre bu çıktı katmanında 16 * 1 + 1 = 17 tane ayarlanması
gereken parametre vardır.
ın saklı katmanlarında en çok kullanılan aktivasyon fonksiyonu "relu" isimli fonksiyondur. İkili sınıflandırma problemlerinde
çıktı katmanı tek nörondan oluşur ve bu katmandaki aktivasyon fonksiyonu "sigmoid" fonksiyonu olur. Sigmoid fonksiyonu 0 ile
1 arasında bir değer vermektedir. Biz aktivasyon fonksiyonlarını izleyen paragraflarda ele alacağız.
Aşağıdaki örnekte "dibates" veri kümesi üzerinde ikili sınıflandırma problemi için bir sinir ağı oluşturulmuştur.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
df[['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']] = si.fit_transform(df[['Glucose', 'BloodPressure',
'SkinThickness', 'Insulin', 'BMI']])
dataset = df.to_numpy()
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense
model = Sequential(name='Diabetes')
model.add(Input((training_dataset_x.shape[1],)))
model.add(Dense(16, activation='relu', name='Hidden-1'))
model.add(Dense(16, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
#----------------------------------------------------------------------------------------------------------------------------
26. Ders - 24/03/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
4) Model oluşturulduktan sonra modelin derlenmesi (compile edilmesi) gerekir. Buradaki "derleme" makine diline dönüştürme
anlamında bir terim değildir. Eğitim için bazı belirlemelerin yapılması anlamına gelmektedir. Bu işlem Sequential sınıfının
compile isimli metoduyla yapılmaktadır. Modelin compile metoduyla derlenmesi sırasında en önemli iki parametre "loss fonksiyonu"
ve "optimizasyon algoritması"dır. Eğitim sırasında ağın ürettiği değerlerin gerçek değerlere yaklaştırılması için w ve bias
değerlerinin nasıl güncelleneceğine ilişkin algoritmalara "optimizasyon algoritmaları" denilmektedir. Matematiksel optimizasyon
işlemlerinde belli bir fonksiyonun minimize edilmesi istenir. İşte minimize edilecek bu fonksiyona da "loss fonksiyonu"
denilmektedir. Başka bir deyişle optimizasyon algoritması loss fonksiyonun değerini minimize edecek biçimde işlem yapan
algoritmadır. Yani optimizasyon algoritması loss fonksiyonunu minimize etmek için yapılan işlemleri temsil etmektedir.
Loss fonksiyonlarıın ürettiği değerlerle gerçek değerler arasındaki farklılığı temsil eden fonksiyonlardır. Loss fonksiyonları
genel olarak iki girdi alıp bir çıktı vermektedir. Loss fonksiyonunun girdileri gerçek değerler ile ağın ürettiği değerlerdir.
Çıktı değeri ise aradaki farklığı belirten bir değerdir. Eğitim sırasında gitgide loss fonksiyonun değerinin düşmesini bekleriz.
Tabii loss değerinin düşmesi aslında ağın gerçek değerlere daha yakın değerler üretmesi anlamına gelmektedir. loss fonksiyonları
çıktının biçimine yani problemin türüne bağlı olarak seçilmektedir. Örneğin ikili sınıflandırma problemleri için "binary cross-entropy",
çoklu sınıflandırma problemleri için "categorical cross-entropy", lojistik olmayan regresyon problemleri için "mean squared error"
isimli loss fonksiyonları tercih edilmektedir.
Optimizasyon algoritmaları aslında genel yapı olarak birbirlerine benzemektedir. Pek çok problemde bu algoritmaların çoğu benzer
performans göstermektedir. En çok kullanılan optimizasyon algoritmaları "rmsprop", "adam" ve "sgd" algoritmalarıdır. Bu algoritmalar
"gradient descent" denilen genel optimizasyon yöntemini farklı biçimlerde uygulamaktadır. Optimizasyon algoritmaları kursumuzun
ilerleyen bölümlerinde ele alınacaktır.
compile metodunda optimizasyon algoritması bir yazı olarak ya da tensorflow.keras.optimizers modülündeki sınıflar türünden bir
sınıf nesnesi olarak girilebilmektedir. Örneğin:
model.compile(optimizer='rmsprop', ...)
Örneğin:
from tensorflow.keras.optimizers import RMSprop
rmsprop = RMSprop()
model.compile(optimizer=rmsprop, ...)
Tabii optimizer parametresinin bir sınıf nesnesi olarak girilmesi daha detaylı belirlemelerin yapılmasına olanak sağlamaktadır.
Optimizasyon işlemlerinde bazı parametrik değerler vardır. Bunlara makine öğrenmesinde "üst düzey parametreler (hyper parameters)"
denilmektedir. İşte optimizasyon algoritması bir sınıf nesnesi biçiminde verilirse bu sınıfın __init__ metodunda biz bu üst
düzey parametreleri istediğimiz gibi belirleyebiliriz. Eğer optimizasyon algoritması yazısal biçimde verilirse bu üst düzey
parametreler default değerlerle kullanılmaktadır. optimzer parametresinin default değeri "rmsprop" biçimindedir. Yani biz bu
parametre için değer girmezsek default optimazyon algoritması "rmsprop" olarak alınacaktır.
loss fonksiyonu compile metoduna yine isimsel olarak ya da tensorflow.keras.losses modülündeki sınıflar türünden sınıf nesleri
biçiminde ya da doğrudan fonksiyon olarak girilebilmektedir. loss fonksiyonları kısa ya da uzun isim olarak yazısal biçimde
kullanılabilmektedir. Tipik loss fonksiyon isimleri şunlardır:
'mean_squared_error' ya da 'mse'
'mean_absolute_error' ya da 'mae'
'mean_absolute_percentage_error' ya da 'mape'
'mean_squared_logarithmic_error' ya da 'msle'
'categorical_crossentropy'
'binary_crossentropy'
tensorflow.keras.losses modülündeki fonksiyonlar da kısa ve uzun isimli olarak bulunabilmektedir:
tensorflow.keras.losses.mean_squared_error ya da tensorflow.keras.losses.mse
tensorflow.keras.losses.mean_absolute_error ya da tensorflow.keras.losses.mae
tensorflow.keras.losses.mean_absolute_percentage_error ya da tensorflow.keras.losses.mape
tensorflow.keras.losses.mean_squared_logarithmic_error ya da tensorflow.keras.losses.msle
tensorflow.keras.losses.categorical_crossentropy
tensorflow.keras.losses.binary_crossentropy
Bu durumda compile metodu örnek bir biçimde şöyle çağrılabilir:
model.compile(optimizer='rmsprop', loss='binary_crossentropy')
compile metodunun üçüncü önemli parametresi "metrics" isimli parametredir. metrics parametresi bir liste ya da demet olarak
girilir. metrics parametresi daha sonra açıklanacak olan "sınama (validation)" işlemi için kullanılacak fonksiyonları
belirtmektedir. Sınamada her zaman zaten bizim loss fonksiyonu olarak belirttiğimiz fonksiyon kullanılmaktadır. metrics
parametresinde ilave fonksiyonlar da girilebilmektedir. Örneğin ikili sınıflandırma problemleri için tipik olarak "binary_accuracy"
denilen metrik fonksiyon, çoklu sınıflandırma için "categorical_accuracy" denilen metrik fonksiyon ve lojistik olmayan regresyon
problemleri için de "mean_absolute_error" isimli metrik fonksiyon sıklıkla kullanılmaktadır. Örneğin ikili sınıflandırma
problemi için biz eğitim sırasında "loss" değerinin yanı sıra "binary_accuracy" değerini de elde etmek isteyelim. Bu durumda
compile metodunu şöyle çağırmalıyız:
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
5) Model derlenip çeşitli belirlemeler yapıldıktan sonra artık gerçekten eğitim aşamasına geçilir. Eğitim süreci Sequential
sınıfının fit metoduyle yapılmaktadır. fit metodunun en önemli parametresi ilk iki parametre olan x ve y veri kümeleridir.
Biz burada training_dataset_x ve training_dataset_y verilerini fit metodunun ilk iki paramtresine geçirmeliyiz.
fit metodunun önemli bir parametresi batch_size isimli parametredir. Eğitim işlemi aslında satır satır değil batch batch
yapılmaktadır. batch bir grup satıra denilmektedir. Yani ağa bir grup satır girdi olarak verilir. Ağdan bir grup çıktı elde
edilir. Bu bir grup çıktı ile bu çıktıların gerçek değerleri loss fonksiyonuna sokulur ve optimizasyon algoritması çalıştırılarak
w ve bias değerleri güncellenir. Yani optimizasyon algoritması her batch işlemden sonra devreye sokulmaktadır. Batch büyüklüğü
fit metodunda batch_size parametresiyle belirtilmektedir. Bu değer girilmezse batch_size 32 olarak alınmaktadır. 32 değeri pek
çok uygulama için uygun bir değerdir. Optimizasyon işleminin satır satır yapılması yerine batch batch yapılmasının iki önemli
nedeni vardır: Birincisi işlem miktarının azaltılması, dolayısıyla eğitim süresinin kısaltılmasıdır. İkincisi ise "overfitting"
denilen olumsuz durum için bir önlem oluşturmasıdır. Overfitting hakkında ileride açıklama yapılacaktır.
fit metodunun diğer önemli parametresi de "epochs" isimli parametredir. eğitim veri kümesinin eğitim sırasında yeniden
eğitimde kullanılmasına "epoch" işlemi denilmektedir. Örneğin elimizde 1000 satırlık bir eğitim veri kümesi olsun. batch_size
parametresinin de 20 olduğunu varsayalım. Bu durumda bu eğitim veri kümesi 1000 / 20 = 50 batch işleminde bitecektir. Yani
model parametreleri 50 kere ayarlanacaktır. Pek çok durumda eğitim veri kümesinin bir kez işleme sokulması model parametrelerinin
iyi bir biçimde konumlandırılması için yetersiz kalmaktadır. İşte eğitim veri kümesinin birden fazla kez yani fit metodundaki
epochs sayısı kadar yeniden eğitimde kullanılması yoluna gidilmektedir. Pekiyi epochs değeri ne olmalıdır? Aslında bunu uygulamacı
belirler. Az sayıda epoch model parametrelerini yeterince iyi konumlandıramayabilir. Çok fazla sayıda epoch "overfitting"
denilen olumsuz duruma zemin hazırlayabilir. Ayrıca çok fazla epoch eğitim zamanını da uzatmaktadır. Uygulamacı epoch'lar
sırasında modelin davranışına bakabilir ve uygun epoch sayısında işlemi kesebilir. Eğitim sırasında Keras bizim belirlediğimiz
fonksiyonları çağırabilmektedir. Buna Keras'ın "callback" mekanizması denilmektedir. Uygulamacı bu yolla model belli bir
duruma geldiğinde eğitim işlemini kesebilir. Ya da uygulamacı eğer eğitim çok uzamayacaksa yüksek bir epoch ile eğitimini
yapabilir. İşlemler bitince epoch'lardaki performansa bakabilir. Olması gerekn epoch değerini kestirebilir. Sonra modeli
yeniden bu sayıda epoch ile eğitir. fit metodunun shuffle parametresi her epoch'tan sonra eğitim veri kümesinin karıştırılıp
karıştırılmayacağını belirtmektedir. Bu parametre default olarak True biçimdedir. Yani eğitim sırasında her epoch'ta eğitim
veri kümesi karıştırılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
27. Ders - 30/03/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Modelin fit metodu ile eğitilmesi sırasında "sınama (validation)" denilen önemli bir kavram daha devreye girmektedir. Sınama
işlemi test işlemine benzemektedir. Ancak test işlemi tüm model eğitildikten sonra yapılırken sınama işlemi her epoch'tan
sonra modelin eğitim sürecinde yapılmaktadır. Başka bir deyişle sınama işlemi model eğitilirken yapılan test işlemidir.
Epoch'lar sırasında modelin performansı hakkında bilgi edinebilmek için sınama işlemi yapılmaktadır. Sınamanın yapılması
için fit metodunun validation_split parametresinin 0 ile 1 arasında oransal bir değer olarak girilmesi gerekir. Bu oransal
değer eğitim veri kümesinin yüzde kaçının sınama için kullanılacağını belirtmektedir. Örneğin validation_split=0.2 eğitim
veri kümesinin %20'sinin sınama için kullanılacağını belirtmektedir. fit metodu işin başında eğitim veri kümesini eğitimde
kullanılacak kısım ile sınamada kullanılacak kısım biçiminde ikiye ayırmaktadır. Sonra her epoch'ta yalnızca eğitimde kullanılacak
kümeyi karıştırmaktadır. Sınama işlemi aynı kümeyle her epoch sonrasında karıştırılmadan yapılmaktadır. fit metodunda ayrıca
bir de validation_data isimli bir parametre vardır. Bu parametre sınama verilerini girmek için kullanılmaktadır. Bazen programcı
sınama verilerinin eğitim veri kümesinden çekilip alınmasını istemez. Onu ayrıca fit metoduna vermek isteyebilir. Tabii validation_data
parametresi girildiyse artık validation_split parametresinin bir anlamı yoktur. Bu parametre girilse bile artık fonksiyon tarafından
dikkate alınmaz. Örneğin:
model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2)
validation_split parametresinin default değerinin 0 olduğuna dikkat ediniz. validation_split değerinin 0 olması epoch'lar sonrasında
sınama işleminin yapılmayacağı anlamına gelmektedir.
Pekiyi modelin fit metodunda her epoch'tan sonra sınama işleminde hangi ölçümler ekrana yazdırılacaktır? İşte compile metodunda
belirtilen ve ismine metrik fonksiyonlar denilen fonksiyonlar her epoch işlemi sonucunda ekrana yazdırılmaktadır. Her epoch
sonucunda fit metodu şu değerleri yazdırmaktadır:
- Epoch sonrasında elde edilen loss değerlerinin ortalaması
- Epoch sonrasında eğitim veri kümesinin kendisi için elde edilen metrik değerlerin ortalaması
- Epoch sonrasında sınama için kullanılan sınama verilerinden elde edilen loss değerlerinin ortalaması
- Epoch sonrasında sınama için kullanılan sınama verilerinden elde edilen metrik değerlerin ortalaması
Bir epoch işleminin batch batch yapıldığını anımsayınız. Budurumda epoch sonrasında fit tarafından ekrana yazdırılan değerler
bu batch işlemlerden elde edilen ortalama değerlerdir. Yani çrneğin her batch işleminden bir loss değeri elde edilir. Sonra
bu loss değerlerinin ortalaması hesap edilerek yazdırılır. Eğitim veri kümesindeki değerler ile sınama veri kümesinden elde
edilen değerler birbirine karışmasın diye fit metodu sınama verilerinden elde edilen değerlerin başına "val_" öneki getirmektedir.
Örneğin biz ikili sınıflandırma problemi üzerinde çalışıyor olalım ve metrik fonksion olarak "binary_accuracy" kullanmış olalım.
fit metodu her epoch sonrasında şu değerleri ekrana yazdıracaktır:
loss (eğitim veri kümesinden elde edilen ortalama loss değeri)
binary_accuracy (eğitim veri kümesinden elde edilen ortalama metrik değer)
val_loss (sınama veri kümesinden elde edilen ortalama loss değeri)
val_binary_accuracy (sınama veri kümesinden elde edilen ortalama metrik değer)
Tabii compile metodunda birden fazla metirk değer de belirtilmiş olabilir. Bu durumda fit tüm bu metrik değerlerin ortalamasını
ekrana yazdıracaktır. fit tarafından ekrana yazdırılan örnek bir çıktı şöyle olabilir:
....
Epoch 91/100
16/16 [==============================] - 0s 3ms/step - loss: 0.5536 - binary_accuracy: 0.7230 - val_loss: 0.5520 -
val_binary_accuracy: 0.7480
Epoch 92/100
16/16 [==============================] - 0s 3ms/step - loss: 0.5392 - binary_accuracy: 0.7251 - val_loss: 0.5588 -
val_binary_accuracy: 0.7805
Epoch 93/100
16/16 [==============================] - 0s 3ms/step - loss: 0.5539 - binary_accuracy: 0.7088 - val_loss: 0.5666 -
val_binary_accuracy: 0.8049
...
Burada loss değeri epoch sonrasında eğitim verilerinden elde edilen ortalama loss değerini, val_loss değeri epoch sonrasında
sınama verilerinden elde edilen ortalama loss değerini, binary_accuracy epoch sonrasında eğitim verilerinden elde edilen
ortalama isabet yüzdesini ve val_binary_accuracy ise epoch sonrasında sınama verilerinden elde edilen ortalama isabet
yüzdesini belirtmektedir.
Eğitim sırasında eğitim veri kümesindeki başarının sınama veri kümesinde görülmemesi eğitimin kötü bir yöne gittiğine işaret
etmektedir. Örneğin ikili sınıflandırma probleminde epoch sonucunda eğitim veri kümesindeki binary_accuracy değerinin %99 olduğunu
ancak val_binary_accuracy değerinin %65 olduğunu düşünelim. Bunun anlamı ne olabilir? Bu durum aslında epoch'lar sırasında
modelin bir şeyler öğrendiği ama bizim istediğimiz şeyleri öğrenemediği anlamına gelmektedir. Çünkü eğitim veri kümesini
epoch'larla sürekli bir biçimde gözden geçiren model artık onu ezberlemiştir. Ancak o başarıyı eğitimden bağımsız bir veri
kümesinde gösterememektedir. İşte bu olguya "overfitting" denilmektedir. Yanlış bir şeyin öğrenilmesi bir şeyin öğrenilememesi
kadar kötü bir durumdur. Overfitting oluşumunun çeşitli nedenleri vardır. Ancak overfitting epoch'lar dolayısıyla oluşuyorsa
epoch'ları uygun bir noktada kesmek gerekir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
6) fit işleminden sonra artık model eğitilmiştir. Onun test veri kümesiyle test edilmesi gerekir. Bu işlem Sequential sınıfının
evaluate isimli metodu ile yapılmaktadır. evaluate metodunun ilk iki parametresi test_dataset_x ve test_dataset_y değerlerini
almaktadır. Diğer bir parametresi yine batch_size parametresidir. Buradaki bacth_size eğitim işlemi yapılırken fit metodunda
kullanılan batch_size ile benzer anlamdadır ancak işlevleri farklıdır. Model test edilirken test işlemi de birer birer değil batch
batch yapılabilir. Ancak bu batch'ler arasında herhangi bir şey yapılmamaktadır. (Eğitim sırasındaki batch işlemleri sonrasında
ağ parametrelerinin ayarlandığını anımsayınız. Test işlemi sırasında böyle bir ayarlama yapılmamaktadır.) Buradaki batch değeri
işlemlerin hızlı yapılması üzerinde etkili olmaktadır. Yine batch_size parametresi girilmezse default 32 alınmaktadır. evaluate
metodu bir liste geri döndürmektedir. Listenin ilk elemanı test veri kümesinden elde edilen loss fonksiyonunun değeridir. Diğer
elemanları da sırasıyla metrik olarak verilen fonksiyonların değerleridir. Örneğin:
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
Aslında eval_result listesinin elemanlarının hangi anlamlara geldiğini daha iyi ifade edebilmek için Sequential sınıfında
metrics_names isimli bir örnek öznietliği (instance attribute) bulundurulmuştur. Bu metrics_names listesindeki isimler bire bir
evalute metodunun geri döndürdüğü liste elemanları ile örtüşmektedir. Bu durumda evaluate metodunun geri döndürdüğü listeyi
aşağıdaki gibi de yazdırabiliriz:
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
Aynı şeyi built-in zip fonksiyonuyla da şöyle yapabilirdik:
for name, value in zip(model.metrics_names, eval_result):
print(f'{name}: {value}')
#----------------------------------------------------------------------------------------------------------------------------
<BURADA KALDIK>
#----------------------------------------------------------------------------------------------------------------------------
7) Artık model test de edilmiştir. Şimdi sıra "kestirim (prediction)" yapmaya gelmiştir. Kestirim işlemi için Sequential
sınıfının predict metodu kullanılır. Biz bu metoda girdi katmanına uygulanacak sütun verilerini veriririz. predict metodu da
bize çıktı katmanındaki nöronların değerlerini verir. predict metoduna biz her zaman iki boyutlu bir numpy dizisi vermeliyiz.
Çünkü predict metodu tek hamlede birden çok satır için kestirim yapabilmektedir. Biz predict metoduna bir satır verecek olsak
bile onu iki boyutlu bir matris biçiminde vermeliyiz. Örneğin:
import numpy as np
predict_data = np.array([[2,148,58,37,128,25.4,0.699,24], [7,114,67,0,0,32.8,0.258,42],
[5,99,79,27,0,29,0.203,32], [0,110,85,30,0,39.5,0.855,39]])
predict_result = model.predict(predict_data)
print(predict_result)
predict metodu bize tahmin edilen değerleri iki boyutlu bir NumPy dizisi biçiminde vermektedir. Bunun nedeni aslında ağın birden fazla
çıktısının olabilmesidir. Eğer ağın bir çıktısı varsa bu durumda predict metodu bize n tane satırdan 1 tane sütundan oluşanm bir
matris verir. O halde örneğin çıktı olarak tek nöronun bulunduğu bir ağda biz kestirim değerlerini şöyle yazdırabiliriz:
for i in range(len(predict_result)):
print(predict_result[i, 0])
Tabii iki boyutlu diziyi Numpy'ın flatten metoduyla ya da ravel metoduyla tek boyutlu hale getirerek de yazırma işlemini
yapabilirdik:
for val in predict_result.flatten():
print(val)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte "diabetes.csv" dosyası üzerinde yukarıda belirtilen tüm adımlar uygulanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Sample')
model.add(Dense(32, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(16, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=200, validation_split=0.2)
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
predict_data = np.array([[2,148,58,37,128,25.4,0.699,24], [7,114,67,0,0,32.8,0.258,42],
[5,99,79,27,0,29,0.203,32], [0,110,85,30,0,39.5,0.855,39]])
predict_result = model.predict(predict_data)
for i in range(len(predict_result)):
print(predict_result[i, 0])
for i in range(len(predict_result)):
print("Şeker Hastası" if predict_result[i, 0] > 0.5 else "Şeker Hastası Değil")
#----------------------------------------------------------------------------------------------------------------------------
Şimdi yukarıdaki adımların bazı detayları üzerinde duralım. İlk olarak modeldeki katmanların sayısı ve katmanlardaki nöronların
sayısı üzerinde duralım. Daha önceden de belirtildiği gibi pek çok problem iki saklı katmanla Dense bir bağlantı ile tatminkar
biçimde çözülebilmektedir. Dolayısıyla bizim ilk aklımıza gelen model iki saklı katmanlı klasik bir modeldir. Ancak özel problemler
(şekil tanıma, yazıdan anlam çıkartma, görüntü hakkında çıkarım yapma gibi) iki saklı katmanla tatminkar biçimde çözülememktedir.
Bu durumda ikiden fazla saklı katman kullanılır. Bu modellere "derin öğrenme (deep learning)" denilmektedir.
Girdi katmanındaki nöron sayısı zaten problemdeki sütun sayısı (feature sayısı kadar) olmalıdır. Tabii kategorik sütunlar "one
hot encoding" işlemine sokulmalıdır. Çıktı katmanındaki nöron sayıları ise yine probleme bağlı olmaktadır. İkili sınıflandırma
tarzı problemlerde çıktı tek nörondan, çoklu sınıflandırma problemlerinde sınıf sayısı kadar nörondan oluşur. Lojistik olmayan
regresyon problemlerinde ise çıktı nöron sayıları genellikle bir tane olmaktadır.
Saklı katmanlardaki nöron sayıları için çok pratik şeyler söylemek zordur. Çünkü saklı katmanlardaki nöron sayıları bazı faktörlere
de bağlı olabilmektedir. Örneğin eğitimdeki kullanılacak veri miktarı, problemin karmaşıklığı, hyper parametrelerin durumları
saklı katmanlardaki nöron sayısı üerinde etkili olabilmektedir. Saklı katmanlardaki nöron sayıları için şunlar söylenebilir:
- Problem karmaşıklaştıkça saklı katmanlardaki nöron sayılarını artırmak uygun olabilmektedir.
- Saklı katmanlarda çok az nöron bulundurmak "underfitting" yani yetersiz öğrenmeye yol açabilmektedir.
- Saklı katmanlarda gereksiz biçimde fazla sayıda nöron bulundurmak hem eğitim süresini uzatabileceği gibi hem de "overfitting"
durumuna yol açabilir.
- Eğitim veri kümesi azsa katmanlardaki nöron sayıları düşürülebilir.
- Pek çok problemde katmanlardaki nöron sayıları çok fazla ya da çok az olmadıktan sonra önemli olmayabilmektedir.
- Saklı katmanlardaki nöron sayısı girdi katmanındaki nöron sayısından az olmamlıdır.
Çeşitli kaynaklar saklı katmanlardaki nöronların sayıları için ise üstünkörü şu pratik tavsiyelerde bulunmaktadır:
- Saklı katmandaki nöronların sayısı girdi katmanındaki nöronların sayısının 2/3 ile çıktı katmanındaki nöronların sayısının toplamı
kadar olabilir. Örneğin girdi katmanındaki nöron sayısı 5, çıktı katmanındaki 1 olsun. Bu durumda saklı katmandaki nöron sayısı
5 olabilir.
- Saklı katmandaki nöronların sayısı girdi katmanındaki nöron sayısının iki katından fazla olmamalıdır.
Biz genellikle genel problemlerde iki saklı katman ve katmalarda da "6, 32, 64, 100, 128 gibi sayılarda nöron kullanacağız.
Ancak ileride özel mimarilerde bu durumu farklılaştıracağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay sinir ağı her farklı eğitimde farklı "w" ve "bias" değerlerini oluşturabilir. Yani ağın peformansı eğitimden eğitime
değişebilir. Her eğitimde ağın farklı değerlere konumlanırılmasının nedenleri şunlardır:
1) train_test_split fonksiyonu her çalıştırıldığında aslında fonksiyon training_dataset ve test_dataset veri kümelerini
karıştırarak elde etmektedir.
2) Katmanlardaki "w" değerleri programın her çalıştırılmasında rastgele bir başlangıç değeriyle set edilmektedir.
3) fit işleminde her epoch sonrasında veri kümesi yeniden karıştırılmaktadır.
Bir rastgele sayı üretiminde aynı üretim aynı "tohum değerden (seed)" başlatılırsa hep aynı değerler elde edilir. Bu duruma
rassal sayı üretiminin "reproducible" olması denmektedir. Eğer tohum değer belirtilmezse bu tohum değer programın her
çalıştırılmasında rastgele biçimde bilgisayarın saatinden alınmaktadır.
O halde eğitimden hep aynı sonucun elde edilmesi için (yani eğitimin "reproducible" hale getirilmesi için) yukarıdaki
unsurların dikkate alınması gerekir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Sample')
model.add(Dense(32, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(32, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=200, validation_split=0.2)
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
predict_data = np.array([[2,148,58,37,128,25.4,0.699,24], [7,114,67,0,0,32.8,0.258,42],
[5,99,79,27,0,29,0.203,32], [0,110,85,30,0,39.5,0.855,39]])
predict_result = model.predict(predict_data)
for i in range(len(predict_result)):
print(predict_result[i, 0])
for i in range(len(predict_result)):
print("Şeker Hastası" if predict_result[i, 0] > 0.5 else "Şeker Hastası Değil")
#----------------------------------------------------------------------------------------------------------------------------
Katmanlardaki aktivasyon fonksiyonları ne olmalıdır? Girdi katmanı gerçek bir katman olmadığına göre oraada bir aktivasyon
fonksiyonu yoktur. Saklı katmanlardaki aktivasyon fonksiyonları için çeşitli seçenekler bulunmaktadır. Ancak son yıllarda
pek çok problemde en çok kullanılan ve başarısı görülmüş olan aktivasyon fonksiyonu "relu (rectified linear unit)" denilen
fonksiyondur. Relu fonksiyonu şöyledir:
x >= 0 ise y = x
x < 0 ise 0
Relu fonksiyonun grafiği aşağıdaki gibi çizilebilir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10, 10, 1000)
y = np.maximum(x, 0)
plt.title('RELU Function', fontsize=14, pad=20, fontweight='bold')
axis = plt.gca()
axis.spines['left'].set_position('center')
axis.spines['bottom'].set_position('center')
axis.spines['top'].set_color(None)
axis.spines['right'].set_color(None)
axis.set_ylim(-10, 10)
plt.plot(x, y)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aktivasyon fonksiyonları isimsel girilebileceği gibi tensorflow.keras.activations modülündeki fonksiyonlar biçiminde de
girilebilir. Örneğin:
from tensorflow.keras.activations import relu
...
model.add(Dense(16, activation=relu, name='Hidden'))
Bu modüldeki fonksiyonlar keras Tensorflow kullanılarak yazıldığı için çıktı olarak Tensor nesneleri vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
İkili sınıflandırma problemlerinde çıktı katmanında en fazla kullanılan aktivasyon fonksiyonu "sigmoid" isimli fonksiyondur.
Yukarıdaki "diabet" örneğinde biz çıktı katmanında sigmoid fonksiyonunu kullanmıştık. İkili sınıflandırma problemlerinde
ın çıktı katmanında tek bir nöron bulunur ve bu nörounun da aktivasyon fonksiyonu "sigmoid" fonksiyonudur.
Pekiyi sigmoid nasıl bir fonksiyondur? Bu fonksiyona "lojistik (logistic)" fonksiyonu da denilmektedir. Matematiksel ifadesi
şöyledir:
y = e ** x / (1 + e ** x)
Burada e 2.71828... biçiminde irrasyonel e sayısıdır. Yukarıdaki kesrin pay ve paydası e ** x ile çarpılırsa fonksiyon aşağıdaki
gibi de ifade edilebilir:
y = 1 / (1 + e ** -x)
Sigmoid eğrisi x = 0 için 0.5 değerini veren x pozitif yönde arttıkça 1 değerine yaklaşan, x negatif yönde arttıkça 0
değerine yaklaşan S şeklinde bir eğridir. Sigmoid fonksiyonu (0, 1) arasında bir değer vermektedir. Bu nedenle çıktı katmanındaki
nöronun aktivasyon fonksiyonu sigmoid ise ağdan sonuç olarak 0 ile 1 arasında noktalı bir sayı elde edilir. İşte biz de bu sayı
0.5 ten büyükse onu 1 olarak, 0.5'ten küçükse 0 olarak değerlendiririz. Tabii aslında çıktı bir olasılık da belirtmektedir.
Yani ağın çıktısı 1'e ne kadar yaklaşmışsa o kestirimin 1 olma olasılığı o kadar yüksek, ağın çıktısı 0'a ne kadar yaklaşmışsa
o kestirimin 0 olma olasılığı o kadar yüksek olur.
Sigmoid eğrisi aşağıdaki gibi çizilebilir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10, 10, 1000)
y = np.e ** x / (1 + np.e ** x)
plt.title('Sigmoid (Logistic) Function', fontsize=14, pad=20, fontweight='bold')
axis = plt.gca()
axis.spines['left'].set_position('center')
axis.spines['bottom'].set_position('center')
axis.spines['top'].set_color(None)
axis.spines['right'].set_color(None)
axis.set_ylim(-1, 1)
plt.plot(x, y)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda da sigmoid fonksiyonun birinci türevi alınmıştır. Sigmoid fonksiyonunun birinci türevi Gauss eğirisine benzemektedir.
#----------------------------------------------------------------------------------------------------------------------------
import sympy
x = sympy.Symbol('x')
fx = sympy.E ** x / (1 + sympy.E ** x)
dx = sympy.diff(fx, x)
from sympy import init_printing
init_printing()
print(dx)
import numpy as np
np.linspace(-10, 10, 1000)
pdx = sympy.lambdify(x, dx)
x = np.linspace(-10, 10, 1000)
y = pdx(x)
import matplotlib.pyplot as plt
plt.title('First Derivative of Sigmoid Function', fontsize=14, pad=20, fontweight='bold')
axis = plt.gca()
axis.spines['left'].set_position('center')
axis.spines['bottom'].set_position('center')
axis.spines['top'].set_color(None)
axis.spines['right'].set_color(None)
axis.set_ylim(-0.4, 0.4)
plt.plot(x, y)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Diğer çok kullanılan bir aktivasyon fonksiyonu da "hiperbolic tanjant" fonksiyonudur. Bu fonksiyon "tanh" ismiyle Keras'ta
kullanılabilmektedir. Fonksiyonun matematiksel ifadesi şöyledir:
f(x) = (e ** (2 * x) - 1) / (e ** (2 * x) + 1)
Tanh fonksiyonu adeta sigmoid fonksiyonunun (-1, 1) arası değer alan biçimi gibidir. Fonksiyon yine S şekli biçimindedir.
Tanh fonksiyonu saklı katmanlarda da bazen çıktı katmanlarında da kullanılabilmektedir. Eskiden bu fonksiyon çok yoğun
kullanılıyordu. Ancak artık saklı katmanlarda en çok Relu fonksiyonu tercih edilmektedir.
Fonksiyonun grafiğini aşağıdaki gibi çizdirebiliriz.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10, 10, 1000)
y = (np.e ** (2 * x) - 1) / (np.e ** (2 * x) + 1)
plt.title('Hiperbolik Tanjant (tanh) Fonksiyonunun Grafiği', fontsize=14, pad=20, fontweight='bold')
axis = plt.gca()
axis.spines['left'].set_position('center')
axis.spines['bottom'].set_position('center')
axis.spines['top'].set_color(None)
axis.spines['right'].set_color(None)
axis.set_ylim(-1, 1)
plt.plot(x, y)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Diğer çok karşılaşılan bir aktivasyon fonksiyonu da "softmax" isimli fonksiyondur. Softmax fonksiyonu çok sınıflı sınıflandırma
problemlerinde çıktı katmanlarında kullanılmaktadır. Bu aktivasyon fonksiyonu Keras'ta "softmax" ismiyle kullanılmaktadır.
Örneğin bir resmin "elma", "armut", "kayısı", "şeftali", "karpuz" resimlerinden hangisi olduğunu anlamak için kullanılan
sınıflandırma modeli çok sınıflı bir sınıflandırma modelidir. Buna istatistikte "çok sınıflı lojistik regresyon (multinomial
logictic regression)" da denilmektedir. Bu tür problemlerd sinir ağında sınıf sayısı kadar nöron bulundurulur. Örneğin yukarıdaki
resim sınıflandırma probleminde ağın çıktısında 5 nöron bulunmalıdır.
ın çıktı katmanındaki tüm nöronların aktivasyon fonksiyonları softmax yapılırsa tüm çıktı katmanındaki nöronların çıktı değerlerinin
toplamı her zaman 1 olur. Böylece adeta çıktı nöronlarının çıktı değerleri ilgili sınıfın olsaılığını belirtmektedir. Biz de
toplamı 1 olan çıktıların en yüksek değerine bakarız ve sınıflandırmanın o sınıfı kestirdiğini düşünürüz. Örneğin yukarıdaki resim
sınıflandırmasında çıktı katmanında 5 nöron olacaktır. Bu öronların çıktılarının toplamı da 1 olacaktır. Aşağıdaki gibi bir kestirim
sonucu elde edilmiş olsum:
Elma Nöronunun Çıktısı ---> 0.2
Armut Nöronunun Çıktısı ---> 0.2
Kayısı Nöronunun Çıktısı ---> 0.3
Şeftali Nöronunun Çıktısı ---> 0.2
karpuz Nöronunun Çıktısı ---> 0.2
Burada en büyük çıktı 0.3 olan Kayısı nöronuna ilişkindir. O halde biz bu kestirimin "kayısı" olduğuna karar veririz.
Softmax fonksiyonu şöyle hesaplanmaktadır. Elimizde aşağıdaki gibi x değerleri olsun:
>>> import numpy as np
>>> x = np.array([3, 6, 4, 1, 7])
>>> x
array([3, 6, 4, 1, 7])
Bu değerlerin soft max değerleri np.e ** x / np.sum(e ** x) biçimindedir.
>> sm = np.e ** x / np.sum(np.e ** x)
>>> sm
array([0.0127328 , 0.25574518, 0.03461135, 0.0017232 , 0.69518747])
>>> np.sum(sm)
1.0
Keras aslında çıktı katmanlarındaki tüm softmax aktivasyon fonksiyonlarının bir grup oluşturduğunu varsayar. Sonra girdilerle
ırlık değelerini çarpıp bias değeriyle toplayarak yukarıdaki gibi bir x vektörü elde eder. Sonra da yukarıdaki işlemi
uygular. Başka bir deyişle çıktı katmanındaki softmax aktivasyon fonksiyonuna sahip olan nöronlar bir grup olarak
değerlendirilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Diğer çok kullanılan aktivasyon fonksiyonlarından biri de "linear" aktivasyon fonksiyonudur. Aslında bu fonksiyon y = x fonksiyonudur.
Yani girdi ile aynı değeri üretmektedir. Başka bir deyişle bir şey yapmayan bir fonksiyondur. Pekiyi böyle bir aktivasyon fonksiyonunun
ne anlamı olabilir? Bu aktivasyon fonksiyonu "lojistik olmayan regresyon problemlerinde" çıktı katmanında kullanılmaktadır.
Lojistik olmayan regresyon problemleri çıktı olarak bir sınıf bilgisi değil bir sayı bulmaya çalışan problemlerdir. Örneğin bir evin fiyatı,
bir otomobilin mil başına yaktığı yakıt miktarı gibi problemler lojistik olmayan regresyon problemleridir. Bu aktivasyon fonksiyonu
Keras'ta "linear" ismiyle kullanılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Anımsanacağı gibi "w" ve "bias" değerleri "optimizer" denilen algoritma tarafından her batch işleminde yapılmaktaydı. İşte optimizer
algoritması aslında "loss" denilen bir fonksiyonu minimize etmeye çalışmaktadır. Başka bir deyişle "loss" fonksiyonu aslında
"w" ve "bias" değerlerinin güncellenmesi için bir amaç fonksiyonu görevini görmektedir. Değişik problemler için değişik loss fonksiyonları bulunmaktadır.
Programcı model sınıfının compile metodunda loss parametresiyle loss fonksiyonu isimsel biçimde girebilir. Ya da isterse tensorflow.keras.losses
modülündeki sınıflar ve fonksiyonlar yoluyla da loss fonksiyonunu girebilmektedir.
loss fonksiyonları eğitim batch batch yağıldığı için tek bir satırın çıktısından değil n tane satırın çıktısından hesaplanmaktadır.
Yani bir batch işleminin gerçek sonucu ile ağdan o batch için elde edilecek kestirimö sonuçlarına dayanılarak loss değerleeri
hesaplanmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
- Lojistik olmayan regresyon problemleri için en yaygın kullanılan loss fonksiyonu "Mean Squared Error (MSE)" isimli fonksiyondur.
Bu fonksiyona Türkçe "Ortalama Karesel Hata" diyebiliriz. Bu fonksiyon gerçek değerlerden kestirilen değerlerin farkının karelerinin
toplamının ortalaması ile hesaplanır. Sembolik gösterimi şöyledir:
mse = np.mean((yi - yi_hat) ** 2)
Burada yi gerçek değerleri, yi_hat ise kestirilen değerleri belirtmektedir.
- Yine lojistik olmayan regresyon problemleri için kullanılabilen diğer bir loss fonksiyonu da "Mean Absolute Error (MAE)" isimlifonksiyondur.
Bu fonksiyona da Türkçe "Ortalama Mutlak Hata" diyebiliriz. Ortalama mutlak hata gerçek değerlerden kestirilen değerlerin farklarının mutlak değerlerinin
ortalaması biçiminde hesaplanmaktadır. Sembolik gösterimi şöyledir:
mae = np.mean(np.abs(yi - yi_hat))
Loss fonksiyonu olarak çoğu kez MSE tercih edilmektedir. Kare işlemi algoritmalar için daha uygun bir işlemdir. Aynı zamanda değerleri
daha fazla farklılaştırmaktadır.
- Lojistik olmayan regresyon problemleri için diğer bir loss fonksiyonu da "Mean Absolute Percentage Error (MAPE)" isimli fonksiyondur.
Fonkisyonun sembolikl ifadesi şöyledir:
mape = 100 * np.mean((yi - yi_hat) / yi_hat)
- Lojistik olmayan regresyon problemleri için yine "Mean Squared Logaritmic Error (MSLE)" isimli diğer bir loss fonksiyonu da vardır.
Bu fonksiyon gerçek değerlerle kestirilen değerlerin logaritmalarının farklarının karelerinin ortalaması biçiminde hesaplanır.
Sembolik ifadesi şöyledir:
msle = np.mean((np.log(yi) - np.log(yi_hat) ** 2)
- İkili sınıflandırma problemleri için en yaygın kullanılan loss fonksiyonu "Binary Cross-Entropy (BCE)" denilen fonlsiyondur. Bu fonksiyonun sembolik
gösterinmi şöyledir:
bce = -np.mean(yi * np.log(yi_hat) + (1 - yi) * np.log(1 - yi_hat))
- Cok sınıflı sınıflandırma problemleri için en yaygın kullanılan loss fonksiyonu ise "Categorical Cross-Entropy (CCE)" isimli fonksiyondur.
CCE fonksiyonu aslında BCE fonksiyonun çoklu biçimidir. Tabii CCE değerini hesaplayabilmek için ağın kategori sayısı kadar çıktıya
sahip olması ve bu çıktıların toplamının 1 olması gerekmektedir. Başka bir deyişle ağın çıktı katmanındaki nöronların aktivasyon fonksiyonları
"softmax" olmalıdır. Ayrıca CCE çok sınıflı bir entropy hesabı yaptığına göre gerçek değerlerin one hot encoding biçiminde kodlanmış olması gerekir.
M tane sınıf olan bir satırın CCE değeri şöyle hesaplanır (m tane one hot encoding edilmiş gerçek değer ve m tane softmax çıktı değeri
söz konusu olmalıdır):
cce = -np.sum(yk * log(yk_hat))
Tabii bu hesap tek bir satır için elde edilen değeri verir. Batch'i oluşturan n tane satır için bu işlem n kere yapılıp ortalaması alınmalıdır.
Anımsanacağı gibi loss fonksiyonunun ne olacağı compile metodunda loss parametresiyle belirtiliyordu. Biz loss fonksiyonlarını yazsısal
olarak belirtebiliriz. Bazı loss fonksiyonlarının kısa ve uzun yazımları da vardır:
'mean_squared_error' ya da 'mse'
'mean_absolute_error' ya da 'mae'
'mean_absolute_percentage_error' ya da 'mape'
'mean_squared_logarithmic_error' ya da 'msle'
'binary_crossentropy'
'categorical_crossentropy'
Aslında loss fonksiyonları tensorflow.keras.losses modülündeki fonksiyonlar ve sınıflar ile de girilebilmektedir. Örneğin:
from tensorflow.keras.losses import binary_crossentropy
model.compile(optimizer='rmsprop', loss=binary_crossentropy, metrics=['binary_accuracy])
Örneğin:
from tensorflow.keras.losses import BinaryCrossentropy
model.compile(optimizer='rmsprop', loss=BinaryCrossentropy(), metrics=['binary_accuracy])
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Anımsanacağı gibi "metrik fonksiyonlar" her epoch'tan sonra sınama verlerine uygulanan ve eğitimin gidişatı hakkında bilgi
almak için kullanılan performans fonksiyonlar idi. Problemin türüne göre çeşitli metirk fonksiyonlar hazır bulunmaktadır.
Birden fazla metirk fonksiyon kullanılabileceği için metrik fonksiyonlar compile metodunda "metrics" parametresine bir liste
biçiminde girilmektedir. Metrik fonksiyonlar yazısal biçimde girilebilceği gibi tensorflow.keras.metrics modülündeki fonksiyuonlar ve sınıflar
biçiminde de girilebilmektedir.
- "binary_accuracy" ikili sınıflandırma problemleri için en yaygın kullanılan metirk fonksiyondur. Bu fonksiyon kabaca
kaç gözlemin değerinin kestirilen değerle aynı olduğunun yüzdesini vermektedir. Örneğin "diabet" örneğinde "binary_accuracy"
değerinin 0.70 olması demek her yüz ölçümün 70 tanesinin doğru biçimde kestirilmesi demektir.
- Çok sınıflı sınıflandırma problemlerinde tipik okullanılan metrik fonksiyon "categorical_accuracy" isimli fonksiyondur.
Bu fonksiyon da yine gözlemlerin yüzde kaçının tam olarak isabet ettirildiğini belirtmektedir. Örneğin i,kili sınıflandırmada 0.50 değeri
iyi bir değer değildir. Çünkü zaten rastgele seçim yapılsa bile ortalama 0.50 başarı elde edilmekltedir. Ancak 100 sınıflı bir problemde
0.50 başarı düşük bir başarı olmayabilir.
- Aslında loss fonksiyonları da bir çeşit metrik fonksiyonlar olarak kullanılabilir. Ancak Keras zaten bize loss fonksiyonlarının değerlerini
her epoch'ta eğitim ve sınama verileri için vermektedir. Dolayısıyla örneğin ikili sınıflandırma problemi için eğer biz loss fonksiyonu
olarak "binary_crossentropy" girmişsek ayrıca bu fonksiyonu metrik olarak girmenin bir anlamı yoktur. Aslında her loss fonksiyonu bir metrik
fonksiyon gibi de kullanılabilir. Ancak her metrik fonksiyon bir loss fonksiyonu olarak kullanılmaz.
- Lojistik olmayan regresyon problemleri için pek çok loss fonksiyonu aslında metrik olarak da kullanılabilmektedir. Örneğin biz böyle
bir problemde loss fonksiyonu olarak "mean_squared_error" seçmişsek metrik sonksiyon olarak "mean_absolute_error" seçebiliriz.
mean_absolute_error fonksiyonu loss fonksiyonu olarak o kadar iyi olmasa da metrik anlamda kişilerin kolay anlayabileceği bir fonksiyondur.
Benzer biçimde lojistik olmayan regresyon problemlerinde "mean_asbolute_percentage_error", "mean_squared_logarithmic_error"
fonksiyonları da metrik olarak kullanılabilmektedir.
Metrik fonksiyonlar yazısal biçimde girilecekse onlar için uzun ya da kısa isimler kullanılabilmektedir. Örneğin:
'binary_accuracy'
'categorical_accuracy'
'mean_absolute_error' ya da 'mae'
'mean_absolute_percentage_error' ya da 'mape'
'mean_squared_error' ya da 'mse'
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bir sinir ağı eğitildikten sonra onun diskte saklanması gerekebilir. Çünkü eğitim uzun sürebilir ve her bilgisayarı açtığımızda
ı yeniden eğitmemiz verimsiz bir çalışma biçimidir. Ayrıca eğitimn tek seferde de yapılmayabilir. Yani örneğin bir kısım verilerle
eğitim yapılıp sonuçlar saklanabilir. Sonra yeni veriler elde edildikçe eğitime devam edilebilir.
Keras'ta yepılan eğitimlerin saklanması demekle neyi kastediyoruz? Tabii öncelikle tüm nöronlardaki "w" ve "bias" değerleri
kastedilmektedir. Ayrıca Keras bize tüm modeli saklama imkanı da vermektedir. Bu durumda modelin yeniden kurulmasına da gerek kalmaz.
Tüm model katmanlarıyla "w" ve "bias" değerleriyle saklanıp geri yüklenebilmektedir.
Modeli saklamak için hangi dosya formatı uygundur? Çok fazla veri söz konusu olduğu için buna uygun tasarımı olan dosya formatları tercih edilmiştir.
Örneğin bu işlem için "CSV" dosyaları hiç uygun değildir. İşte bu tür uygulamalar için ilk akla gelen format "HDF (Hieararchical Data Format)" denilen
formattır. Bu formatın beşinci versiyonu HDF5 ya da H5 formatı olarak bilinmektedir.
Modeli bir bütün olarak saklamak için Sequential sınıfının save isimli metodu kullanılır. save metodunun birinci parametresi
dosyanın yol ifadesini almaktadır. save_format parametresi saklanacak dosyanın formatını belirtir. Bu parametre girilmezse dosya TensorFlow
kütüphanesinin kullandığı "tf" formatı ile saklanmaktadır. Biz HDF5 formatı için bu parametreye 'h5' girmeliyiz. Örneğin:
model.save('diabetes.h5', save_format='h5')
Aşağıdaki örnekte "diabetes" veri kümesi eğitildikten sonra save metodu ile saklanıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Diabetes')
model.add(Dense(32, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(32, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=200, validation_split=0.2)
model.save('diabetes.h5', save_format='h5')
#----------------------------------------------------------------------------------------------------------------------------
HDF5 formatıyla sakladığımız model bir bütün olarak tensorflow.keras.models modülündeki load_model fonksiyonu ile geri yüklenebilir.
load_model fonksiyonu bizden yüklenecek dosyanın yol ifadesini almaktadır. Geri dönüş değeri olarak model nesnesini verir. Örneğin:
from tensorflow.keras.models import load_model
model = load_model('diabetes.h5')
Aşağıdaki örnekte yukarı save ettiğimiz model yüklenerek kestirim işlemi yapılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.models import load_model
model = load_model('diabetes.h5')
import numpy as np
predict_data = np.array([[2,148,58,37,128,25.4,0.699,24], [7,114,67,0,0,32.8,0.258,42],
[5,99,79,27,0,29,0.203,32], [0,110,85,30,0,39.5,0.855,39]])
predict_result = model.predict(predict_data)
for i in range(len(predict_result)):
print(predict_result[i, 0])
for i in range(len(predict_result)):
print("Şeker Hastası" if predict_result[i, 0] > 0.5 else "Şeker Hastası Değil")
#----------------------------------------------------------------------------------------------------------------------------
Aslında modelin tamamını değil yalnızca "w" ve "bias" değerlerini save etmek de mümkündür. Bunun için save_weights metodu
kullanılmaktadır. Örneğin:
model.save_weights('diabetes-weights', save_format='h5')
Aşağıdaki örnekte "diabetes" veri kğmesinde yalnızca modelin "w" ve "bias" değerleri saklanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Diabetes')
model.add(Dense(32, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(32, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=200, validation_split=0.2)
model.save_weights('diabetes-weights.h5', save_format='h5')
#----------------------------------------------------------------------------------------------------------------------------
save_weights metodu yalnızca "w" ve "bias" değerlerini save etmektedir. Bizim bu geri yükleyebilmemiz için modeli yeniden
aynı biçimde oluşturmamız gerekir. "w" ve "bias" değerlerinin geri yüklenmesi için Sequential sınıfının load_weights metodu kullanılmaktadır.
Aşağıdaki örnekte yukarıdaki örnekteki modelin "w" ve "bias" değerleri geri yüklenmiştir. Tabii save_weights model bilgilerini
saklamadığı için modelin aynı biçimde yeniden oluşturulması gerekmektedir.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Diabetes')
model.add(Dense(32, activation='relu', input_dim=8, name='Hidden-1'))
model.add(Dense(32, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.load_weights('diabetes-weights.h5')
import numpy as np
predict_data = np.array([[2,148,58,37,128,25.4,0.699,24], [7,114,67,0,0,32.8,0.258,42],
[5,99,79,27,0,29,0.203,32], [0,110,85,30,0,39.5,0.855,39]])
predict_result = model.predict(predict_data)
for i in range(len(predict_result)):
print(predict_result[i, 0])
for i in range(len(predict_result)):
print("Şeker Hastası" if predict_result[i, 0] > 0.5 else "Şeker Hastası Değil")
#----------------------------------------------------------------------------------------------------------------------------
Biz yukarıda modeli ve ağırlıkları dosyaya save edip geri yükledik. Pekiyi eğitilmiş modeldeki ağırlık değerlerini doğrudan
program içerisinde kullanmak istesek ne yapmalıyız? İşte katman nesneleri (örneğin Dense nesnesi) get_weights ve set_weights
isimli metotlar bulundurmaktadır. Biz bir katman nesnesindeki "w" ve "bias" değerlerini istersek NumPy dizisi olarak elde edip
geri yükleyebiliriz. Tabii bu işlemi yapabilmek için katman nesnelerine erişmemiz gerekir. Bunun bir yolu katman nesnelerini
yaratırken bir değişkende saklamak olabilir. Örneğin:
model = Sequential(name='Sample')
hidden1 = Dense(32, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1')
model.add(hidden1)
hidden2 = Dense(32, activation='relu', name='Hidden-2')
model.add(hidden2)
output = Dense(1, activation='sigmoid', name='Output')
model.add(output)
Biz artık burada katman nesnelerini değişkenlerde saklamış olduk. Ancak aslında bu işleme de gerek yoktur. Zaten Sequential sınıfı
aklenen tüm katman nesnelerini bize layers isimli örnek özniteliği yoluyla bir liste biçiminde vermektedir. Yani örneğin
model.layers[0] zaten ilk saklı katmanı, model.layers[1] ikinci saklı katmanı bize verir.
Dense sınıfının get_weights metodu iki elemanlı bir listeye geri dönmektedir. Bu listenin her iki elemanı da NumPy dizisidir.
Listenin ilk elemanı o katmandaki nöronların "w" değerlerini ikinci elemanı ise "bias" değerlerini belirtmektedir. Katmandaki nöronların
"w" değerleri iki boyutlu bir NumPy dizisi biçimindedir. Burada satırlar önceki katmanı sütunlar da ilgili katmanı belirtirler.
Bu durumda o katmandaki i'inci nöronun önceki katmandaki k'ıncı nöron için "w" değeri [k, i] ifadesi ile elde edilir.
Yani "w" değerlerine ilişkin NumPy dizisinin her sütunu o katmandaki belli bir nöronun "w" değerlerini belirtmektedir. Tabii
nöronların "bias" değerleri tek boyutlu bir NumPy dizisi ile temsil edilmektedir.
Aşağıda örnekte "diabetes" veri kümesi için ilk saklı katmandaki "w" değerlerine ilişkin bilgiler görüntülenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Diabetes')
model.add(Dense(32, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(32, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=200, validation_split=0.2)
hidden1 = model.layers[0]
hidden1_weights = hidden1.get_weights()
print(type(hidden1_weights)) # <class 'list'>
print(len(hidden1_weights)) # 2
print(type(hidden1_weights[0]), type(hidden1_weights[1])) # <class 'numpy.ndarray'> <class 'numpy.ndarray'>
print(hidden1_weights[0].shape) # (8, 32)
print(hidden1_weights[1].shape) # (32,)
w = hidden1_weights[0][5, 17]
print(w) # -0.31158817
#----------------------------------------------------------------------------------------------------------------------------
Bir katmandaki "w" ve "bias" değerlerini Dense sınıfının set_weights metodu ile geri yükleyebiliriz. Aşağıdaki örnekte
katmandaki "w" değerlerine 0.1 toplanarak değerler geri yüknemiştir. (Bu işlemde bir anlam aramayınız).
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Diabetes')
model.add(Dense(32, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(32, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=200, validation_split=0.2)
hidden1 = model.layers[0]
hidden1_weights = hidden1.get_weights()
hidden1_weights[0] = hidden1_weights[0] + 0.1
hidden1.set_weights(hidden1_weights)
#----------------------------------------------------------------------------------------------------------------------------
Yapay sinir ağları ile kestirim yapabilmek için bazı aşamalardan geçmek gerekir.
1) Hedefin Belirlenmesi: Öncelikle uygulamacının ne yapmak istediğine karar vermesi gerekir. Yani uygulamacının çözmek
istediği problem nedir? Bir sınıflandırma problemi midir? Lojistik olmayan regresyon problemi midir? Şekil tanıma problemi midir?
2) Kestirimle İlgili Olacak Özellerin (Sütunların) Belirlenmesi: Tespit edilen problemin kestirimi için hangi bilgilere gereksinim
duyulmaktadır? Kestirim ile ilgili olabilecek özellikler nelerdir? Örneğin bir eczanenin cirosunu tahmin etmek isteyelim.
Burada ilk gelecek özellikler şunla rolabilir:
- Eczanenin konumu
- Ecnanenin önünden geçen günlük insan sayısı
- Eczannein destek ürünleri satıp satmadığı
- Eczanenin kozmetik ürünler satıp satmadığı
- Eczanenin anlaşmalı olduğu kurumlar
- Eczanein büyüklüğü
- Eczanenin yakınındaki, hastenelerin ve sağlık ocaklarının sayısı
- Eczacının tanıdığı doktor sayısı
3) Eğitim İçin Verilerin Toplanması: Eğitim için verilrin toplanması en zor süreçlerden biridir. Veri toplama için şu yöntemler
söz konusu olabilir:
- Anketler (Surveys)
- Daha önce elde edilmiş veriler
- Çeşitli kurumlar tarafından zaten elde edilmiş veriler
- Sensörler yoluyla elde edilen veriler
- Sosyal ğalardan elde edilen veriler
- Birtakım doğal süreç içerisinde oluşan veriler (Örneğin her müşteri için bir fiş kesildiğine göre bunlar kullanılabilir)
4) Verilerin Kullanıma Hazır Hale Getirilmesi: Veriler topamdıktan sonra bunların üzerinde bazı önişlemlerin yapılması gerekmektedir.
Örneğin gereksiz sütunlar atılmalıdır. Eksik veriler varsa bunlar bir biçimde ele alınmalıdır. Kategorik veriler sayısallaştırılmalıdır. Text ve görüntü verileri
kullanıma hazır hale getirilmelidir. Özellik "ölçeklemeleri (feature scaling)" ve "özellik mühendisliği (feature engineering)"
işlemleri yapılmalıdır.
5) Yapay Sinir Ağı Modelinin Oluşturulması: Probleme uygun bir yapay sinir ağı modeli oluşturulmalıdır.
6) Modelin Eğitilmesi ve Test Edilmesi: Oluşturulan model eldeki veri kümesiyle eğitilmeli ve test edilmelidir.
7) Kestirim İşleminin Yapılması: Nihayet eğtilmiş model artık kestirim amacıyla kullanılmalıdır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Callback sözcüğü belli bir olay devam ederken programcının verdiği bir fonksiyonun (genel olarak callable bir nesnenin) çağrılması
anlamında kullanılmaktadır. Keras'ın da bir callback mekanizaması vardır. Bu sayede biz çeşitli olaylar devam ederken bu olayları
sağlayan metotların bizim callable nesnelerimizi çağırmasını sağlayabiliriz. Böylece birtakım işlemler devam ederken arka planda
o işlemleri izleyebilir duruma göre gerekli işlemleri yapabiliriz.
Sequential sınıfının fit, evalaute ve predict gibi metotları "callbacks" isimli bir parametre almaktadır. İşte biz bu parametreye
kendi callback fonksiyonlarımızı ve sınıf nesnelerimizi verebiliriz. Bu metotlar da ilgili olaylar sırasında bizim verdiğimiz callable
nesneleri çağırır.
Aslında Keras'ta hazır bazı callback sınıflar zaten vardır. Dolayısıyla her ne kadar programcı kendi callback sınıflarını yazabilirse de
aslında buna fazlaca gereksinim duyulmamaktadır. Keras'ın sağladığı hazır callback sınıfları genellikle gereksinimi karşılamaktadır. Keras'ın hazır
callback sınıfları tensorflow.keras.callbacks modülü içerisinde bulunmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
En sık kullanılan callback sınıfı History isimli sınıftır. Aslında programcı bu callbak sınıfını genellikle kendisi kullanmaz.
Sequential sınıfının fit metodu zaten bu sınıf türünden bir nesneye geri dönmektedir. Örneğin:
hist = model.fit(....)
fit metodunun bize verdiği History nesnesi eğitim sırasında her epoch'tan sonra elde edilen değerleri barındırmaktadır. Anımsanacağı gibi
fit metodu zaten eğitim sırasında her epoch'tan sonra birtakım değerleri ekrana yazıyordu. İşte fit metodunun geri döndürdüğü bu history
nesnesi aslında bu metodun ekrana yazdığı bilgileri barındırmaktadır. History nesnesinin epoch elemanı uygulanan epoch numaralarını bize verir.
Ancak nesnenin en önemli elemanı history isimli elemandır. history isimli örnek özniteliği bir sözlük türündendir. Sözlüğün anahtarları
yazısal olarak loss ve metrik değer isimlerini bazrındırır. Bunlara karşı gelen değerler ise her epoch'taki ilgili değerleri belirten list
türünden nesnelerdir. History nesnesinin history sözlüğü her zaman "loss" ve "val_loss" anahtarlarını barındırır. Bunun dışında bizim belirlediğimiz
metriklere ilişkin eğitim ve sınama sonuçlarını da barındırmaktadır. Örneğin biz metrik olarak "binary_accuracy" girmiş olalım. history sözlüğü
bu durumda "binary_accuracy" ve "val_binary_accuracy" isimli iki anahtara da sahip olacaktır. Burada "val_xxx" biçiminde "val" ile başlayan
anahtarlar sınama verisinden elde edilen değerleri "val" ile başlamayan anahtarlar ise eğitim veri kğmesinden elde edilen verileri
belirtir. "loss" değeri ve diğer metrik değerler epoch'un tamamı için elde edilen değerlerdir. Her epoch sonucunda bu değerler sıfırlanmaktadır.
Epoch'lar sonrasında History nesnesi yoluyla elde edilen bilgilerin grafiği çizilebilir. Uygulamacılar eğitimin gidişatı hakkında
fikir edinebilmek için genellikle epoch grafiğini çizerler. Epoch sayıları arttıkça başarının artacağını söyleyemeyiz. Hatta tam tersine
belli bir epoch'tan sonra "overfitting" denilen olgu kendini göstermekte model gitgide yanlış şeyleri öğrenir duruma gelmektedir.
İşte uygulamacı gelellikle loss, val_loss, binary_accuracy, val_binary_accuracy gibi grafikleri epoch sayılarına göre çizerek
bunların uyumlu gidip gitmediğine bakabilir. Eğitimdeki verilerle sınama verilerinin birbirbirlerinden kopması genel olarak
kötü bir gidişata işaret etmektedir. Uygulamacı bu grafiklere bakarak uygulaması gereken epoch sayısına karar verebilir.
Aşağıdaki örnekte daha önce yapmış olduğumuz "diabetes" veri kümesi için epoch grafikleri çizilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Diabetes')
model.add(Dense(64, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
CSVLogger isimli callback sınıfı epoch işlemlerinden elde edilen değerleri bir CSV dosyasının içerisine yazmaktadır.
CSVLogger nesnesi yaratılırken __init__ metodunda CSV ismi dosyasının ismi verilir. Eğitim bittiğinde bu dosaynın içi
doldurulmuş olacaktır. Örneğin:
from tensorflow.keras.callbacks import CSVLogger
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2, callbacks=[CSVLogger('diabtes-epoch.csv')])
Burada fit metodunun callbacks parametresinin bir liste aldığına dikkat ediniz. Çünkü biz birden fazla callback nesnesini metoda verebiliriz.
Aşağıdaki örnekte "diabetes" veri kümesi için bu biçimde bir CSV dosyası yaratılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Diabetes')
model.add(Dense(64, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import CSVLogger
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2, callbacks=[CSVLogger('diabtes-epoch.csv')])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aslında programcı bir History callback nesnesi yaratıp da bunu fit metodunun callbacks parametresine verebilir. Bu durumda
fit bu nesnenin içini dolduracaktır. Örneğin:
from tensorflow.keras.callbacks import History
hcb = History()
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2, callbacks=[hcb])
Tabii böyle bir işleme gerek yoktur. Çünkü zaten fit metodu bize tamamen yukarıdaki callback nesnesini kendisi vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
LambdaCallback isimli callback sınıfı bizden __init__ metodu yoluyla çeşitli fonksiyonlar alır ve bu fonksiyonları belli noktalarda
çağırır. __init__ metodundaki parametreler ve anlamları şöyledir:
on_train_begin: Eğitim başladığında çağrılacak fonksiyonu belirtir.
on_train_end: Eğitim bittiğinde çağrılacak fonksiyonu belirtir.
on_epoch_begin: Her epoch başladığında çağrılacak fonksiyonu belirtir.
on_epoch_end: Her epoch bittiğinde çağrılacak fonksiyonu belirtir.
on_batch_begin: Her batch işleminin başında çağrılacak fonksiyonu belirtir.
on_batch_end: Her batch işlemi bittiğinde çağrılacak fonksiyonu belirtir.
Bu fonksiyonların parametreleri şöyle olmalıdır:
Fonksiyon Parametreler
on_epoch_begin epoch ve logs
on_epoch_end epoch ve logs
on_batch_begin batch ve logs
on_batch_end batch ve logs
on_train_begin logs
on_train_end logs
Burada epoch parametresi epoch numarasını, batch parametresi batch numarasını belirtir. los parametreleri ise birer sözlük belirtmektedir.
Bu sözlüğün içerisinde loss değeri gibi metrik değerler gibi önemli bilgiler vardır. epoch'lar için logs parametresi History nesnesindeki
anahtarları içermektedir. Ancak batch'ler için logs parametresi "val" önekli değerleri içermeyecektir.
Aşağıdaki örnekte LambdaCallback callback sınıfı yoluyla her epoch sonucunda "loss" değeri ile "val_loss" değeri arasındaki fark
yazdırılmıştır. Bu örnekte fit metodu zaten ekrana bir şeyler yazdırdığı için fit metodunun verbose parametresi 0 yapılarak
onun ekranabir şey yazması engellenmiştir. Böylece ekrana yalnızca callback fonksiyon bir şey yazmış olacaktır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Diabetes')
model.add(Dense(64, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import LambdaCallback
def on_epoch_end(epoch, logs):
diff = logs['loss'] - logs['val_loss']
print(f' Epoch: {epoch}, Diff: {diff}')
lcb = LambdaCallback(on_epoch_end=on_epoch_end)
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2, callbacks=[lcb], verbose=0)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte LambdaCallback nesnesinde on_epoch_begin, on_epoch_end ve on_batch_end parametrelerine fonksiyonlar girilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Diabetes')
model.add(Dense(64, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import LambdaCallback
def on_epoch_begin(epoch, logs):
print(f'Epoch {epoch} begins...', end='\n\n')
def on_epoch_end(epoch, logs):
print(f'\nEpoch {epoch} ends...', end='\n\n')
def on_batch_end(batch, logs):
print(f' Batch: {batch}: Loss: {logs["loss"]}')
lcb = LambdaCallback(on_epoch_begin=on_epoch_begin, on_epoch_end=on_epoch_end, on_batch_end=on_batch_end)
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2, callbacks=[lcb], verbose=0)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aslında programcının kendisi de kendi callback sınıflarını yazabilmektedir. Bunun için programcının kendi callback sınıflarını
tensorflow.keras.callbacks modülündeki Callback sınıfından türetmesi gerekir. Örneğin:
from tensorflow.keras.callbacks import Callback
class MyCallback(Callback):
pass
İşte eğitim sırasında (ya da diğer işlemler sırasında) bizim yazdığımız sınıfın bazı metotları çağrılmaktadır. Biz bu metotları
yazmazsak taban Callback sınıfındaki metotlar çağrılır. Dolayısıyla sorun çıkmaz. Biz bu metotlardan istediğimizi yazabiliriz.
Bu metotların listesi şöyledir:
on_epoch_begin
on_epoch_end
on_batch_begin
on_batch_end
on_train_begin
on_train_end
Tabii bir sınıf söz konusu olduğuna göre bu metotların birinci parametreleri self olacaktır. Diğer parametreleri yukarıdaki
LambdaCallback sınıfında açıkladığımız parametrelerdir.
Metot Parametreler
on_epoch_begin self, epoch ve logs
on_epoch_end self, epoch ve logs
on_batch_begin self, batch ve logs
on_batch_end self, batch ve logs
on_train_begin self, logs
on_train_end self, logs
Örneğin:
from tensorflow.keras.callbacks import Callback
class MyCallback(Callback):
def on_epoch_end(self, epoch, logs):
print(f'Epoch: {epoch}, Loss: {logs["loss"]}')
mcb = MyCallback()
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2, callbacks=[mcb], verbose=0)
Pekiyi zaten LambdaCallback çeşitli durumlarda bizim verdiğimiz fonksiyonları çağırıyordu. O zaman custom Callback sınıfı
yazmamızın bir gerekçesi olabilir mi? İşte sınıflar bir faaliyet alanına sahip olduğu için ve örnek özniteliklerinde bilgi
tutabildikleri için karmaşık işlemlerde daha iyi bir araç sunmaktadır.
Aşağıdaki örnekte bir custom Callback sınıfı yazılmıştır. Bu sınıfta her epoch sonucunda loss değerleri toplanıp en sonunda
ortalaması yazdırılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Diabetes')
model.add(Dense(64, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import Callback
class MyCallback(Callback):
def __init__(self):
self.total_loss = 0
self.epochs = 0
def on_epoch_end(self, epoch, logs):
print(f'Epoch: {epoch}, Loss: {logs["loss"]}')
self.total_loss += logs['loss']
self.epochs += 1
def on_train_end(self, logs):
ratio = self.total_loss / self.epochs
print(ratio)
mcb = MyCallback()
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2, callbacks=[mcb], verbose=0)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Tabii aslında custom callback ana mekanizmadır. Diğer callback sınıfları aslında Callback sınııfndan türetilerek yazılmışlardır.
Örneğin aşağıda LambdaCallback sınııfnın nasıl yazılmış olduğuna yönelik bir örnek görüyorsunuz.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Diabetes')
model.add(Dense(64, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import Callback
class MyLambdaCallback(Callback):
def __init__(self, on_epoch_begin=None, on_epoch_end=None, on_batch_begin=None, on_batch_end=None,
on_train_begin=None, on_train_end=None):
super().__init__()
self._on_epoch_begin = on_epoch_begin
self._on_epoch_end = on_epoch_end
self._on_batch_begin = on_batch_begin
self._on_batch_end = on_batch_end
self._on_train_begin = on_train_begin
self._on_train_end = on_train_end
def on_epoch_begin(self, epoch, logs):
if self._on_epoch_begin:
self._on_epoch_begin(epoch, logs)
def on_epoch_end(self, epoch, logs):
if self._on_epoch_end:
self._on_epoch_end(epoch, logs)
def on_batch_begin(self, batch, logs):
if self._on_batch_begin:
self._on_batch_begin(batch, logs)
def on_batch_end(self, batch, logs):
if self._on_batch_end:
self._on_batch_end(batch, logs)
def on_train_begin(self, logs):
if self._on_train_begin:
self._on_train_begin(logs)
def on_train_end(self, logs):
if self._on_train_end:
self._on_train_end(logs)
def on_epoch_begin(epoch, logs):
print(f'Epoch {epoch} begins...', end='\n\n')
def on_epoch_end(epoch, logs):
print(f'\nEpoch {epoch} ends...', end='\n\n')
def on_batch_end(batch, logs):
print(f' Batch: {batch}: Loss: {logs["loss"]}')
mlcb = MyLambdaCallback(on_epoch_begin=on_epoch_begin, on_epoch_end=on_epoch_end, on_batch_end=on_batch_end)
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2, callbacks=[mlcb], verbose=0)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Bir nörona giren değerlerin "w" değerleriyle çarpılıp toplandığını (dot-product) ve sonuca bias değerinin toplanarak aktivasyon
fonksiyonuna sokulduğunu biliyoruz. Örneğin modelde girdi katmanında x1, x2 ve x3 olmak üzere üç "sütun (feature)" bulunuyor olsun.
Bu girdiler ilk saklı katmana sokulduğunda w1 x1 + w2 x2 + w3 x3 + bias biçiminde bir toplam elde edilecektir. Bu toplam da
aktivasyon fonksiyonuna sokulacaktır. İşte bu dot-product işleminde x1, x2, x3 sütunlarının mertebeleri brbirlerinden çok farklıysa
mertebesi yüksek olan sütunun dot-product etkisi yüksek olacaktır. Bu durum da sanki o sütunun daha önemli olarak değerlendirilmesine
yol açacaktır. Bu biçimdeki bir sinir ağı "geç yakınsar" ve kestirim bakımından zayıflar. İşte bu nedenden dolayı işin başında
sütunların (yani özelliklerin) mertebelerini birbirlerine yaklaştırmak gerekmektedir. Bu işlemlere "özellik ölçeklemesi (feature scaling)"
denilmektedir. Yapay sinir ağlarında sütunlar arasında mertebe farklılıkları varsa mutlaka özellik ölçeklemesi yapılmalıdır.
Özellik ölçeklemesi makine öğrenmesinde başka konularda da gerekebilmektedir. Ancak bazı konularda gerekmemektedir. Biz kursumuzda
her konuda özellik ölçeklemesinin gerekli olup olmadığınııklayacağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Çeşitli özellik ölçeklemesi yöntemleri vardır. En çok kullanılan iki yöntem "standart ölçekleme (standard scaling)" ve
"minmax ölçeklemesi (minmax scaling)" dir. Özellik ölçeklemesi konusunda aşağıdaki sorular sıkça sorulmaktadır:
Soru: Yapay sinir ağlarında özellik ölçeklemesi her zaman gerekir mi?
Cavap: eğer sütunlar metrebe olarak birbirlerine zaten yakınsa özellik ölçeklemesi yapılmayabilir.
Soru: Özellik ölçeklemesi gerektiği halde yapmazsak ne olur?
Cevap: Modelin kestirim gücü azalır. Yani performans düşer.
Soru: Özellik ölçeklemesi gerekmediği halde özellik ölçeklemesi yaparsak bunun bir zararı dıkunur mu?
Cevap: Hayır dokunmaz.
Soru: Kategorik sütunlara (0 ve 1'lerden oluşan ya da one hot encoding yapılmış) özellik ölçeklemesi uygulayabilir miyiz?
Cevap: Bu sütunlara özellik ölçeklemesi uygulanmayabilir. Ancak uygulamanın bir sakıncası olmaz. Genellikle veri bilimcisi
tüm sütunlara özllik ölçeklemesi uyguladığı için bunlara da uygulamaktadır.
Özellik ölçeklemesi x verilerine uygulanmalıdır, y verilerine özellik ölçeklemesi uygulamanın genel olarak faydası yoktur.
Ayrıca bir nokta önemlidir: Biz ağımızı nasıl eğitmişsek öyle test ve kestirim yapmalıyız. Yani eğı eğitmeden önce özellik ölçeklemesi
yapmışsak test işleminden önce test verilerini de kestirimn işlemindne önce kestirim verilerini de önce ölçeklendirip sonra işleme sokmalıyız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
En çok kullanılan özellşkj ölçeklendirmesi yöntemlerinden biri "standat ölçekleme (standard scaling)" yöntemidir. Bu yöntemde
sütunlar diğerlerden bağımısz olarak kendi aralarında standart normal dağılıma uydurulmaktadır. Bu işlem şöyle yapılmaktadır:
(x - mean(x)) / std(x)
Tabii burada biz formülü pseudo kod olarak verdik. Burdaki mümean sütunun ortalamasını std ise standart sapmasını belirtmektedir.
Sütunu bu biçimde ölçeklendirdiğimizde değerler büyük ölçüde 0'ın ertafında toplanır.
Standart ölçekleme eğer veri kümesindeki sütunlar kendi aralarında "normal dağılıyorsa" uygulanmalıdır. Tabii buradaki normallik
kusursuz bir normal dağılım değildir. Örneğin bazı sütunlarda çarpıklıklar olabilir. Veri kümesinin çoğunluk sütunları normal
dağılmış ama bazı sütunlar normal dağılmamış olabilir. Bu tür durumlarda standart ölçekleme yine de yapılabilir. Bu kararı
kar-zarar ilişkisi ile veri bilmcisi vermelidir. Sütunların kendi aralarında normal dağılıp dağılmadığını onların histogramını
çizerek anlayabiliriz. Dağılımları çarpıklıkları çok önemsemeyiniz.
Standart ölçekleme yapan bir fonksiyonu aşağıdaki gibi yazabiliriz.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
def standard_scaler(dataset):
result = np.zeros(dataset.shape)
for col in range(dataset.shape[1]):
result[:, col] = (dataset[:, col] - np.mean(dataset[:, col])) / np.std(dataset[:, col])
return result
a = np.array([[12, 20, 9], [2, 5, 6], [4, 8, 9]])
result = standard_scaler(a)
print(a, end='\n\n')
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Aslında yukarıdaki fonksiyon axis=0 için tek hamlede de yapılabilir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
def standard_scaler(dataset):
return (dataset - np.mean(dataset, axis=0)) / np.std(dataset, axis=0)
a = np.array([[12, 20, 9], [2, 5, 6], [4, 8, 9]])
result = standard_scaler(a)
print(a, end='\n\n')
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Asında scikit-learn içerisinde sklearn.preprocessing modülü içerisinde zaten standart ölçekleme yapan StandardScaler isimli
bir sınıf vardır. Bu sınıf diğer scikit-learn sınıfları gibi çalışmaktadır (yani fit, transform, fit_transform).
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
a = np.array([[12, 20, 9], [2, 5, 6], [4, 8, 9]])
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(a)
result = ss.transform(a)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Örneğin "diabetes.csv" veri kümesi üzerinde standart ölçekleme yapmak isteyelim. Bunun için önce veri kümesini dataset_x ve
dataset_y biçiminde ayırmamız gerekir. Ondan sonra biz veri kümesini "eğitim" ve "test" olmak üzere ikiye ayırabiliriz.
Ondan sonra eğitim veri kümesi üzerinde özellik ölçeklemesi yapıp eğitimi uygulayabiliriz. Yani ölçekleme işlemi genel olarak
train_test_split işleminden sonra yapılmaktadır. Burada elde edilen ölçekleme bilgisinin test işlemi işlemi öncesinde test
veri kümesine de, kestirim işlemi öncesinde kestirim veri kümesine de uygulanması gerekmektedir.
Aşağıdaki örnekte "diabetes" veri kümesi üzerinde özellik ölçeklemesi yapılarak eğitim, test ve kestirim işlemleri uygulanmıştır.
Burada dikkat edilmesi gereken önemli noktalar şunlardır:
- fit işlemi yaknızca training_dataset_x üzerinde yapılmıştır.
- Test işleminde training_dataset_x üzerinde yapılan fit işlemine ilişkin bilgiler kullanılarak transform işlemi yapılmıştır.
- Kestirimn işlemi yapılmadan önce yine kestirim yapılacal bilgiler üzerinde ölçekleme yapılmıştır.
- Ölçekleme yapılmamış modelle ölçekleme yapılan model arasında binary_accuracy bakımından %5'ten daha büyük bir fark söz
konusudur.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy(dtype=np.float32)
dataset_y = df.iloc[:, -1].to_numpy(dtype=np.float32)
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(training_dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Diabetes')
model.add(Dense(64, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
scaled_test_dataset_x = ss.transform(test_dataset_x)
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y)
for i in range(len(model.metrics_names)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_data = np.array([[2,148,58,37,128,25.4,0.699,24], [7,114,67,0,0,32.8,0.258,42],
[5,99,79,27,0,29,0.203,32], [0,110,85,30,0,39.5,0.855,39]])
scaled_predict_data = ss.transform(predict_data)
predict_result = model.predict(scaled_predict_data)
for i in range(len(predict_result)):
print(predict_result[i, 0])
for i in range(len(predict_result)):
print("Şeker Hastası" if predict_result[i, 0] > 0.5 else "Şeker Hastası Değil")
#----------------------------------------------------------------------------------------------------------------------------
Diğer çok kullanılna bir özellik ölçeklemesi yöntemi de "min-max" ölçeklemesidir. Bu ölçeklemede sütun değerleri [0, 1]
arasında noktalı sayılarla temsilk edilir. Genel işlem şöyle yapılmaktadır:
(a - min(a)) / (max(a) - min(a))
Bunu bir "pseudo code" olarak ele alınız. Min-max ölçeklemesi sütunlar normal dağılmıyorsa en çok tercih edilen ölçeklemedir.
Min-maz ölçeklemesi yapan bir fonksiyonu aşağıdaki gibi yazabiliriz.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
def minmax_scaler(dataset):
return (dataset - np.min(dataset, axis=0)) / (np.max(dataset, axis=0) - np.min(dataset, axis=0))
a = np.array([[12, 20, 9], [2, 5, 6], [4, 8, 9]])
result = minmax_scaler(a)
print(a, end='\n\n')
print(result
#----------------------------------------------------------------------------------------------------------------------------
scikit-learn kütüphanesi içerisinde min-max ölçeklemesi yapam MinMaxScaler isimli bir sınıf da vardır. Sınıfın kullanımı
diğer scikit-learn sınıflarında olduğu gibidir (yani fit, transform, fit_transform)
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
a = np.array([[12, 20, 9], [2, 5, 6], [4, 8, 9]])
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(a)
result = mms.transform(a)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de "dibates" veri kümesinde min-max ölçeklemesini kullanalım. Aşağıdaki programı çalıştırdığımızda min-maz ölçeklemesinin
standart ölçeklemeden bu veri kümesi için daha iyi sonuçlar verdiğini görmekteyiz.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy(dtype=np.float32)
dataset_y = df.iloc[:, -1].to_numpy(dtype=np.float32)
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Diabetes')
model.add(Dense(64, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
scaled_test_dataset_x = mms.transform(test_dataset_x)
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y)
for i in range(len(model.metrics_names)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_data = np.array([[2,148,58,37,128,25.4,0.699,24], [7,114,67,0,0,32.8,0.258,42],
[5,99,79,27,0,29,0.203,32], [0,110,85,30,0,39.5,0.855,39]])
scaled_predict_data = mms.transform(predict_data)
predict_result = model.predict(scaled_predict_data)
for i in range(len(predict_result)):
print(predict_result[i, 0])
for i in range(len(predict_result)):
print("Şeker Hastası" if predict_result[i, 0] > 0.5 else "Şeker Hastası Değil")
#----------------------------------------------------------------------------------------------------------------------------
Diğer kullanılan ölçeklemeden biri de "mutlak max ölçeklemesi (absolute max scaling)" denilen ölçeklemedir. Bu ölçeklemede
değerler sütunların maximum değerinin mutlak değerine bölünür. Pseudo kodu şöyledir:
a / abs(max(a))
Mutlak max ölçeklemesi scikit-learn kütüphanesindeki MaxAbsScaler sınıfı ile gerçekleştirilebilmrktedir. Sınıfın çalışması
benzerdir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
a = np.array([[12, 20, 9], [2, 5, 6], [4, 8, 9]])
from sklearn.preprocessing import MaxAbsScaler
mas = MaxAbsScaler()
mas.fit(a)
result = mas.transform(a)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi özellik ölçeklemesi yaptığımız bir modeli nasıl saklayıp geri yükleyebiliriz? Yukarıdaki örneklerde biz özellik
ölçeklemesini scikit-learn kullnarak yaptık. Sequential sınıfının save metodu modeli save ederken bu ölçeklemelerini modelin
bir parçası olmadığı için save etmemektedir. Bu nedenle bizim bu bilgileri ayrıca save etmemiz gerekmektedir. Ancak Keras'ta
özellik ölçeklendirmeleri bir katman nesnesi olarak da bulundurulmuştur. Eğer özellik ölçeklemeleri bir katman ile yapılırsa
bu durumda modeli zaten save ettiğimizde bu bilgiler de save edilmiş olacaktır. Biz burada scikit-learn ölçekleme bilgisinin
nasıl saklanacağı ve geri yükleneceği üzerinde duracağız.
Programalamada bir sınıf nesnesinin diskteki bir dosyaya yazılmasına ve oradan geri okunmasına "object serialization" denilmektedir.
scikit-learn içerisinde "object serialiazation" işlemine yönelik özel sınıflar yoktur. Ancak seri hale getirme işlemi Python'ın
standart kütüphanesindeki pickle modülü ile yapılabilmektedir.
Aşağıdaki örnekte model "diabetes.h5" dosyası içerisinde, MinMaxScaler nesnesi de "diabetes.pickle" dosyası içerisinde
saklanmıştır ve geri yüklenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
# scaled-model-save.py
import numpy as np
import pandas as pd
df = pd.read_csv('diabetes.csv')
dataset_x = df.iloc[:, :-1].to_numpy(dtype=np.float32)
dataset_y = df.iloc[:, -1].to_numpy(dtype=np.float32)
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Diabetes')
model.add(Dense(64, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
scaled_test_dataset_x = mms.transform(test_dataset_x)
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y)
model.save('diabetes.h5')
import pickle
with open('diabetes.pickle', 'wb') as f:
pickle.dump(mms, f)
# scaled-model-load.py
from tensorflow.keras.models import load_model
model = load_model('diabetes.h5')
import pickle
with open('diabetes.pickle', 'rb') as f:
mms = pickle.load(f)
import numpy as np
predict_data = np.array([[2,148,58,37,128,25.4,0.699,24], [7,114,67,0,0,32.8,0.258,42],
[5,99,79,27,0,29,0.203,32], [0,110,85,30,0,39.5,0.855,39]])
scaled_predict_data = mms.transform(predict_data)
predict_result = model.predict(scaled_predict_data)
for i in range(len(predict_result)):
print(predict_result[i, 0])
for i in range(len(predict_result)):
print("Şeker Hastası" if predict_result[i, 0] > 0.5 else "Şeker Hastası Değil")
#----------------------------------------------------------------------------------------------------------------------------
Auto-MPG isimli veri kümesi otomobillerin mil başına yaktıkları yakıtın galon cinsinden tahmin edilmesi amacıyla oluşturulmuştur.
Lojistik olmayan regresyon problemleri için sık kullanılan veri kümelerinden biridir. Veriler 80'li yılların başlarında
toplanmıştır. O zamanki otomobil teknolojisi dikkate alınmalıdır. Veri kümesi aşağıdaki adresten indirilebilir:
https://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/
Verti kümsindeki sütunlar SPACE karakterleriyle ve son sütun da TAB karakteriyle biribirlerinden ayrılmıştır. Sütunların
anlamları şöyledir:
1. mpg: continuous
2. cylinders: multi-valued discrete
3. displacement: continuous
4. horsepower: continuous
5. weight: continuous
6. acceleration: continuous
7. model year: multi-valued discrete
8. origin: multi-valued discrete
9. car name: string (unique for each instance)
Burada orijin kategorik bir sütundur. Buradaki değer 1 ise araba Amerina, 2 ise Avrupa ve 3 ise Japon'dur.
Aşağıdaki örnekte veri kümesi için yapay sinir ağı modeli oluşturulmuştur. Kullanılan model şöyledir:
- Veriler Pandas'ın read_csd fonksiyonuyla okunmuş son sütun atılmıştır.
- Veri kümesinin 3'üncü indeksli sütununda '?' biçiminde eksik veriler vardır. Bunlar tamamen veri kümesinden atılmıştır.
- Modelde iki saklı katman vardır. Saklı katmanlardaki aktivasyon fonksiyonu 'relu' alınmıştır.
- Veri kümesinde sütunların skalaları farklı olduğu için özellik ölçeklemesi uygulnamıştır.
- Model "lojistik olmayan regresyon" modelidir. Dolayısıyla modelin çıktı katmanında tek bir nöron vardır ve onun da
aktivasyon fonksiyonu 'linear' alınmıştır.
- Modelin ağırlık değerleri ve ölöekleme bilgisi save edilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('auto-mpg.data', delimiter=r'\s+', header=None)
df = df.iloc[:, :-1]
dataset_df = df[df.iloc[:, 3] != '?']
dataset_ohe_df = pd.get_dummies(dataset_df, columns=[7])
dataset = dataset_ohe_df.to_numpy(dtype='float32')
dataset_x = dataset[:, 1:]
dataset_y = dataset[:, 0]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Auto-MPG')
model.add(Dense(64, activation='relu', input_dim=training_dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
hist = model.fit(scaled_training_dataset_x , training_dataset_y, batch_size=32, epochs=200, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Mean Absolute Error Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['mae'])
plt.plot(hist.epoch, hist.history['val_mae'])
plt.legend(['Mean Absolute Error', 'Validation Mean Absolute Error'])
plt.show()
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
predict_data = np.array([[4, 91, 81, 2230, 17.5, 71, 1, 0, 0], [4, 150, 92, 2164, 16.5, 70, 0, 1, 0],
[3, 111, 90, 2428, 15, 78, 0, 0, 1]])
scaled_predict_data = mms.transform(predict_data)
predict_result = model.predict(scaled_predict_data)
for val in predict_result[:, 0]:
print(val)
model.save('auto-mpg.h5')
import pickle
with open('auto-mpg.pickle', 'wb') as f:
pickle.dump(mms, f)
#----------------------------------------------------------------------------------------------------------------------------
Yukarıdaki modelin save edilen bilgileri aşağıdaki gibi diskten okunarak kestirim işlemi yapılabilir. Aşağıdaki örnekte
kestirilecek değerler de bir CSV dosyasından okunmaktadır. Kestirilecek değerler üzerinde yine "one hot encoding" dönüştürmesi
ve özellik ölçeklemesi yapılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.models import load_model
model = load_model('auto-mpg.h5')
import pickle
with open('auto-mpg.pickle', 'rb') as f:
mms = pickle.load(f)
import pandas as pd
df = pd.read_csv('predict-data.csv', header=None)
predict_data = pd.get_dummies(df, columns=[6]).to_numpy()
scaled_predict_data = mms.transform(predict_data)
predict_result = model.predict(scaled_predict_data)
for val in predict_result[:, 0]:
print(val)
#----------------------------------------------------------------------------------------------------------------------------
One hot encoding yaparken Pandas'ın get_dummies fonksiyonunu kullanırken dikkat ediniz. Bu fonksiyon one hot encoding yapılacak
sütundaki kategorileri kendisi belirlemektedir. Eğer predict yapacağınız CSV dosyasındaki satırlar tüm kategorileri içermezse
bu durum bir sorun yaratır. Tabii bu durumda veri kğmesinin sonuna 0'lardan olulan sütunlar eklenebilir. scikit-learn içerisindeki
OneHotEncoder sınıfı bu tür durumlarda "categories" isimli parametreyle bizlere yardımcı olmaktadır. get_dummies fonksiyonun böyle bir parametresi
yoktur.
OneHotEncoder sınfıının __init__ metodunda categories isimli parametre ilgili sütundaki kategorileri belirtmek için düşünülmüştür.
Ancak biz mevcut kategorilerden daha fazla kategori oluşturmak istiyorsak bu parametredeki listeye eklemeler yapabiliriz. categories
parametresi iki boyutlu bir liste olarak girilmelidir. Çünkü birden fazla sütun one hot encoding işlemine sokulabilmektedir. Bu durumda
bu iki boyutlu lsitenin her elemanı sırasıyla sütunları belirtir. Örneğin:
ohe = OneHotEncoder(sparse=False, categories=[[0, 1, 2], [0, 1, 2, 3, 4]])
Burada biz iki sütunlu bir veri tablosunun iki sütununu da one hot encoding yapmak istemekteyiz. Bu veri tablosunun ilk sütunundaki
kategoriler 0, 1, 2 biçiminde, ikinci sütunundaki kategoriler 0, 1, 2, 3, 4 biçimindedir. Eğer bu sütunlarda daha az kategori varsa
burada belirtilen sayıda sütun oluşturulur.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.models import load_model
model = load_model('auto-mpg.h5')
import pickle
with open('auto-mpg.pickle', 'rb') as f:
mms = pickle.load(f)
import pandas as pd
df = pd.read_csv('predict-data.csv', header=None)
predict_data = pd.get_dummies(df, columns=[6]).to_numpy()
scaled_predict_data = mms.transform(predict_data)
predict_result = model.predict(scaled_predict_data)
for val in predict_result[:, 0]:
print(val)
#----------------------------------------------------------------------------------------------------------------------------
Tabii yukarıdaki problem get_dummies fonksiyonu ile şöyle de çözülebilirdi: Biz önce get_dummies işlemini yapardık. Sonra
elde ettiğimiz DataFrame nesnesine içi sıfırlarla dolu n tane sütun eklerdik. Aşağıdaki kod bir DataFrame nesnesinin sonuna
içi 0'larla dolu n tane sütun eklemek için kullanılabilir:
df_result = pd.concat([df, pd.DataFrame(np.zeros((n, k)))], axis=1)
Burada n satırlık bir DataFrame nesnesine k tane içi 0'larla dolu sütun eklenmiştir. Birer sütun ismi uydurulursa bu işlem
aşağıdaki gibi de yapılabilir:
df[['a', 'b', 'c']] = 0
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Lojistik olmayan regresyon problemlerinde kullanılan çok kullanılan veri kümelerinden biri de "Boston Housing Price" isiml
veri kümesidir. Bu veri kümesinde evin çeşitli bilgileri sütunlar haline kodlanmıştır. Amaç evin fiyatını tahmin etmektir.
Veriler 1070 yılında toplanmıştır. Veri küümesi aşağıdaki bağlantıdan indirilebilir:
https://www.kaggle.com/datasets/vikrishnan/boston-house-prices
Veri kümesinin son sütunu evin fiyatını belirtmektedir. Bu değeri 1000 ile çarpılmalıdır (1970'te 20000 dolar bugünküne
göre çok daha değerliydi). Veri kümesinde bir tane kategorik alan vardır. O da iki kategori içerdği için one hot encoding
yapılmayacaktır.
Aşağıda modelin tipik biçimi verilmiştir:
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Boston-Housing-Price')
model.add(Dense(64, activation='relu', input_dim=training_dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=200, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Mean Absolute Error Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['mae'])
plt.plot(hist.epoch, hist.history['val_mae'])
plt.legend(['Mean Absolute Error', 'Validation Mean Absolute Error'])
plt.show()
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
predict_data = np.array([[0.11747, 12.50, 7.870, 0, 0.5240, 6.0090, 82.90, 6.2267, 5, 311.0, 15.20, 396.90, 13.27]])
scaled_predict_data = mms.transform(predict_data)
predict_result = model.predict(scaled_predict_data)
for val in predict_result[:, 0]:
print(val)
model.save('boston.h5')
import pickle
with open('boston.pickle', 'wb') as f:
pickle.dump(mms, f)
#----------------------------------------------------------------------------------------------------------------------------
Girdiyle çıktı arasında ilişki kurma sürecine "regresyon" denilmektedir. Regresyon çeşitli biçimlerde yani çeşitli yöntemlerle
gerçekleştirilebilmektedir. Aslıda yapay sinir ağları da regresyon için bir yöntem grubunu oluşturmaktadır. Regresyon y = f(x)
biçiminde uygun bir fonksiyonun bulunması sürecidir. Eğer biz böyle bir fonksiyon bulursak x değerlerini fonksiyonda yerine koyarak
y değerini elde edebiliriz. Tabi y = f(x) fonklsiyonunda x birden fazla değişkeni temsil ediyor olabilir. Benzer biçimde bu
eşitlikte f fonksiyonu birden fazla değer de veriyor olabilir.
İstatistikte regresyon işlemleri tipik olarak aşağıdaki gibi sınıflandırılmaktadır:
- Eğer bağımsız değişken bir tane ise buna genellikle "basit regresyon (simple regression)" denilmektedir.
- Eğer x birden fazla ise buna da genellikle "çoklu regresyon (mulptiple regression)" denilmektedir.
- İstatistikte regresyonun çıktısı kategorik değerler ise yani f fonksiyonu bir kategori değeri üretiyorsa buna "lojistik
regresyon (logictics regression)" ya da "logit regresyonu" denilmektedir. Lojistk regresyonda çıktı iki sınıftan oluşuyorsa
(hasta-sağlıklı gibi, olumlu-olumsuz gibi, doğru-yanlış gibi) böyle lojistik regresyonlara "iki sınıflı lojistik regresyon
(binary logistic regression)" denilmektedir. Eğer çıktı ikiden fazla sınıftan oluşyorsa böyle lojistik regresyonlara da "çok
sınıflı lojistik regresyon (multi-class/multinomial logistic regression)" denilmektedir.
- Eğer girdiyle çıktı arasında doğrusal bir ilişki kurulmak isteniyorsa (yani regresyon işleminden bir doğru elde edilmek
isteniyorsa) bu tür regresyonlara "doğrusal regresyon (linear regression)" denilmektedir. Doğrusal regresyon da bağımsız
bir tane ise "basit doğrusal regresyon (simple linear regression)", bağımsız değişken birden fazla ise "çoklu doğrusal regresyon
(multiple linear regression)" biçiminde ikiye ayrılabilmektedir.
- Bağımsız değişken ile bağıumlı değişken arasında polinomsal ilişki kurulmaya çalışılabilir. (Yani bir polinom eğrisi elde
edilmeye çalışılabilir). Buna da "polinomsal regresyon (polynomial regression)" denilmektedir. Bu da yine basit ya da
çoklu olabilir. Aslında işin matematiksel tarafında polinomsal regresyon bir transformasyonla doğrusal regresyon haline
dönüştürülebilmektedir. Dolayısıyla doğrusal regresyonla polinomsal regresyon arasında aslında işlem bakımından önemli bir
fark yoktur.
- Girdiyle çıktı arasında doğrusal olmayan bir ilişki de kurulmak istenebilir. (Yani doğrusal olmayan bir eğri de oluşturulmak
istenebilir). Bu tür regresyonlara "doğrusal olmayan regresyon (nonlinear regression)" denilmektedir. Her ne kadar polinomlar
da doğrusal değilse de bunlar transformasyonla doğrulsal hale getirilebildikleri için doğrusal olmayan regresyon demekle genel
olarak polinomsal regreson kastedilmemektedir. Örneğin logatirmik, üstel regresyonlar doğrusal olmayan regresyonlara örnektir.
- Bir regresyonda çıktı da birden fazla olabilir. Genellikle (her zaman değil) bu tür regresyonlara "çok değişkenli (multivariate)"
regresyonlar denilmektedir. Örneğin:
y1, y2 = f(x1, x2, x3, x4, x5)
Çok değişkenli sözcüğü genellikle bağımsız değişkenin birden fazla olmasını değil (buna çoklu denmektedir) bağımlı değişkenin
birden fazla olması için kullanılmaktadır.
- Lojistik regresyon problemlerinde bir de "etiket (label)" kavramı sıklıkla karşımıza çıkmaktadır. Etiket genellikle çok
değişkenli sınıflandırma problemlerinde yani çıktının birden fazla olduğu ve kategorik olduğu problemlerde her çıktı için
kullanılan bir terimdir. Örneğin biz bir sinir ağından üç bilgi elde etmek isteyebiliriz: "kişinin hasta olup olmadığı",
"kişinin şişma olup olmadığı", "kişinin mutlu olup olmadığı". Burada üç tane etiket vardır. "Sınıf kavramı" belli bir etiketteki
kategorileri belirtmek için kullanılmaktadır. Bu durumda "tek etiketli ikili lojistik regresyon (single-lable binary
logistic regression)" gibi, "tek etiketli çok sınıflı lojistik regresyon (single-lable multinomial logistic regression)"
gibi terimleri de duyabiliriz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda üç sınıflı bir sınıflandırma problemi örneği verilmektedir. Örnek veri kümesi "iris (zambak)" isimli eğitim amaçlı
çok kullanılan bir veri kümesidir. Veri kümesinde üç grup zamnbak vardır: "Iris-setosa", "Iris-sersicolor" ve "Iris-virginica".
x verileri ise çanak yaprakların ve taç yaprakların genişlik ve yüksekliklerine ilişkin dört değerden oluşmaktadır. Veri
kümesi içerisinde "id" isimli ilk sütun sıra numarası belirtir. Dolayısıyla kestirim sürecinde bir anlamı yoktur. Veri kümesi
aşağıdaki bağlantıdan indirilebilir:
https://www.kaggle.com/datasets/uciml/iris?resource=download
Çok sınıflı sınılandırma (lojistik regresyon) problemlerinde çıktıların (yani dataset_y verilerinin) one hot encoding işlemine
sokulması gerekir. One hot encodinh yapıldığında uygulamacının sütunlara ilişkin sınıfları biliyor olması gerekir. Pandas'ın
gety_dummies fonksiyonu aslında önce unique fonksiyonunu uygulayıp one hot encoding işlemi yapmaktadır. unique fonksiyonu
ise bir sıraya dizme işlemi yapar. Dolayısıyla aslında get_dummies kategori belirten yazıları küçükten büyüğe sütunlarla
temsil etmektedir. SciPy'daki OneHotEncoder sınıfı zaten kendi içerisinde categories_ özniteliği ile bu sütunların neler
olduğunu sırasıyla bize vermektedir.
Çok sınıflı modellerin çıktı katmanında sınıf sayısı kadar nöron olması gerektiğini belirtmiştik. Çıktı katmanında aktivasyon
fonksiyonu olarak softmax alındığı için bunların değerleri toplamı 1 olmak zorundadır. Bu durumda biz kestirim işlemi yaparken
çıktıdaki en büyük değerli nöronu tespit etmemiz gerekir. Tabii bizim en büyük çıktıya sahip olan nöronun çıktı değerinden
ziyade onun çıktıdaki kaçıncı nöron olduğunu tespit etmemiz gerekmektedir. Bu işlem tipik olarak Numpy kütüphanesindeki
argmax fonksiyonu ile yapılır. Pekiyi varsayalım ki ilki 0 olmak üzere 2 numaları nöronun değeri en yüksek olmuş olsun.
Bu 2 numaralı nöron hangi sınıfı temsil etmektedir? İşte bu 2 numaralı nöron aslında eğitimdeki dataset_y sütunun one hot
encoding sonucundaki 2 numaralı sütunu temsil eder. O halde bizim dataset_y değerlerini one hot encoding yaparken hangi
sütunun hangi sınıfa karşı geldiğini biliyor olmamız gerekir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('iris.csv')
dataset_x = df.iloc[:, 1:-1].to_numpy(dtype='float32')
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse=False)
dataset_y = ohe.fit_transform(df.iloc[:, -1].to_numpy().reshape(-1, 1))
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(training_dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
scaled_test_dataset_x = ss.transform(test_dataset_x)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Iris')
model.add(Dense(64, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(3, activation='softmax', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=200, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Categorical Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['categorical_accuracy'])
plt.plot(hist.epoch, hist.history['val_categorical_accuracy'])
plt.legend(['Categorical Accuracy', 'Validation Categorical Accuracy'])
plt.show()
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_df = pd.read_csv('iris-predict.csv', header=None)
predict_data = predict_df.to_numpy(dtype='float32')
scaled_predict_data = ss.transform(predict_data )
predict_result = model.predict(scaled_predict_data)
import numpy as np
result_index = np.argmax(predict_result, axis=1)
predict_names = ohe.categories_[0][result_index]
print(predict_names)
model.save('iris.h5')
import pickle
with open('iris.pickle', 'wb') as f:
pickle.dump(ss, f)
#----------------------------------------------------------------------------------------------------------------------------
Sınıflandırma problemlerinde üzerinde çalışılan problem gruplarından biri de "sentiment analysis" denilen gruptur. Bu grup
problemlerde kişiler bir olgu hakkında kanılarını belirten yazılar yazarlar. Sonra yazılan bu yazılardan kanının olumlu olup
olmadığı kestirilmeye çalışılır. Tabii bu tür problemler iki sınıflı olabildiği gibi çok sınıflı da olabilmektedir. Sentiment
analiz için oluşturulmuş örnek veri kümeleri vardır. Bunlardan en çok kullanılanlarından biri IMDB (ınternet Movie Database)
veri kümesidir. Bu veri kümesinde kişiler bir film hakkında yorum yazısı yazmışlardır. Bu yorum yazısı "positive" ya da
"negative" olarak sınıflandırılmaktadır. Böylece birisinin yazdığı yazının olumlu ya da olumsuz yargı içerdiği otomtik olarak
tespit edilebilmektedir.
Bu problemde girdiler yani dataset_x yazılardan oluşmaktadır. Çıktı ise ikili bir çıktıdır. Bu tarz problemlerde girdiler
birer yazı olduğu için işlemlere doğrudan sokulamazlar. Önce onların bir biçimde sayısal hale dönüştürülmeleri gerekir.
İşte yazıların sayılara dönüştürülmesi için ilk yapılacak şey yazıları sözcüklere ayırmaktır. Bu işlem Python'daki regex
kütüphanesi kullanılarak basit bir biçimde yapılabilmektedir. Tüm yorumlardakiş tüm sözcüklere "vocabulary" denilmektedir.
İşte uygulamacı öncelikle "vocabulary"yi elde etmelidir. Sonra uygulamacı bu vocalary'deki her sözcüğe unique bir numara
vermelidir. Tabii bunın bir sözlük kullanılması uygun olur. Sözlüğün anahtarları sözcükler, değerleri ise bu sözcüklere
karşı gelen unique sayılardır. Hangi sözcüğün hangi sayıya karşı geldiğinin bir önemi yoktur. Önemli olan vocabulary'deki
her sözcüğe unique bir numara karşı getirilmiş olmasıdır. Böylelikle artık kiilerin yaptığı yorumlar bir yazı olmaktan
çıkartılıp bir sayı dizisi haline dönüştürülmüş olur. Ancak geldiğimiz bu noktada iki pürüz vardır:
1) Sözcüklerin belirttiği sayıların anlamı olmamalıdır. Ancak sinir ağı bunlara anlam yüklemeye çalışır. Yani aslında
sözcükler kategorik bir bilgi gibi sayısallaştırılmalıdır.
2) Her yorumda eşit sayıda sözcük yoktur. Oysa sinir ağlarının girdi katmanına hep eşit sayıda girdinin uygulanması
gerekmektedir.
İşte bu tür uygulamalarda temelde iki yöntem kullanılmaktadır:
1) Vektörüzasyon (vectorization) yöntemi
2) Word embedding yöntemi
Biz burada öncelikle "vektörizasyon" yöntemi üzerinde duracağız. Vektörizasyon şöyle bir yöntemdir:
- Her yorum için toplam vocabulary'deki sözcük sayısı kadar içi 0 ile dolu olan bir dizi oluşturulur. Örneğin vocabulary
genişliğinin 100000 olduğunu düşünelim. Bu durumda her yorum 100000'lik bir dizi ile temsil edilecektir. Yani sinir ağının
girdi katmanında 100000 nöron bulunacaktır.
- Sayısallaştırılmış olan yazıların sözcükler değil sayılardan oluştuğunu belirtmiştik. Şimdi bir yorumdaki her bir sayı
için oluştutulmuş olan dizinin ilgili inmdeksli elemanı 1 yapılır. Örneğin yorum şu değerlerden oluşyor olsun: 101, 8452,
16, 73452, 9371. Burada biz 100000'lik dizinin 101'inci, 8452'inci, 16'ıncı, 73452'inci, 9371'inci elemanlarını 1 yaparız.
Böylece büyük miktarda elemanı 0 olan yalnızca bazı elemanları 1 olan "binary encoding" yapılmış bir vektör elde ederiz.
Artık eğitimde bu vektörleri kullanırız.
Bu vektörizasyon yöntemi fazlaca bellek kullanma eğilimindedir. Veri yapıları dünyasında çok büyük kısmı 0 olan, az kısmı
farklı değerde bulunan matrislere "sparse (seyrek)" matris denilmektedir. Buradaki vektörler seyrek durumda olacaktır.
Eğer vocabulary çok büyükse gerçekten de tüm girdilerin yukarıda belirtildiği gibi bir matris içerisinde toplanması zor ve
hatta imkansız olabilmektedir. Çünkü fit metodu bziden training_dataset_x ve training_dataset_y yi bir bütün olarak istemektedir.
Aşağıdaki örnekte yukarıda anlatılan adımlar sırasıyla uygulanmıştır. Kodun anlamlandırılması için birkaç anahtar nokta şunlar
olabilir:
- Bütün vocabulary word_dict isimli bir sözlükte bulundurulmuştur. Sözlüğün anahtarları sözcükler değerleri ise "unique"
olan numaralardır.
- dataset_x dixzisi toplam yorum sayısı kadar satırdan ve vocabulary uzunluğu kadar sütundan oluşmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
word_set = set()
import regex
for text in df['review']:
words = regex.findall("[A-Za-z-0-9'-]+", text.lower())
word_set.update(words)
"""
word_dict = {}
for index, word in enumerate(word_set):
word_dict[word] = index
"""
import numpy as np
word_dict = {word: index for index, word in enumerate(word_set)}
dataset_x = np.zeros((df.shape[0], len(word_dict)), dtype='int8')
for row, text in enumerate(df['review']):
words = regex.findall("[A-Za-z-0-9'-]+", text.lower())
word_indices = [word_dict[word] for word in words]
dataset_x[row, word_indices] = 1
dataset_y = np.zeros(len(df), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
"""
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
dataset_y = le.fit_transform(df['sentiment'])
"""
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='IMDB')
model.add(Dense(128, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=5, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
texts = ['the movie was very good. The actors played perfectly. I would recommend it to everyone.', 'this film is awful.
The worst film i have ever seen']
for predict_text in texts:
words = regex.findall("[A-Za-z-0-9'-]+", predict_text.lower())
predict_vect = np.zeros(len(word_dict), dtype='int8')
word_indices = [word_dict[word] for word in words]
predict_vect[word_indices] = 1
predict_result = model.predict(predict_vect.reshape(1, -1))
if predict_result[0, 0] > 0.5:
print('Positive')
else:
print('Negative')
model.save('imdb.h5')
# Reverse operation (exposition only), not correct
rev_word_dict = {value: key for key, value in word_dict.items()}
indices = np.argwhere(dataset_x[0] == 1).flatten()
result_text = ' '.join([rev_word_dict[index] for index in indices])
#----------------------------------------------------------------------------------------------------------------------------
Yukarıdakiş gibi yazıların vektörizasyon işlemiyle binary bir vektöre dönüştürülmesi işleminin görünen dezavantajları
şunlardır:
- Aynı sözcükten birden fazla kez yazı içerisinde kullanılmışsa bunun eğitimde bir anlamı kalmamaktadır. Oysa gerçek hayattaki
yazılarda örneğin "mükemmel" gibi bir sözcük çokça tekrarlanıyorsa bu durum onun olumlu yorumlanma olasılığını artırmaktadır.
- Vektörizasyon işlemi bir bağlam oluşturamamaktadır. Şöyle ki: Biz bir yazıda "çok kötü" dersek buradaki "çok" aslında "kötüyü"
nitelemektedir. Ancak bunu bizim ağımız anlayamaz. Başka bir deyişle biz yorumdaki sözcüklerin sırasını değiştirsek de elde
ettiğimiz vektör değişmeyecektir.
- Vektörizasyon işlemi çok yer kaplama potansiyelinde olan bir işlemdir. Bu durumda ağı parçalı olarak eğitmek zorunda kalabiliriz.
Parçalı eğitimler ileride ele alınacaktır.
- Biz işlemden önce tüm sözcükleri küçük harfe dönüştürdük. Bazı özel karakterleri sözcüğün parçası olmaktan çıkardık. Halbuki
bu gibi bazı küçük ayrıntılar yazının daha iyi anlamlandırılmasına katkı sağlayabilir. Tabii bu durumda vocabulary büyür bu
da eğitimin zorlaşması anlamına gelir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aslında vektörizasyon işlemi pratik bir biçimde scikit-learn kütüphanesindeki CountVectorizer sınıfıyla yapılabilmektedir.
Sınıfın kullanımı şöyledir:
- Önce CountVectorizer sınıfı türünden bir nesne yaratılır. Nesne yaratılırken sınıfın __init__ metodunda bazı önemli
belirlemeler yapılabilmektedir. Örneğin dtype parametresi ile elde edilecek vektörün elemanlarının türü belirtilebilir. Bu
parametreyi 'int8' gibi küçük bir tür olarak geçmek gerekebilir. Çünkü default durum 'float64' biçimindedir. Sınıf yine
default durumda tüm sözcükleri küçük harfe dönüştürmektedir. Metodun lowercase parametresi False geçilirse bu dönüştürme
yapılmamaktadır. Metodun diğer önemli parametreleri de vardır. Örneğin:
cv = CountVectorizer(dtype='uint8')
Bundan sonra scikit-learn kütüphanesinin diğer sınıflarında olduğu gibi fit ve trasform işlemleri yapılır. fit işleminde biz
fit metoduna yazılardan oluşan dolaşılabilir bir nesne veriririz. fit metoudu tüm yazılardan bir "sözlük haznesi (vocabulary)"
oluşturur. Biz de bu sözlük haznesini bir sözlük nesnesi biçiminde nesnenin vocabulary_ özniteliğinden elde edebiliriz. Örneğin:
texts = ['Film güzeldi ama oyuncular kötü oynamıştı. Senaryo da güzeldi.', 'Film berbattı. Ama ben eğlendim. Film beklenedildiği
gibi bitti.']
cv = CountVectorizer(dtype='uint8')
cv.fit(texts)
print(cv.vocabulary_)
Asıl dönüştürmeyi transform metodu yapmaktadır. fit metodu yalnızca sözlük haznesini oluşturmaktadır. Ancak tranform bize
vektörel hale getirilmiş olan yazıları "seyrek matris (sparse matrix)" biçiminde vermektedir. Örneğin:
sparse_result = cv.transform(texts)
Seyrek matrisleri normal matrise dönüştürmek için seyrek matris sınıflarının todense metotları kullanılmaktadır:
dense_result = sparse_result.todense()
#----------------------------------------------------------------------------------------------------------------------------
from sklearn.feature_extraction.text import CountVectorizer
texts = ['Film güzeldi ama oyuncular kötü oynamıştı. Senaryo da güzeldi.', 'Film berbattı. Ama ben eğlendim. Film beklenedildiği
zgibi bitti.']
cv = CountVectorizer(dtype='uint8')
cv.fit(texts)
print(cv.vocabulary_)
sparse_result = cv.transform(texts)
dense_result = sparse_result.todense()
print(dense_result)
Şöyle bir çıktı elde edilmiştir:
{'film': 7, 'güzeldi': 9, 'ama': 0, 'oyuncular': 12, 'kötü': 10, 'oynamıştı': 11, 'senaryo': 13, 'da': 5, 'berbattı': 3, 'ben': 2,
'eğlendim': 6, 'beklenedildiği': 1, 'gibi': 8, 'bitti': 4}
[[1 0 0 0 0 1 0 1 0 2 1 1 1 1]
[1 1 1 1 1 0 1 2 1 0 0 0 0 0]]
#----------------------------------------------------------------------------------------------------------------------------
CountVectorizer sınıfının __init__ metodunun binary parametresi default olarak False durumdadır. Bu parametrenin False olması
yazı içerisinde belli bir sözcük n defa geçtiğinde o sözcüğe ilişkin sütun elemanın n olacağı anlamına gelmektedir. Eğer bu parametre
True yapılırsa bu durumda binary bir vector elde edilir. Pekiyi biz vektörizasyon yaparken "binary" mi yoksa sayaçlı mı vektörizsyon
yapmalıyız? Aslında sayaçlı vektörizasyon yapmak toplamda daha iyidir. Tabii binary bilgilerin tutulma biçiminden özel olarak bir
kazanç sağlanmaya çalışılabilir.
CountVectorizer sınıfının __init__ metodunun lowercase parametresi default olarak True durumdadır. Bu parametre sözcük
haznesi oluşturulmadan önce tüm yazıların küçük harfe dönüştürülüp dönüştürülmeyeceğini belirtmektedir. CountVectorizer default
durumda tüm yazıları küçük harfe dönüştürmektedir. Tüm yazıların küçük harfe dönüştürülmesi sözcük haznesini küçültmektedir.
Ancak bazen büyük harfli yorrumlar vurgusal br anlam taşıyor olabilirler. Yani küçük harfe dönüştürmenin bu bakımdan bir bilgi
kaybı söz konusu olabilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
tensorflow.keras.datasets paketinin içersinde çok kullanılan çeşitli veri kümelerine ilişkin modüller bulunmaktadır. Bunların listesi şöyledi:
boston_housing module
cifar10
cifar100
fashion_mnist
imdb module
mnist module
reuters module
Bu modüllerin load_data isimleri fonksiyonları bize ikili demetlerden oluşan ikili bir demet vermektedir. Bu fonksiyonların verdiği demet
((training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y)) biçimindedir. Örneğin "Boston Housing Prices" veri kümesini hazır
bir biçimde şöyle yükleyebiliriz:
from tensorflow.keras.datasets import boston_housing
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = boston_housing.load_data()
IMDB verileri de benzer biçimde yüklenebilmektedir:
from tensorflow.keras.datasets import imdb
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = imdb.load_data()
Burada load_data fonksiyonunun num_words isimli parametresine eğer bir değer girilirse bu değer toplam kelime haznesinin sayısını belirtmektedir.
Örneğin:
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = imdb.load_data(num_words=1000)
Burada artık yorumlar en sık kullanılan 1000 sözcükten hareketle oluşturulmaktadır. Yani bu durumda vektörizasyon sonrasında vektörlerin
sütun uzunlukları 1000 olacaktır. Bu parametre girilmezse IMDB yorumlarındaki tüm sözükler dikkate alınır.
imdb modülündeki get_word_index fonksiyonu bize sözcük haznesini bir sözlük olarak vermektedir. num_words ne olurs aolsun bu sözlük her zaman
tüm kelmie haznesini içermektedir.
vocab_dict = imdb.get_word_index()
load_data fonksiyonunun bize verdiği veri kümeleri Numpy dizisi biçimindedir. Ancak bu Numpy dizisinin her elemanı listelerden oluşmaktadır. Bu listelerin içerisinde
de o yorumda geçen sözcüklerin numaraları bulunmaktadır. Tabii bu indekslerden oluşan listeyi yeniden yazıya dönüştürmek için ters bir sözlük
hazırlamak gerekir:
rev_vocab_dict = {value: key for key, value in vocab_dict.items()}
Ancak burada önemli bir nokta vardır. Bu modülü hazırlayanlar 0, 1 ve 2 numaralara özel başka bir anlam yüklemişlerdir.
Bu nedenle veri kümesinden elde ettiğimiz sayılar hep 3 fazladır. Bizim bu sayıları yazılara dönüştürmemiz için bunlardan 3 çıkarttıktan
sonra rev_vocab_dict sözlüüne sokmamız gerekir. Örneğin:
def get_text(text_numbers):
return ' '.join ([rev_vocab_dict[number - 3] for number in text_numbers if number > 2])
text = get_text(training_dataset_x[0])
print(text)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yazıların sınıflandırılması için çok kullanılan diğer bir veri kümesi de "Reuters" isimli veri kümesidir. Bu veri kümesi
80'lerin başlarında Reuters haber ajansının haber yazılarından oluşmaktadır. Bu haber yazıları birtakım konulara ilişkindir.
Dolayısıyla bu veri kmesi "çok sınıflı sınıflandırma (lojistek regresyon)" problemleri için örnek amacıyla kullanılmaktadır.
Haberler toplam 46 farklı konuya ilişkindir.
Reuters veri kümesinin orijinali .SGM uzantılı dosyalar biçiminde SGML formatındadır. Dolayısıyla bu verilerin kullanıma hazır hale
getirilmesi biraz yorucudur. Ancak tensorflow.keras.datasets paketi içerisinde reuters isimli modül tıpkı IMDB verilerinde olduğu
bu veri kümesini hazır bir biçimde yükleyip bize vermektedir. Ayrıca aşağıdaki bağlantıda her yazının bir dosya olarak da kaydedilmiş
olarak bulunmaktadır:
https://www.kaggle.com/datasets/nltkdata/reuters
Buradaki dosya indirilip açıldığında aşağıdaki gibi bir dizin yapısı oluşacaktır:
training <DIR>
test <DIR>
cats.txt
Buradaki "cats.text" dosyası tüm yazıların kategorilerinin belirtildiği bir dosyadır. training dizininde ve test dizininide her bir yazı
birtext dosya biçiminde oluşturulmuştur. Buradaki text dosyaları okursak "latin-1" encoding'ini kullanmalısınız.
Aşağıdaki örnekte sırasıyla şunlar yapılmıştır:
- Önce cats.txt dosyası okunarak tüm dosyaların kategorileri (etiketleri) sözlük nesnelerinde saklanmıştır. Daha bu sözlğklerden tüm kategoriler
elde edimiştir.
- Sonra training ve test dizinlerindeki dosyalar tek tek okunarak birer listede toplanmış bu işlemler yapılırken bunların kategorileri de
bir listede toplanmıştır. Toplanan bu yazılar CountVectorizer sınıfı yardımıyla vektörizasyon işlemine sokulmuştur. Yazılara karşı gelen y değerleeri
de one hot encoding yapılmıştır.
- Eğitimde iki saklı katman kullanılmıştır. Çıktı katmanındaki aktivasyon fonksiyonu "softmax" alınmıştır.
- Modele önce 100 epoch uygulanmış belirgin bir overfitting görüldüğü iin eğitim yeniden 5 epoch'ta kesilmiştir.
- Kestirim için yine kestirilecek yazılar vektörizasyon işlemine sokulmuş ve predict metoduna verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import os
training_label_dict = {}
test_label_dict = {}
with open('ReutersData/cats.txt', encoding='latin-1') as f:
for line in f:
a = line.split()
label_type, pos = a[0].split('/')
pos = int(pos[pos.find('/') + 1:])
if label_type == 'training':
training_label_dict[pos] = a[1]
elif label_type == 'test':
test_label_dict[pos] = a[1]
all_labels_set = set(training_label_dict.values())
all_labels_set.update(test_label_dict.values())
all_labels_list = list(all_labels_set)
training_dataset_ylist = []
reuters_training_text = []
for fname in os.listdir('ReutersData/training'):
with open('ReutersData/training/' + fname) as f:
reuters_training_text.append(f.read())
val = training_label_dict[int(fname)]
index = all_labels_list.index(val)
training_dataset_ylist.append(index)
test_dataset_ylist = []
reuters_test_text = []
for fname in os.listdir('ReutersData/test'):
with open('ReutersData/test/'+ fname, encoding='latin-1') as f:
reuters_test_text.append(f.read())
val = test_label_dict[int(fname)]
index = all_labels_list.index(val)
test_dataset_ylist.append(index)
import numpy as np
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse=False, categories=[range(len(all_labels_list))])
training_dataset_y = ohe.fit_transform(np.array(training_dataset_ylist).reshape(-1, 1))
test_dataset_y = ohe.fit_transform(np.array(test_dataset_ylist).reshape(-1, 1))
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
cv.fit(reuters_training_text + reuters_test_text)
training_dataset_x = cv.transform(reuters_training_text).todense()
test_dataset_x = cv.transform(reuters_test_text).todense()
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Reuters')
model.add(Dense(64, activation='relu', input_dim=training_dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(len(all_labels_list), activation='softmax', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=5, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Categorical Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.plot(hist.epoch, hist.history['categorical_accuracy'])
plt.plot(hist.epoch, hist.history['val_categorical_accuracy'])
plt.legend(['Categorical Accuracy', 'Validation Categorical Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
texts = ['countries trade volume is expanding. Trade is very important for countries.', 'It is known that drinking a lot of coffee is harmful to health. However, according to some, a little coffee is beneficial.']
predict_data = cv.transform(texts).todense()
predict_result = model.predict(predict_data)
predict_result = np.argmax(predict_result, axis=1)
for pr in predict_result:
print(all_labels_list[pr])
#----------------------------------------------------------------------------------------------------------------------------
Aslında "Reuters" veri kümesi zaten tensorflow.keras.datasets paketi içerisinde hazır bir biçimde bulunmaktadır. Veri kğmesinin
yüklenmesi yine load_data fonksiyonuyla yapılabilir. Fonksiyonun num_words parametresi yine kelime haznesini belli bir sayıda tutmak
için kullanılabilir. Eğer bu parametreye giriş yapılmazsa bütün Reuters verileri kullanılacaktır. Bu modüldeki Reuters verilerinde toplam 46
farklı kategori vardır. Bu kategorilerin isimleri şöyledir:
category_list = ['cocoa','grain','veg-oil','earn','acq','wheat','copper','housing','money-supply',
'coffee','sugar','trade','reserves','ship','cotton','carcass','crude','nat-gas',
'cpi','money-fx','interest','gnp','meal-feed','alum','oilseed','gold','tin',
'strategic-metal','livestock','retail','ipi','iron-steel','rubber','heat','jobs',
'lei','bop','zinc','orange','pet-chem','dlr','gas','silver','wpi','hog','lead']
Tıpkı imdb modülünde olduğu gibi bu modülde de get_word_index isimli fonksiyon tüm sözüklerin numaralarını bize vermektedir. load_data verilen
x veri kümeleri de yine listelerden oluşmaktadır. Bu listeler yazıdaki sözcüklerin numaralarından oluşmaktadır. Tıpkı imdb modülünde olduğu gibi x
verilerindeki sayılar her zaman 3 fazla olarak kodlanmıştır.
Biz bu örnekte CountVectorizer sınıfını kullanamayız. Çünkü CountVectorizer sınıfı yazı dizilerinden vektörizasyon yamaktadır. Halbuki burada
bizim elimizde yazılar değil yazılara karşı helen sayılar vardır. Tabii biz sayıları aşağıdaki gibi yazılara dönüştürüp CountVectorizer sınıfını kullanabiliriz.
Ancak bu işlem yavaş olur. Şimdi sayılarla belirtilen bir yazıyı yazdıralım:
convert_text = lambda text_numbers: ' '.join([rev_word_dict[tn - 3] for tn in text_numbers if tn > 2])
print(convert_text(training_dataset_x[0]))
Şimdi vektörizasyon işlemini yapan bir fonksiyon yazalım:
def vectorize(sequence, colsize):
dataset_x = np.zeros((len(sequence), colsize), dtype='int8')
for index, vals in enumerate(sequence):
dataset_x[index, vals] = 1
return dataset_x
Aslında bu fonksiyon one hot encoding işlemini de yapabilecek yetenektedir.
Aşağıda tüm kod verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.datasets import reuters
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = reuters.load_data()
word_dict = reuters.get_word_index()
rev_word_dict = {value: key for key, value in word_dict.items()}
convert_text = lambda text_numbers: ' '.join([rev_word_dict[tn - 3] for tn in text_numbers if tn > 2])
print(convert_text(training_dataset_x[0]))
import numpy as np
def vectorize(sequence, colsize):
dataset_x = np.zeros((len(sequence), colsize), dtype='int8')
for index, vals in enumerate(sequence):
dataset_x[index, vals] = 1
return dataset_x
training_dataset_x = vectorize(training_dataset_x, len(word_dict) + 3)
test_dataset_x = vectorize(test_dataset_x, len(word_dict) + 3)
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse=False)
ohe_training_dataset_y = vectorize(training_dataset_y, 46)
ohe_test_dataset_y = vectorize(test_dataset_y, 46)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Reuters')
model.add(Dense(64, activation='relu', input_dim=training_dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(46, activation='softmax', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(training_dataset_x, ohe_training_dataset_y, batch_size=32, epochs=5, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Categorical Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.plot(hist.epoch, hist.history['categorical_accuracy'])
plt.plot(hist.epoch, hist.history['val_categorical_accuracy'])
plt.legend(['Categorical Accuracy', 'Validation Categorical Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, ohe_test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
texts = ['countries trade volume is expanding. Trade is very important for countries.', 'It is known that drinking a lot of coffee is harmful to health. However, according to some, a little coffee is beneficial.']
import regex
predict_data_list = []
for text in texts:
predict_data_list.append([word_dict[word] + 3 for word in regex.findall("[a-zA-Z!0-9']+", text.lower())])
predict_data = vectorize(predict_data_list, len(word_dict) + 3)
predict_result = model.predict(predict_data)
predict_result = np.argmax(predict_result, axis=1)
category_list = ['cocoa','grain','veg-oil','earn','acq','wheat','copper','housing','money-supply',
'coffee','sugar','trade','reserves','ship','cotton','carcass','crude','nat-gas',
'cpi','money-fx','interest','gnp','meal-feed','alum','oilseed','gold','tin',
'strategic-metal','livestock','retail','ipi','iron-steel','rubber','heat','jobs',
'lei','bop','zinc','orange','pet-chem','dlr','gas','silver','wpi','hog','lead']
for pr in predict_result:
print(category_list[pr])
#----------------------------------------------------------------------------------------------------------------------------
Çok büyük verilerle eğitim ve test işlemi sorunlu bir kondur. Çünkü örneğin fit işleminde büyük miktarda bir veri ile eğitim ve
test yapılırken bu veriler eldeki belleğe sığmayabilir. IMDB ya da Reuters örneklerinde vektörizasyon işlemi sonucunda çok büyük miktarda
matrisler oluşmaktadır. Gerçi bu matrislerin çok büyük kısmı 0'lardan oluşmaktadır. Bu tür matrislere "seyrek matrisler (sparse matrix)"
denilmektedir. Ancak fit metodu evaluate metodu ve predict metodu seyrek matrislerle çalışmamaktadır. İşte bu nedenden dolayı Keras'ta modeller
parçalı verilerle eğitilip test edilebilmektedir. Parçalı eğitim ve test işlemlerinde eğitim ve test sırasında her batch işleminde fit ve
evaluate metotları o anda bziden bir batch'lik verileri istemekte ve eğitim batch-bacth verilere tedarik edilerek yapılabilmektedir.
Parçalı eğitim ve test işlemi için fit ve evaluate metotlarının birinci parametrelerinde x verileri yerine bir üretici fonksiyon ya da
Sequence sınıfından türetilmiş olan bir sınıf nesnesi girilir. Biz önce üretici fonksiyon yoluyla sonra Sequence sınıfından türetilmiş olan
sınıf yoluyla işlemlerimizi yapacağız. Eskiden Keras'ta normal eğitim için fit isimli metot, parçalı eğitim için fit_generator isimli
metot kullanılıyordu. Ancak daha sonra bu fit_generator metodu kaldırıldı. Artık hem normak eğitimde hem de parçalı eğitimde fit metodu
kullanılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Üretici fonksiyon yoluyla parçalı eğitim yaparken fit metodunun birinci parametresine bir üretici fonksiyon nesnesi girilir. Artık
fit metodunun batch_size ve validation_split parametrelerinin bir önemi kalmaz. Çünkü batch miktarı zaten üretici fonksiyonden elde
edilen verilerle yapılmaktadır. Kaldı ki bu yöntemde aslında her batch işleminde aynı miktarda verinin kullanılması da zorunlu değildir.
Yine bu biçimde eğitim yapılırken validation_split parametresinin de bir anlamı yoktur. Çünkü sınama işlemi de yine parçalı verilerle
yapılmaktadır. Ancak bu yöntemde programcının iki parametreyi açıkça belirlemesi gerekir. Birincisi steps_per_epoch parametresidir. Bu parametre
bir epoch işleminin kaç batch işleminden oluşacağını belirtir. İkinci parametre epochs parametresidir. Bu da yine toplam epoch sayısını belirtir.
(Bu parametrenin default değeri 1'dir.) Bu durumda programcının üretici fonksiyon içerisinde "epochs * steps_per_epoch" kadar yield
uygulaması gerekir. Çünkü toplam batch sayısı bu kadardır. fit metodu aslında "epochs * steps_per_epoch" kadar işlemden sonra son kez bir daha
nect işlemi yapmaktadır. Böylece üretici fonksiyonun bitmesine yol açar.
Aşağıdaki toplam 100 epoch'tan oluşan her epoch'ta 20 tane batch işlemi yapılan ikili sınıflandırma örneği verilmiştir.
Ancak bu örnekte x ve y verileri üretici fonksiyonlardan elde edilmiştir. Üretici fonksiyon içerisinde toplam "epochs * steps_per_epoch"
kadar yield işlemi yapılmıştır. Ancak bu örnekte bir sınama işlemi yapılmamıştır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
EPOCHS = 100
STEPS_PER_EPOCH = 20
BATCH_SIZE = 32
def data_generator():
for _ in range(EPOCHS):
for _ in range(STEPS_PER_EPOCH):
x = np.random.random((BATCH_SIZE, 10))
y = np.random.randint(0, 2, (BATCH_SIZE, 1), dtype='int8')
yield x, y
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='PartialDataTraining')
model.add(Dense(64, activation='relu', input_dim=10, name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(data_generator(), epochs=EPOCHS, steps_per_epoch=STEPS_PER_EPOCH)
#----------------------------------------------------------------------------------------------------------------------------
Yukarıdaki örnekte bir sınama işlemi yapılmamaıştır. Sınama işlemi de üretici fonksiyon yoluyla yapılabilmektedir. Bunun için
yine bir üretici fonksiyon yazılır. Üretici fonksiyon her epoch için fit metodunun validation_steps parametresinde belirtilen miktarda
yield işlemi yaparak bizden sınama verilerini ister. Böylece her epoch'ta kullanılacak sınama verileri bizden üretici fonksiyon yoluyla
parça parça alınmış olur.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
EPOCHS = 10
STEPS_PER_EPOCH = 20
BATCH_SIZE = 32
VALIDATION_STEPS = 5
def data_generator():
for _ in range(EPOCHS):
for _ in range(STEPS_PER_EPOCH):
x = np.random.random((BATCH_SIZE, 10))
y = np.random.randint(0, 2, BATCH_SIZE, dtype='int8')
yield x, y
def validation_generator():
for _ in range(EPOCHS):
for _ in range(VALIDATION_STEPS + 1):
x = np.random.random((BATCH_SIZE, 10))
y = np.random.randint(0, 2, BATCH_SIZE, dtype='int8')
yield x, y
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='PartialDataTraining')
model.add(Dense(64, activation='relu', input_dim=10, name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(data_generator(), epochs=EPOCHS, steps_per_epoch=STEPS_PER_EPOCH, validation_data=validation_generator(), validation_steps=VALIDATION_STEPS)
#----------------------------------------------------------------------------------------------------------------------------
Parçalı eğitimin yanı sıra test işlemi de parçalı bir biçimde yapılabilmektedir. Bunun için evaluate metodunda test_dataset_x yerine
yine bir üretici fonksiyon girilir. evaluate metodunun steps parametresi bizden test verilerinin kaç parça olarak isteneceğini belirtir.
Yani programcının burada belirtilen sayıda yield işlemi yapması gerekir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
EPOCHS = 10
STEPS_PER_EPOCH = 20
BATCH_SIZE = 32
VALIDATION_STEPS = 5
TEST_STEPS = 10
def data_generator():
for _ in range(EPOCHS):
for _ in range(STEPS_PER_EPOCH):
x = np.random.random((BATCH_SIZE, 10))
y = np.random.randint(0, 2, BATCH_SIZE, dtype='int8')
yield x, y
def validation_generator():
for _ in range(EPOCHS):
for _ in range(VALIDATION_STEPS + 1):
x = np.random.random((BATCH_SIZE, 10))
y = np.random.randint(0, 2, BATCH_SIZE, dtype='int8')
yield x, y
def test_generator():
for _ in range(TEST_STEPS):
x = np.random.random((BATCH_SIZE, 10))
y = np.random.randint(0, 2, BATCH_SIZE, dtype='int8')
yield x, y
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='PartialDataTraining')
model.add(Dense(64, activation='relu', input_dim=10, name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(data_generator(), epochs=EPOCHS, steps_per_epoch=STEPS_PER_EPOCH, validation_data=validation_generator(), validation_steps=VALIDATION_STEPS)
eval_result = model.evaluate(test_generator(), steps=TEST_STEPS)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de gerçekten bellekte büyük bir yer kaplayan vektörizasyon işlemi gerektiren bir örneği parçalı bir biçimde eğitelim ve test
edelim. Aşağıda IMDB veri kümesi bu biçimde üretici fonksiyon yoluyla eğitilmiştir. Aşağıdaki örnekte tek bir üretici fonksiyon yazılmıştır.
Hem eğitim, hem sınama hem de test işlemlerinde aynı üretici fonksiyonb değişik parametrelerle çağrılmıştır.
Anımsanacağı gibi fit metodu default olarak her epoch'ta veri kümesini karıştırmaktadır. Ancak üretici fonksiyon yoluyla eğitim yaparken
fit metodu böyle bir karıştırmayı yapamamaktadır. O zaman bu karıştırmayı her epoch başlangıcında üretici fonksiyonun kendisinin yapması gerekir.
Büyük bir veri yapısının, Pandas DataFrame ve Series nesnelerinin ve Numpy dizilerinin karıştırılması göreli bir zaman kaybına yol açmaktadır.
Bu tür durumlarda asıl veri kümesini karıştırmak yerine bir indeks dizisi alıp onu karıştırmak ve asıl veri kümesine dolaylı bir biçimde
bu index dizisiyle erişmek daha uygun bir yöntemdir. Aşağıdaki örnekte eğitim veri kümesinin karıştırılmasında bu yöntem kullanılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
EPOCHS = 5
BATCH_SIZE = 32
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(dtype='int8')
cv.fit(df['review'])
df_x = df['review']
df_y = df['sentiment']
import numpy as np
dataset_y = np.zeros(len(df_y), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
from sklearn.model_selection import train_test_split
temp_df_x, test_df_x, temp_y, test_y = train_test_split(df_x, dataset_y, test_size=0.20)
training_df_x, validation_df_x, training_y, validation_y = train_test_split(temp_df_x, temp_y, test_size=0.20)
def data_generator(data_df_x, data_y, steps, shuffle=True):
indexes = list(range(steps))
for _ in range(EPOCHS):
if shuffle:
np.random.shuffle(indexes)
for i in range(steps):
start_index = indexes[i] * BATCH_SIZE;
stop_index = (indexes[i] + 1) * BATCH_SIZE
x = cv.transform(data_df_x.iloc[start_index:stop_index]).todense()
y = data_y[start_index:stop_index]
yield x, y
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='IMDB')
model.add(Dense(64, activation='relu', input_dim=len(cv.vocabulary_), name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
steps_per_epoch = len(training_df_x) // BATCH_SIZE;
steps_per_validation = len(validation_df_x) // BATCH_SIZE;
steps_per_test = len(test_df_x) // BATCH_SIZE;
hist = model.fit(data_generator(training_df_x, training_y, steps_per_epoch), steps_per_epoch=steps_per_epoch, validation_data=data_generator(validation_df_x, validation_y, steps_per_validation + 1, False), validation_steps=steps_per_validation, epochs=EPOCHS)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(data_generator(test_df_x, test_y, steps_per_test), steps=steps_per_test)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Parçalı biçimde eğitim, test ve kestirim işlemi yapmanın diğer bir yolu tensorflow.keras.utils modülü içerisindeki Sequence
sınıfından türetme yapmaktadır. Programcı türetmiş sınıf içerisinde __len__ ve __getiitem__ metotlarını yazar. fit metodu
bir epoch'un kaç tane batch içerdiğini tespit etmek için sınıfın __len__ metodunu çağırmaktadır. Daha sonra fit eğitim sırasında
her batch bilgiyi elde etmek için sınıfın __getitem__ metodunu çağırır. Bu metodu çağırırken batch numarasını metodun indeks
parametresine geçirir. Bu metottan programcı batch büyüklüğü kadar x ve y verisinden oluşan bir demetle geti dönmelidir.
Sonra programcı fit metodunın training_dataset_x parametresine bu sınıf türünden bir nesneyi girer.
Sınama işlemi yine benzer bir biçimde yapılmaktadır. Yani programcıo yine Sequence sınıfından sınıf türetip __len__ ve __getiitem__
metotlarını yazar. Tabii durumda her epoch sonrasında bu sınama verileri __getitem__ metodu yoluyla elde edilecektir.
Ayrıca Sequence sınıfından türetilmiş olan sınıfta on_epoch_end isimli bir metot da yazılabilmektedir. Eğitim sırasında her epoch bittiğinde
bu metot çağrılır. Programcı da tipik olarak bu metotta eğitim veri kümesibi karıştırır. Bu biçimdeki parçalı eğitimde artık fit metodunun
steps_per_epoch gibi bir parametresi kullanılmamaktadır. Zaten bu bilgi metot tarafından __len__ metodu çağrılarak elde edilmektedir
Benzer biçimde evaluate işleminde de yine Sequence sınıfından sınıf türetilerek test edilecek veriler parçalı bir biçimde evalaute metoduna
verilebilmektedir. Tabii bu metotta on_epoch_end metodunun bir anlamı yoktur. evaluate metodunda yine validation_steps parametresine gereksinim
duyulmamaktadır. Bu değer zaten __len__ metodundan elde edilmektedir.
Uygulamada parçalı verilerle eğitim işleminde aslında üretici fonksiyonlardan ziyada bu sınıfsal yöntem daha çok tercih edilmektedir.
Bu yöntemi uygulamak daha kolaydır. Ayrıca toplamda bu yöntem daha hızlı olma eğilimindedir.
Aşağıda rastgele verilerle bu işlemin nasıl yapılacağına yönelik bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
EPOCHS = 10
BATCH_SIZE = 32
from tensorflow.keras.utils import Sequence
class DataGenerator(Sequence):
def __init__(self, number_of_batches):
super().__init__()
self.number_of_batches = number_of_batches
def __len__(self):
return self.number_of_batches
def __getitem__(self, index):
x = np.random.random((BATCH_SIZE, 10))
y = np.random.randint(0, 2, BATCH_SIZE)
return x, y
def on_epoch_end(self):
print('on_epoch_end')
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='PartialDataTraining')
model.add(Dense(64, activation='relu', input_dim=10, name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(DataGenerator(5), epochs=EPOCHS, validation_data=DataGenerator(2))
eval_result = model.evaluate(DataGenerator(20))
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
Şimdi gerçekten parçalı verilerle eğitimin gerektiği IMDB örneğini üretici fonksiyonlar yerine bu yöntemi kullanarak gerçekleştirelim.
Aşağıdaki örnekte şu anahtar noktalara dikkat ediniz:
- DataGenerator isimli sınıf hem eğitimi hem sınama hem de test veri kğmesi için kullanılacapından dolayı __init__ metodunda
kullanılacak x ve y veri kümelerini alarak nesnenin özniteliklerinde saklamıştır.
- Bir epoch2un kaç tane batch işleminden oluşacağını fit metodu __len__ metodu yoluyla elde etmektedir. Dolayısıyla bu metotta
eğitim için gereken parça sayısını hesaplamaktayız.
- __getitem__ metodu her defasında epoch2un hangi batch'i için çağrılmışsa metodun indez parametresine o batch'in numarası
geçirilmiştir. Dolayısıyla bizim __getitem__ metodunda o batch'e ilişkin verileri vektörel hale getirip geri döndürmemiz
gerekir.
- Her epoch'tan sonra karıştırma işlemi yine asıl diziler üzerinde değil indekslerden oluşan dizi üzerinde yapılmıştır.
Karıştırma işlemi on_epoch_end metodunda yapılmaktadır.
- Sınama verileri için de aynı sınıf kullanılmıştır. Tabii sınama işlemind ebir epoch kavramı yoktur. __len__ metodu
sınama işleminin kaç parçada gerçekleşeceğini belirlemektedir.
- Test işlemi de benzer biçimde yapılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
EPOCHS = 5
BATCH_SIZE = 32
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(dtype='int8')
cv.fit(df['review'])
df_x = df['review']
df_y = df['sentiment']
import numpy as np
dataset_y = np.zeros(len(df_y), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
from sklearn.model_selection import train_test_split
temp_df_x, test_df_x, temp_y, test_y = train_test_split(df_x, dataset_y, test_size=0.20)
training_df_x, validation_df_x, training_y, validation_y = train_test_split(temp_df_x, temp_y, test_size=0.20)
from tensorflow.keras.utils import Sequence
class DataGenerator(Sequence):
def __init__(self, x_df, y, batch_size, shuffle=True):
super().__init__()
self.x_df = x_df
self.y = y
self.batch_size = batch_size
self.shuffle = shuffle
self.indexes = list(range(len(self.x_df) // self.batch_size))
def __len__(self):
return len(self.x_df) // self.batch_size
def __getitem__(self, index):
start_index = self.indexes[index] * self.batch_size;
stop_index = (self.indexes[index] + 1) * self.batch_size
x = cv.transform(self.x_df.iloc[start_index:stop_index]).todense()
y = self.y[start_index:stop_index]
return x, y
def on_epoch_end(self):
if self.shuffle:
np.random.shuffle(self.indexes)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='IMDB')
model.add(Dense(64, activation='relu', input_dim=len(cv.vocabulary_), name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(DataGenerator(training_df_x, training_y, BATCH_SIZE), validation_data=DataGenerator(validation_df_x, validation_y, BATCH_SIZE, False), epochs= EPOCHS)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(DataGenerator(test_df_x, test_y, BATCH_SIZE, False))
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
Elemanlarının çok büyük kısmı 0 olan matrislere "seyrek matrisler (sparse matrixes)" denilmektedir. Seyrek matrisleri ifade etmek
için alternatif birkaç veri yapısı kullanılmaktadır. Bunlardan biri DOK (Dictionary Of Keys) denilen veri yapısıdır.
Burada yalnızca 0'dan farklı olan elemanlar bir sözlükte tutulurlar. Sözlüğün biçimi aşağıdaki gibidir:
matrix = {(34674,17000): 1, (542001, 170): 4, ...}
Burada anahtar satır ve sütun numarasını belirten demettir. Değer ise o elemandaki değeri belirtir.
Böyle bir sınıfı temsili olarak aşağıdaki gibi yazabiliriz.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
class DokMatrix:
def __init__(self, shape):
self.shape = shape
self.matrix = {}
def __setitem__(self, index, value):
if not isinstance(index, tuple) or len(index) != 2:
raise IndexError('Invalid index')
if index[0] >= self.shape[0] or index[1] >= self.shape[1]:
raise IndexError('inde out of range')
self.matrix[index] = value
def __getitem__(self, index):
if not isinstance(index, tuple) or len(index) != 2:
raise IndexError('Iinvalid index')
if index[0] >= self.shape[0] or index[1] >= self.shape[1]:
raise IndexError('index out of range')
return self.matrix.get(index, 0)
def __repr__(self):
s = ''
for key, value in self.matrix.items():
if s != '':
s += '\n'
s += f'({key[0]},{key[1]}): {value}'
return s;
def todense(self):
result = np.zeros(self.shape)
for key, value in self.matrix.items():
result[key] = value
return result
dok = DokMatrix((10, 10))
dok[3, 5] = 10
dok[4, 6] = 20
print(dok[3, 5])
print(dok[4, 6])
print(dok[7, 7])
a = dok.todense()
print(a)
#----------------------------------------------------------------------------------------------------------------------------
DOK biçimindeki seyrek matrisler SciPy kütüphanesinde scipy.sparse modülü içerisinde dok_matrix sınıfıyla temsil edilmiştir.
Biz bir dok_marix sınıfı türünden nesneyi yalnızca boyut belirterek yaratbiliriz. Daha sonra bu nesne sanki bir NumPy dizisiymiş gibi
onu kullanabiliriz. Seyrek matrisi normal bir Numpy Dizisine dönüştürmek için sınıfın todense ya da toarray metotları kullanılabilir.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.sparse import dok_matrix
dok = dok_matrix((1000, 1000), dtype='int32')
dok[3, 6] = 100
dok[30, 7] = 200
print(dok[3, 6])
print(dok[30, 60])
print(dok)
a = dok.todense()
print(a)
print(dok)
#----------------------------------------------------------------------------------------------------------------------------
Bir dok_matrix nesnesi Python listelerinden ya da NumPy dizilerinden de oluşturulabilir.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.sparse import dok_matrix
dok = dok_matrix([[1, 0, 0], [1, 0, 0], [0, 0, 1]], dtype='int32')
print(dok)
#----------------------------------------------------------------------------------------------------------------------------
Bir seyrek matris nesnesi ile biz NumPy dizileri üzerinde yaptığımız işlemlerin benzerlerini yapabiliriz. Örneğin bir seyrek matrisi
dilimleyebiliriz. Bu durumda yine bir seyrek matris elde ederiz. dok_matrix sınıfının keys metodu yalnızca anahtarları, values metodu ise
yalnızca değerleri vermektedir.
Biz seyrek matrislerin karşılıklı elemanları üzerinde işlemler yapabiliriz. Ancak her türlü işlem değişik veri yapılarına sahip
seyrek matrislerde aynı verimlilikte yapılamamaktadır. Örneğin iki dok_matrix nesnesini toplayabiliriz ya da çarpabiliriz.
Ancak bu işlemler yavaş olma eğilimindedir.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.sparse import dok_matrix
import numpy as np
a = np.random.randint(0, 2, (10, 10))
b = np.random.randint(0, 2, (10, 10))
dok1 = dok_matrix(a, dtype='float32')
dok2 = dok_matrix(b, dtype='float32')
dok3 = dok1 + dok2
print(dok3)
dok3 = dok1 * dok2
print(dok3)
#----------------------------------------------------------------------------------------------------------------------------
Diğer bir seyrek matris veri yapısı da "LIL (List of List)" denilen veri yapısıdır. Bu veri yapısında matrisin satır satır
0 olmayan elemanları ayrı listelerde tutulur. Başka bir listede de bu sıfır olmayan elemanların sütunlarının indeksi tutulmaktadır.
LIL matrisler SciPy kütüphanesinde scipy.sparse modülünde lil_matrix sınıfyla temsil edilmektedir. Bu sınıfın geenel kullanımı dok_matrix sınıfında
olduğu gibidir.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.sparse import lil_matrix
import numpy as np
lil1 = lil_matrix((100, 100), dtype='float32')
lil1[30, 32] = 10
lil1[35, 2] = 20
lil1[38, 27] = 30
lil1[36, 42] = 40
lil1[55, 21] = 50
print(lil1)
lil2 = lil_matrix(np.random.randint(0, 2, (100, 100)))
lil3 = lil1 + lil2
print(lil3)
#----------------------------------------------------------------------------------------------------------------------------
lil_matrix sınıfının data örnek özniteliği satırlardaki sıfırdan farklı elemanları, rows örnek özniteliği ise sütun indekslerini vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.sparse import lil_matrix
a = [[0, 0, 10, 0, 5], [12, 0, 11, 0, 7], [0, 0, 0, 0, 0], [0, 0, 5, 0, 9], [0, 0, 0, 0, 0]]
lil = lil_matrix(a)
print(a)
print(f'data={lil.data}') # data=[list([10, 5]) list([12, 11, 7]) list([]) list([5, 9]) list([])]
print(f'rows={lil.rows}') # rows=[list([2, 4]) list([0, 2, 4]) list([]) list([2, 4]) list([])]
#----------------------------------------------------------------------------------------------------------------------------
Aslında uygulamada DOK ve LIL matrisler seyrek kullanılmaktadır. Daha çok CSR ve CSC veri yapıları tercih edilmektedir.
CSR (Compressed Sparse Row), ve CSC (Compress Sparse Column) matrisleri genel veri yapısı olarak birbirlerine çok benzemektedir.
Bu ver yapısı setrek matrislerin karşılıklı elemanlarının işleme sokulması durumunda diğer veri yapılarına göre daha avantajlıdır.
CSR fotmatı satırsla dilimlerde, CSC formatı ise sütunsal dilimlemelerde daha hızlı sonuç veröektedir .
CSR matrislerde sıfırdan farklı elemanları üç dizi (liste) haline tutulmaktadır: data, indices, indptr. data dizisi sıfır olmayan elemanların
tutulduğu tek boyutlu dizidir. indices dizisi data dizisindeki, elemanların matristekü sütun indekslerinden oluşur. indptr ise
sıfır olmayan elemanların hangi satırlarda olduğuna ilişkin ilk ve son indeks (ilk indeks dahil, son indeks dahil değil) değerlerinden oluşmaktadır.
Bu veri yapısı da SciPy kütüphanesinde scipy.sparse modülünde csr_matrix sınıfıyla temsil edilmektedir. Örneğin:
0, 0, 9, 0, 5
8, 0, 3, 0, 7
0, 0, 0, 0, 0
0, 0, 5, 0, 9
0, 0, 0, 0, 0
Burada söz konusu üç dizi şöyledir:
data: [9, 5, 8, 3, 7, 5, 9]
indices: [2, 4, 0, 2, 4, 2, 4]
indptr: [0, 2, 5, 5, 7, 7]
csr_matrix sınıfının genel kullanımı diğer seyrek matris sınıflarındaki gibidir. Ancak CSR ce CSC matrislerde sıfır olan bir elemana
atama yapmak yavaş bir işlemdir. Çünkü bu işlemler yukarıda belirtilen üç dizide kaydırmalara yol açmaktadır. Bu tür durumlarda DOK ya da LIL
matrisler daha hızlı işleme yol açmaktadır. Bu nedenle bu matrisler kullanılırken sıfır olmayan bir elemena atama yapıldığında bir uyarı
mesajıyla karşılaşabilirsiniz. O halde CSR ve CSC matrisleri iin başında oluşturulmalı ve sonra da onların elemanları bir daha değiştirilmemelidir.
CSR matrislerinde satırsal, CSC matrislerinde sütunsal dilimlemeler hızlıdır. Aynı zamanda bu iki matrisin karşılıklı elemanları üzerinde hızlı işlemler
yapılabilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.sparse import csr_matrix
import numpy as np
a = np.zeros((100, 100), dtype='float32')
for _ in range(100):
row = np.random.randint(0, 100, 1)
col = np.random.randint(0, 100, 1)
a[row, col] = np.random.randint(0, 100, 1)
csr = csr_matrix(a, dtype='float32')
print(csr)
csr2 = csr[2:4, :]
print(csr2)
#----------------------------------------------------------------------------------------------------------------------------
csr matrislerin data, indices ve indptr örnek öznitelikleri yukarıda açıklanan matris bilgilerini bize vermeketedir.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.sparse import csr_matrix
a = [[0, 0, 9, 0, 5], [8, 0, 3, 0, 7], [0, 0, 0, 0, 0], [0, 0, 5, 0, 9], [0, 0, 0, 0, 0]]
csr = csr_matrix(a)
print(csr.todense(), end='\n\n')
print(f'data: {csr.data}') # data: [9 5 8 3 7 5 9]
print(f'indices: {csr.indices}') # indices: [2 4 0 2 4 2 4]
print(f'indptr: {csr.indptr}') # indptr: [0 2 5 5 7 7]
#----------------------------------------------------------------------------------------------------------------------------
CSC formatı aslında CSR formatına çok benzerdir. Bu iki format arasındaki tek fark CSR formatında satır indeksleri tutulurken,
CSC formatında sütun indekslerinin tutulmasıdır. Yani yapılan ilemlerin hepsi satır-sütun temelinde terstir. Örneğin:
0, 0, 9, 0, 5
8, 0, 3, 0, 7
0, 0, 0, 0, 0
0, 0, 5, 0, 9
0, 0, 0, 0, 0
data: [8, 9, 3, 5, 5, 7, 9]
indices: [1, 0, 1, 3, 0, 1, 3]
indptr: [0, 1, 1, 4, 4, 7]
#----------------------------------------------------------------------------------------------------------------------------
from scipy.sparse import csc_matrix
a = [[0, 0, 9, 0, 5], [8, 0, 3, 0, 7], [0, 0, 0, 0, 0], [0, 0, 5, 0, 9], [0, 0, 0, 0, 0]]
csc = csc_matrix(a)
print(csc.todense(), end='\n\n')
print(f'data: {csc.data}') # data: [8 9 3 5 5 7 9]
print(f'indices: {csc.indices}') # indices: [1 0 1 3 0 1 3]
print(f'indptr: {csc.indptr}') # indptr: [0 1 1 4 4 7]
#----------------------------------------------------------------------------------------------------------------------------
from scipy.sparse import csc_matrix
a = [[0, 0, 9, 0, 5], [8, 0, 3, 0, 7], [0, 0, 0, 0, 0], [0, 0, 5, 0, 9], [0, 0, 0, 0, 0]]
csc = csc_matrix(a)
print(csc.todense(), end='\n\n')
print(f'data: {csc.data}') # data: [8 9 3 5 5 7 9]
print(f'indices: {csc.indices}') # indices: [1 0 1 3 0 1 3]
print(f'indptr: {csc.indptr}') # indptr: [0 1 1 4 4 7]
#----------------------------------------------------------------------------------------------------------------------------
Yukarıdaki seyrek matris sınıflarının her birinde toxxx biçiminde diğer seyrek matris sınıflarına dönüştürme yapan metotlar vardır.
Yani örneğin elimizde bir csr_matrix nesnesi varsa biz bu sınıfın tolil metoduyla bunu bir lil_matris nesnesine dönüştürebiliriz.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.sparse import csc_matrix
a = [[0, 0, 9, 0, 5], [8, 0, 3, 0, 7], [0, 0, 0, 0, 0], [0, 0, 5, 0, 9], [0, 0, 0, 0, 0]]
csc = csc_matrix(a)
lil = csc.tolil()
print(lil)
#----------------------------------------------------------------------------------------------------------------------------
Seyrek bir matris train_test_split fonksiyonuyla ayrıştırılabilir. Çünkü zaten train_test_split fonksiyonu dilimleme yoluyla
işlemlerini yapmaktadır. Ancak seyrek matrislere len fonksiyonu uygulanamaz. Ancak seyrek matrislerin boyutları yine shape
örnek çzniteliği ile elde edilebilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Seyrek matrislerin birbirlerine göre avantaj ve dezavantajları şöyle özetlenebilir:
- DOK matriste elemanlara okuma ya da yazma amaçlı erişim hızlı bir biçimde gerçekleştirilmektedir. Ancak DOK matrisler matris işlemlerinde çok etkin değildir. DOK matrisler dilimleme de de etkin değillerdir.
- LIL matrisler de okuma amaçlı eleman erişimlerinde ve satırsal dilimlemelerde hızlıdırlar. Ancak sütunsal dilimlemelerde ve matris işlemlerinde yavaştırlar. 0 olan elemanlara yazma amaçlı erişimlerde çok hızlı olmasalar da yavaş değillerdir. Bu matrislerin matris işlemleri için CSR ve CSC formatlarına dönüştürülmesi uygundur ve bu dönüştürme hızlıdır.
- CSR matrisler satırsal dilimlemelerde CSC matrisler ise sütunsal dilimlemelerde hızlıdırlar. Ancak CSR sütünsal, CSC de satırsal dilimlemelerde yavaştır. Her iki matris de matris işlemlerinde hızlıdır. Bu matrislerde elemanların değerlerini değiştirmek (özellikle 0 olan elemanların) yavaştır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aslında biz daha önce bazı konularda seyrek matris kavramıyla karşılaşmıştık. Örneğin scikit-learn içerisindeki OneHotEncoder
sınıfının sparse parametresi False geçilmezse bu sınıf bize transform işleminde CSR tarzı seyrek matris vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
from sklearn.preprocessing import OneHotEncoder
import numpy as np
a = np.array(['Mavi', 'Yeşil', 'Kırmızı', 'Mavi', 'Kırmızı', 'Mavi', 'Yeşil'])
ohe = OneHotEncoder()
result = ohe.fit_transform(a.reshape(-1, 1))
print(result)
print(result.todense())
#----------------------------------------------------------------------------------------------------------------------------
Benzer biçimde scikit-learn içerisindeki CountVectorizer sınıfı da aslında bize CSR tarzı bir seyrek matris veriyordu.
#----------------------------------------------------------------------------------------------------------------------------
from sklearn.feature_extraction.text import CountVectorizer
text = ['this is a test', 'this is a book', 'yes it is', 'it is a book']
cv = CountVectorizer()
result = cv.fit_transform(text)
print(result)
print(result.todense())
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de seyrek matrisler için gerçek bir uygulama yapalım. Daha önce yapmış olduğumuz IMDB örneğinde biz vektörizasyonu
parça parça yapmıştık. Şimdi de tüm verilerin vektörizasyonunu tek hamlede yapıp parçalı eğitim sırasında onların ilgili kısımlarını
todense metodu ile dense matris hale getirelim. Yani biz örneğimizde IMDB verilerini bütünsel olarak vektörize edeceğiz. Ancak parçalı
eğitim sırasında ilgili kısmı todense metodu ile dense hale hale getireceğiz. Biz daha önce tüm IMDB verilerini tek hamlede CountVectorizer ile
transform yapmamıştık. Hep parçalı eğitim sırasında ilgili kısmı transform yapmıştık. Halbuki şimde tüm verileri tek hamlede transform yapıp
parçalı eğitim sırasında todense işlemini yapacağız.
#----------------------------------------------------------------------------------------------------------------------------
EPOCHS = 5
BATCH_SIZE = 32
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(dtype='int8')
vectorized_dataset_x = cv.fit_transform(df['review'])
df_y = df['sentiment']
import numpy as np
dataset_y = np.zeros(len(df_y), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
from sklearn.model_selection import train_test_split
temp_vectorized_x, test_x, temp_y, test_y = train_test_split(vectorized_dataset_x, dataset_y, test_size=0.20)
training_x, validation_x, training_y, validation_y = train_test_split(temp_vectorized_x, temp_y, test_size=0.20)
from tensorflow.keras.utils import Sequence
class DataGenerator(Sequence):
def __init__(self, vectorized_x, y, batch_size, shuffle=True):
super().__init__()
self.vectorized_x = vectorized_x
self.y = y
self.batch_size = batch_size
self.shuffle = shuffle
self.indexes = list(range(vectorized_x.shape[0] // self.batch_size))
def __len__(self):
return self.vectorized_x.shape[0] // self.batch_size
def __getitem__(self, index):
start_index = self.indexes[index] * self.batch_size;
stop_index = (self.indexes[index] + 1) * self.batch_size
x = self.vectorized_x[start_index:stop_index].todense()
y = self.y[start_index:stop_index]
return x, y
def on_epoch_end(self):
if self.shuffle:
np.random.shuffle(self.indexes)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='IMDB')
model.add(Dense(64, activation='relu', input_dim=len(cv.vocabulary_), name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(DataGenerator(training_x, training_y, BATCH_SIZE), validation_data=DataGenerator(validation_x, validation_y, BATCH_SIZE, False), epochs= EPOCHS)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(DataGenerator(test_x, test_y, BATCH_SIZE, False))
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Resimlerle ilgili işlemler yapmaan önce resimler hakkında bazı bilgilerin edinilmiş olması gerekmektedir. Bilgisayar ekranlarında
tüm görüntü aslında pixel'lerden oluşmaktadır. Pixel ("picture element" sözcüklerindne uydurulmuştur) bir ekranda görüntülenebilen en küçük öğedir.
Yani ekrandaki her şey aslında pixel'lerin bir araya getirilmesiyle oluşturulmaktadır. Bizim resim dediğimiz şey de aslında bir grup pixel'den oluşur.
Tabii her pixel'in bir rengi de vardır. Bugün kullandığımız bilgisayarlarda her pixel "kırmızı (red), yeşil (green) ve mavinin (blue)" bir byte'lık
tonal birleşimleriyle ifade edilmektedir. Dolayısıyla toplam renk sayısı 2 ** 24 yani 16 milyon civarındadır. Örneğin RGB byte'larının hepsi 0 olursa
o pixel siyah görünüt, hepsi 255 olursa o pixel beyaz görünür. Tüm renkler bu üç rengin girişimi ile elde edilebilmektedir.
Bilgisayar ortamındaki en doğal resim formatları "bitmap (ya da rester)" formatlardır. Örneğin BMP, PNG gibi formatlar bitmap
formatlardır. Bu formatlarda resmin her bir pixel'inin rengi dosya içerisinde saklanır. Dolayısıyla resmi görüntüleyecek kişinin
tek yapacağı şey o pixel'leri o renklerde görüntülemektedir. Ancak bitmap formatlar çok yer kaplama eğilimindedir. Örneğin 100x100 pixel!lik
bir resim kabaca 100 * 100 * 3 byte yer kaplar. Bu yüzden resimler üzerinde kayıplı sıkıştırma yöntemleri oluşturulmuştur. Örneğin JPEG formatı
aslında bir bakıma kayıplı sıkıştırma uygulanmış bir formattır. Yani bir BMP resmi JPG formatına dönüştürdüğümüzde resim çok bozulmaz. Ama aslında biraz
bozulur. Sonra onu yeniden BMP'ye dönüştürdüğümüzde aynı resmi elde etmeyiz. Ancak JPEG formatı resimleri çok az bozup çok iyi sıkıştıran bir formattır.
O halde aslında doğal format BMP formaı ve benzerleridir. JPEG gibi formatlar doğal formatlar değildir. Sıkıştırılmış formatlardır.
Bugünkü bilgisayar sistemlerinde arka planda bir görüntü varken onun önüne bir görüntü getrilip arkadaki görüntü adeta bir tül perdeden görünüyormuş
etkisi yaratılabilmektedir. Bu etkiyi sağlamak için her pixel'de ilave bir "alpha channel" denilen byte tutulur. Bölece pixel 3 byte değil 4 byte ile
ifade edilir. Örneğin PNG dosya formatı bu tarz bir transparanlık bilgisini de tutmaktadır. Alpha channel 255 ise tam saydamsızlık oluşur. 0 ise tam saydamlılık oluşmaktadır.
Siyah-Beyaz resim demek pixel'leri tam siyah ya da tam beyaz resim demektir. Siyah-Beyaz resimlerde her pixel 1 bit ile tutulabildiğinden dolayı çok az yer kaplamaktadır.
Ancak siyah-beyaz resimler algılanması zor resimlerdir. "Gri tonlamalı (gray scale)" resim grinin tonlarını gösterebilen ancak renk gösteremeyen resimlerdir.
Gri tonlamalı resimlerde her pixel'in Reg, Green, Blue değerleri aynıdır. Bu durumda gri tonlamalı resimlerde her pixel 1 byte ile tutulmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Matplotlib kütüphanesinde bir resmi resim dosyasından (JEPG, BMP, PNG vs.) okuyarak onun pixel'lerini elde edip bize bir NumPy
dizisi biçiminde veren imread isimli bir fonksiyon vardır. Biz bir resim dosyasını imread ile okuduğumuzda artık o resmin saf pixel değerlerini
elde ederiz. Örneğin:
import matplotlib.pyplot as plt
image_data = plt.imread('AbbeyRoad.jpg')
Bu örnekte biz bir NumPy dizisi elde etmiş olduk. Bu dizinin shape demeti (300, 300, 3) biçiminde elde edilmiştir. Yani söz konusu resim
300x300 pixel'lik bir resimdir. Ancak her pixel'i RGB olan 3 bileşenden oluşmaktadır.
Matplotlib kütüphanesinin imshow isimli fonksiyonu pixel bilgilerini alarak resmi görüntüler. Tabii imshow resmi orijinal boyutuyla görüntülememektedir.
imshow resmi ölçeklendirip figür büyüklüğünde görüntülemektedir.
Matplotlib bir resim üzerinde ondan parça almak, onu büyütmrk, küçültmek gibi işlemler için uygun değildir. Bu tür işlemler için
Python programcıları başka kütüphanelerden faydalamaktadır. Örneğin bu bağlamda en yaygın kullanılan kütüphane "Python Image Library (PIL ya da Pillow diye kısaltılmaktadır)" isimli kütüphanedir. Matplotlib yalnızca resim dosyalarını okuyup bize pixel'lerini verir ve pixel'leri verilmiş resmi görüntüler.
#----------------------------------------------------------------------------------------------------------------------------
import matplotlib.pyplot as plt
image_data = plt.imread('AbbeyRoad.jpg')
plt.imshow(image_data)
plt.show()
print(image_data.shape)
#----------------------------------------------------------------------------------------------------------------------------
Örneğin biz bir resmi ters çevirmek için resmin tüm satırlarını ters yüz etmemiz gerekir. Bu işlemi aslında NumPy'ın flip fonksiyonu
pratik bir biçimde yapmaktadır. Bu biçimde bir resmin pixel'leri üzerinde aşağı seviyeli çalışma yapmak için Matplotlib ve NumPy
iyi araçlardır.
#----------------------------------------------------------------------------------------------------------------------------
import matplotlib.pyplot as plt
image_data = plt.imread('AbbeyRoad.jpg')
plt.imshow(image_data)
plt.show()
import numpy as np
flipped_image_data = np.flip(image_data, 0)
plt.imshow(flipped_image_data)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Renkli resimler imread fonksiyonu ile okunduğunda row x col x 3 boyutunda olurlar. Gri tonlamalı resimler ise row x col ya da row x col x 1
boyutunda olurlar. Tabii rol x col ile row x col x 1 arasında bir boyut farkı yoktur. Dolayısıyla gri tonlamalı resimler
boyut olarak karşımıza row x col biçiminde de row x col x 1 biçiminde de çıkabilmektedir. Şüphesiz gri tonlamalı bir resim karşımıza
row x col x 3 boyutlarında da çıkabilir. Bu durumda bu durumda axis=2 eksenindeki RGB değerleri aynı olur. Tabii gri tonlamalı resmin bu biçimde
saklanması gereksiz bir yer kaplar.
Pekiyi renkli bir resmi gri tonlamalı bir resim haline nasıl getirebiliriz? Bunun için en basit yöntem her pixel'in RGB renklerinin
ortalamasını almaktır. Bunu basit bir biçimde np.mean fonksiyonunda axis=2 parametresi vererek sağlayabiliriz. Örneğin:
import matplotlib.pyplot as plt
image_data = plt.imread('AbbeyRoad.jpg')
import numpy as np
gray_scaled_image_data = np.mean(image_data, axis=2)
#----------------------------------------------------------------------------------------------------------------------------
import matplotlib.pyplot as plt
image_data = plt.imread('AbbeyRoad.jpg')
import numpy as np
gray_scaled_image_data = np.mean(image_data, axis=2)
plt.imshow(gray_scaled_image_data, cmap='gray')
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
RGB bir resmi yukardaki gibi gri tonlamalı hale getirirken açık renklerin etkisi fazlalaşmaktadır. Bu nedenle çoğu kez
ırlıklı ortalama alma yolu izlenir. Tipik olarak ağırlıklar R=0.3, G=0.59, B=0.11 biçiminde alınmaktadır. Aşağıda bu ağırlıklarla
res,m gri tonlamalı hale getirilmiştir. (NumPy'ın mean fonksiyonunda ağırlıklandırma parametresi yoktur. average isimli fonksiyon ağırlıklı ortalama
için kullanılmaktadır.)
#----------------------------------------------------------------------------------------------------------------------------
import matplotlib.pyplot as plt
image_data = plt.imread('AbbeyRoad.jpg')
import numpy as np
gray_scaled_image_data = np.average(image_data, axis=2, weights=[0.3, 0.59, 0.11])
plt.imshow(gray_scaled_image_data, cmap='gray')
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Resim tanıma üzerinde en sık kullanılan popüler bir veri kğmesi MNIST (Modified National Institute of Standards and Technology)
denilen veri kümesidir. Bu veri kümesinde her biri 28x28 pixel'den oluşan gri tonlamalı resimler vardır. Bu resimler çeşitli kişilerin
0 ile 9 arasındaki sayıları elle çizmesiyle oluşturulmuştur. Veri kümesi resmin üzerindeki sayının kestirilmesi gibi resim tanıma uygulamalarında
kullanılmaktadır. Veri kümesinde toplam 60000 tane resim bulunmaktadır. Veri kümesini CSV dosyası olarak aşağıdaki bağlantıdan indirebilrisiniz:
https://www.kaggle.com/oddrationale/mnist-in-csv
Buradan minist_train.csv ve mnist_test.csv dosyaları elde edilmektedir.
Aşağıdaki örnekte MNIST verileri dosyadan okunmuş ve iki saklı katmanlı bir sinir ağı ile model oluşturulmuştur.
Model test edildiğinde %97 civarında bir başarı elde edilmektedir. Daha sonra 28x28'lik kendi oluşturduğumuz rakamlarla kestirim işlemi
yapılmıştır. Tabii kestirim işlemi eğitim verileriyle aynı biçimde oluşturulmuş rakamlarla yapılmalıdır. Eğitim verilerinde "anti-aliasing"
kullanılmıştır. Biz de Microsoft Paint ile fırça kullnarak anti-aliasing eşliğinde kestirilecek resimleri oluşturkduk.
Pixel verileri üzerinde eğitime sokulmadan önce Min-Max ölçeklemesi yapılmıştır. Tabii [0, 255] arasındaki verilerde Min-Max ölçeklemesi aslında
bu pixel verilerinin 255'e bölümüyle oluşturulabilmektedir. Modele çok fazla epoch uygulandığında "overfitting" problemi ortaya çıkmaktadır.
Bu nedenle epoch sayısı 20 olarak belirlenmiştir. 28x28'lik pixel verileri bir matris biçiminde değil 784 elemanlı bir girdi biçiminde uygulanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df_training = pd.read_csv('mnist_train.csv')
df_test = pd.read_csv('mnist_test.csv')
training_dataset_x = df_training.iloc[:, 1:].to_numpy()
training_dataset_y = df_training.iloc[:, 0].to_numpy()
test_dataset_x = df_test.iloc[:, 1:].to_numpy()
test_dataset_y = df_test.iloc[:, 0].to_numpy()
import matplotlib.pyplot as plt
plt.figure(figsize=(20, 40))
for i in range(50):
plt.subplot(10, 5, i + 1)
plt.title(str(training_dataset_y[i]), fontsize=14)
plt.imshow(training_dataset_x[i].reshape(28, 28), cmap='gray')
plt.show()
from tensorflow.keras.utils import to_categorical
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
training_dataset_x = training_dataset_x / 255
test_dataset_x = test_dataset_x / 255
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='MNIST')
model.add(Dense(256, activation='relu', input_dim=784, name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(training_dataset_x, ohe_training_dataset_y, epochs=20, batch_size=32, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Categorical Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['categorical_accuracy'])
plt.plot(hist.epoch, hist.history['val_categorical_accuracy'])
plt.legend(['Categorical Accuracy', 'Validation Categorical Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, ohe_test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
import glob
for path in glob.glob('test-images/*.jpg'):
image_data = plt.imread(path)
gray_scaled_image_data = np.average(image_data, axis=2, weights=[0.3, 0.59, 0.11])
gray_scaled_image_data = gray_scaled_image_data / 255
predict_result = model.predict(gray_scaled_image_data.reshape(1, 784))
result = np.argmax(predict_result)
print(f'{path}: {result}')
"""
import itertools
for x in itertools.islice(training_dataset_x[training_dataset_y == 6], 10):
plt.imshow(x.reshape(28, 28), cmap='gray')
plt.show()
"""
#----------------------------------------------------------------------------------------------------------------------------
Aslında MNIST verileri tensorflow.keras.datasets paketindeki mnist isimli modülde de bulunmaktadır. Modülün load_data fonksiyonu
bize pixel verilerini üç boyutlu bir NumPy dizisi olarak vermektedir. Bizim bu verileri reshape ile 784'lük iki boyutlu matrise
dönüştürmemiz gerekir.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.datasets import mnist
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = mnist.load_data()
import matplotlib.pyplot as plt
plt.figure(figsize=(20, 40))
for i in range(50):
plt.subplot(10, 5, i + 1)
plt.title(str(training_dataset_y[i]), fontsize=14)
plt.imshow(training_dataset_x[i], cmap='gray')
plt.show()
from tensorflow.keras.utils import to_categorical
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
training_dataset_x = training_dataset_x.reshape(-1, 784) / 255
test_dataset_x = test_dataset_x.reshape(-1, 784) / 255
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='MNIST')
model.add(Dense(256, activation='relu', input_dim=784, name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(training_dataset_x, ohe_training_dataset_y, epochs=20, batch_size=32, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Categorical Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['categorical_accuracy'])
plt.plot(hist.epoch, hist.history['val_categorical_accuracy'])
plt.legend(['Categorical Accuracy', 'Validation Categorical Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, ohe_test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
import glob
for path in glob.glob('test-images/*.jpg'):
image_data = plt.imread(path)
gray_scaled_image_data = np.average(image_data, axis=2, weights=[0.3, 0.59, 0.11])
gray_scaled_image_data = gray_scaled_image_data / 255
predict_result = model.predict(gray_scaled_image_data.reshape(1, 784))
result = np.argmax(predict_result)
print(f'{path}: {result}')
"""
import itertools
for x in itertools.islice(training_dataset_x[training_dataset_y == 6], 10):
plt.imshow(x.reshape(28, 28), cmap='gray')
plt.show()
"""
#----------------------------------------------------------------------------------------------------------------------------
Evrişim (convolution) genel olarak "sayısal işaret işleme (digital signal processing)" faaliyetlerinde kullanılan bir tekniktir.
Bir verinin başka bir veriyle girişime sokulması anlamına gelir. Evrişim en çok görüntü verileri üzerinde kullanılmaktadır. Ancak görüntünün
dışında işitsel (audio) ve hareketli görüntüler (video) verileri üzerinde de sıkça uygulanmaktadır. Evrişim işleminin yapay sinir ağlarında kullanılması durumuna
"Evrişimsel Sinir Ağları (Convolutional Neural Network)" denilmektedir ve İngilizce CNN biçiminde kısaltılmaktadır.
Bir resmim evrişim işlemine sokulması için elimizde bir resim ve bir küçük matrisin olması gerekir. Bu küçük matrise "filtre (filter)"
ya da "kernel" denilmektedir. Kernel herhangi bir boyutta olabilir. Kare bir matris biçiminde olması gerekmez. Ancak uygulamada NxN'lik
kare matrisler kullanılmaktadır ve genellikle buradaki N değeri 3, 5, 7, 9 gibi tek sayı olmaktadır. Evrişim işlemi şöyle yapılmaktadır:
Kernel resmin sol üst köşesi ile çakıştırılır. Sonra resmin arkada kalan kısmıyla dot-product işlemine sokulur. Buradan bir değer elde edilir.
Sonra kernel resim üzerinde kaydırılır ve aynı işlem yine yapılır. Kaydırma sağa doğru ve sonra da aşağıya doğru yapılır.
Böylece evrişim işlemi sonucunda başka bir resim elde edilmiştir. Örneğin 5x5'lik bir resim olsun:
a11 a12 a13 a14 a15
a21 a22 a23 a24 a25
a31 a32 a33 a34 a35
a41 a42 a43 a44 a45
a51 a52 a53 a54 a55
Kullandığımız kernekl da 3x3'lük olsun:
b11 b12 b13
b21 b22 b23
b31 b32 b33
Bu kernel resmin sol üst köşesi ile çakıştırılıp dot product uygulanırsa şöyle bir değer elde edilir:
c11 = b11 * a11 + b12 * a12 + b13 * a13 + b21 * a21 + b22 * a22 + b23 * a23 + b31 * a31 + b32 * a32 + b33 * a33
Şimdi kernel'ı bir sağa kaydırıp aynı işlemi yapalım:
c12 = b11 * a12 + b12 * a13 + b13 * a14 + b21 * a22 + b22 * a23 + b23 * a24 + b31 * a32 + b32 * a32 + b33 * a34
Şimdi kernel'ı bir sağa daha kaydıralım:
c13 = b11 * a13 + b12 * a14 + b13 * a15 + b21 * a23 + b22 * a24 + b23 * a25 + b31 * a33 + b32 * a34 + b33 * a35
Şimdi kernel'ı aşağı kaydıralım:
c21 = b11 * a21 + b12 * a22 + b13 * a23 + b21 * a31 + b22 * a32 + b23 * a33 + b31 * a41 + b32 * a42 + b33 * a43
İşte bu biçimde işlemlere devam edersek aşağıdkai gibi bir C matrisi (resmi) elde ederiz:
c11 c12 c13
c21 c22 c23
c31 c32 c33
Eğer işlemle ryukarıdaki gibi yapılırsa hedef olarak elde edilecek resmin genişlik ve yüksekliği şöyle olur:
Hedef Resmin Genişliği = Asıl Resmin Genişliği - Kernel Genişliği + 1
Hedef Resmin Yüksekliği = Asıl Resmin Yüksekliği - Kernel Yüksekliği + 1
Görüldüğü gibi hedef resim asıl resimden küçük olmaktadır. Eğer biz hedef resmin asıl resimle aynı büyüklükte olmasını istersek
asıl resmin sağına ve aşağısına eklemeler yaparız. Bu eklemelere "padding" denilmektedir. Pekiyi sağa ve aşağıya ne kadar padding yapılmalıdır?
Tabii ki (kernel genişliği ya da yükseliği - 1) kadar:
Padding Genişliği = Kernel Genişliği - 1
Padding Yüksekliği = Kernel Yüksekliği - 1
Örneğin 5x5'lik resme 3x3'lük kernel uygulamak isteyelim:
a11 a12 a13 a14 a15
a21 a22 a23 a24 a25
a31 a32 a33 a34 a35
a41 a42 a43 a44 a45
a51 a52 a53 a54 a55
Padding şu biçimde görünecektir:
a11 a12 a13 a14 a15 p16 p17
a21 a22 a23 a24 a25 p26 p27
a31 a32 a33 a34 a35 p36 p37
a41 a42 a43 a44 a45 p46 p47
a51 a52 a53 a54 a55 p56 p57
p61 p62 p63 p64 p65 p66 p67
p71 p72 p73 p74 p75 p76 p77
Pekiyi padding resme dahil olmadığına göre hangi içeriğe sahip olacaktır? İşte tipik olarak iki yöntem kullanılmaktadır. Birincisi padding'leri
0 almak ikincisi son n tane satır ya da sütunu tekrarlamak. Genellikle bu tekrarlama yöntemi tercih edilmektedir.
Evrişim işleminde kaydırma birer birer yapılmayabilir. Örneğin ikişer ikişer, üçer üçer yapılabilir. Bu kaydırmaya "stride" denilmektedir.
stride değeri artırılırsa hedef resim padding de yapılmadıysa daha fazla küçülecektir. Hedef resmi küçültmek için stride değeri artırılabilmektedir.
Evrişim işlemi ile ne elde edilmek istenmektedir? Resimlerde evrişim işlemi resmi filtrelemek için kullanılır. Resim filtrelenince
farklı bir hale gelir. Bu biçimde çeşitli filtreler bulunmuştur. Çeşitli amaçlar için çeşitli filtreler bulunmuştur. Örneğin biz bir filtre sayesinde
resmi bulanık (blurred) hale getirebiliriz. Başka bir filtre sayesinde resmin içerisindeki nesnelerin sınır çizgilerini elde edebiliriz.
Bu konu "sayısal sinyal işleme" ile ilgildir. Detayları çeşitli kaynaklardan edinilebilir.
Aşağıdaki örnekte evrişim işlemi yapan conv isimli bir fonksiyon yazılmıştır. Bu fonksiyonla "blur" ve "sobel" filtreleri denenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
def conv(image, kernel):
image_height = image.shape[0]
image_width = image.shape[1]
kernel_height = kernel.shape[0]
kernel_width = kernel.shape[1]
target = np.zeros((image_height - kernel_height + 1, image_width - kernel_width + 1), dtype='uint8')
for row in range(image_height - kernel_height + 1):
for col in range(image_width - kernel_width + 1):
dotp = 0
for i in range(kernel_height):
for k in range(kernel_width):
dotp += image[row + i, col + k] * kernel[i, k]
target[row, col] = np.clip(dotp, 0, 255)
return target
import matplotlib.pyplot as plt
image = plt.imread('AbbeyRoad.jpg')
gray_scaled_image = np.average(image, axis=2, weights=[0.3, 0.59, 0.11])
blur_kernel = np.full((9, 9), 1 / 100)
convoluted_image = conv(gray_scaled_image, blur_kernel)
plt.figure(figsize=(10, 10))
plt.subplot(1, 2, 1)
plt.imshow(gray_scaled_image, cmap='gray')
plt.subplot(1, 2, 2)
plt.imshow(convoluted_image, cmap='gray')
plt.show()
sobel_kernel = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]])
convoluted_image = conv(gray_scaled_image, sobel_kernel)
plt.figure(figsize=(10, 10))
plt.subplot(1, 2, 1)
plt.imshow(gray_scaled_image, cmap='gray')
plt.subplot(1, 2, 2)
plt.imshow(convoluted_image, cmap='gray')
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi evrişim işleminin yapay sinir ağları için anlamı nedir? İşte burada işlemler yukarıdaki örneğin tersi olacak biçimde yapılır.
Yani bir resmin sınıfını belirlemek için onu filtreye sokabiliriz. Ancak bu filtrenin nasıl bir filtre olacağını da ağın bulmasını sağlayabiliriz.
O halde evrişimsel ağlarda biz uygulayacağımız filtreyi bilmemekteyiz. Biz kestirimin daha isabetli yapılması için resmin nasıl bir filtreden geçirilmesi gerektiğini
bulmaya çalışırız. Yani ağ filtreyi uygulamaz bizzat filrtenin kendisini bulmaya çalışır. Ancak resmin yalnızca filtreden geçirilmesi yeterli değildir.
Resim filtreden geçirildikten sonra yine dense katmanlara sokulur. Yani filtreleme ön katmanlarda yapılır sonra yine dense katmanlar kullanılır.
Tabii resmi filtrelerden geçirmek ve bu filtreleri ağın kendisinin bulmasını sağlamak modeldeki eğitilebilir parametrelerin sayısını artırmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aslında evrişim işlemi nöronlarla ifade edilebilir. Çünkü evrişim sırasında yapılan dot-product işlemi aslında nöron girişlerinin
ırlık değerleriyle çarpılıp toplanması işlemi ile aynıdır. Örneğin:
a11 a12 a13 a14 a15
a21 a22 a23 a24 a25
a31 a32 a33 a34 a35
a41 a42 a43 a44 a45
a51 a52 a53 a54 a55
Burada aşağıdaki gibi bir kernek kullanılmış olsun:
b11 b12 b13
b21 b22 b23
b31 b32 b33
Bu kernel'ı resmin sol üst köşesi ile çakıştırdığımızda aslında bir nöron girdisi oluşturmuş oluruz. Şöyle ki: Bu nöronun girdileri
kernel'ın altındaki resmin pixelleridir. Nöronun ağırlık değerleri ise kernel'ın kendisidir. Yani biz aslında evrişim işlemini sanki bir
katmanmış gibi ifade edebiliriz. Pekiyi yukarıdaki örnekte padding yapılmadıysa oluşturulacak evrişim katmanında kaç nöron olacaktır?
Tabii ki hedef resmin büyüklüğü kadar. Yani 3x3 = 9 tane. Bu katmandaki nöronların girdisi 9 tanedir ve çoktısı 1 tanedir. Tabii ağırlık
değeri anlamına gelen kernel hep aynı kernel'dır. Dolyısıyla eğitilebilir parametrelerin sayısı yalnızca kernel büyüklüğü kadardır. Tabii bir de
buradaki nöronların bias değerleri vardır. Genel olarak bu bias değeri tıpkı kernel'daki gibi her nöronda aynı değerdir. Dolayısıyla
oluşturulacak bu evrişim katmanının toplam eğitilebilir parametrelerinin sayısı 3 * 3 + 1 = 10 tane olacaktır.
Aşağıdaki gibi 3x3'lük gri tonlamalı bir resim olsun:
a11 a12 a13
a21 a22 a23
a31 a32 a33
Biz de 2x2'lik aşağıdaki kernel'ı evrişimde uygulayalım:
b11 b12
b21 b22
Bu işlemin sonucu olarak padding yapılmadığı durumda bu işlemden toplamda 4 nöron elde edilecektir. Elde edilen nöronların çıktıları şöyle olacaktır:
activation(a11 * b11 + a12 * b12 + a21 * b21 + a22 * b22 + b) --->
activation(a12 * b11 + a13 * b12 + a22 * b21 + a23 * b22 + b) --->
activation(a21 * b11 + a22 * b12 + a31 * b21 + a32 * b22 + b) --->
activation(a22 * b11 + a23 * b12 + a32 * b21 + a33 * b22 + b) --->
Burada toplam 4 nöron çıktısı vardır. Bu nöronların hepsinin bias değerleri aynıdır.
Aslında uygulamada resim tek bir filtreye de sokulmamaktadır. Birden fazla filrteye sokulmaktadır. Örneğin yukarıdaki resimde biz 3x3'lük 32 farklı
filtre kullanabiliriz. Ağın bu 32 filtreyi de belirlemesini isteyebiliriz. Filtre sayısı artırıldıkça her filtre resmin bir yönünü keşfedeceğinden
resmin anlaşılması da iyileştirilmektedir. Şimdi yukarıdaki resmi 32 farklı filtreye sokalım. O zaman nasıl bir çıktı elde ederiz? İte bu durumda
4x4'lük resimlerden toplam 32 tane elde edilir. Yani toplam nöronların sayısı 4 * 4 * 32 olur. İşte bu çıktı sonraki evrişim katmanına sanki çok
kanallı bir resim gibi sokulmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi evrişim işlemi RGB resimlerde nasıl yürütülmektedir? RGB resimler aslında R, G ve B'lerden oluşan 3 farklı resim gibi ele alınmaktadır.
Dolayısıyla üç farklı kernel bu R, G ve B resimlere ayrı ayrı uygulanmaktadır. Görüntü işleme uygulamalarında bu farklı kernel'ların her bir kanala uygulanması sonucunda
ayrı bir değer elde edilir. Bu da hedef pixel'in RGB değerleir olur. Ancak sinir ağlarında genel olarak 3 farklı kernel her bir kanala uygulandıktan sonra
else edilen değerler toplanarak teke düşürülür. Yani adeta biz evrişimsel ağlarda renkli resimleri evrişim işlemine soktuktan sonra
onlardan gri tonlamalı bir resim elde etmiş gibi oluruz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi evrişimsel sinir ağlarında tek bir evrişim katmanı mı bulunmalı? Aslında evirişim işlemi komuşu pxelleri birbirleriyle
ilişkilendirmektedir. Yani onlara bir bağlam kazandırmaktadır. Yani evrişim işlemiyle pixel'ler biribirinden bağımsız değil
komşu pixel'lerle bağımlı hale getlmektedir. Evrişimin çıktısının yeniden evrişime sokulması pixel'lerin daha uzak pixel'lerle
ilişkilendirilmesi anlamına gelir. İşte bu nedenle genel olarak evrişim katmanları birden fazla katman olarak bulundurulur.
Bu ağın derinleşmesine yol açmaktadır. Anımsanacağı gibi ara katmanların sayısı 2Den fazla ise böyle ağlara "derin ağler (deep neural network)"
denilmektedir. Pek çok uygulamacı evrişim katmanlarında filtre sayısını önceki evrişimin iki katı olacak biçimde artırmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Keras'ta evrişimsel ağların oluşturulması için evrişim katmanı Conv2D isimli bir sınıfla temsil edilmiştir. Conv2D sınıfı resim girdisini
iki boyutla bizden ister. Aslında buna benzer Conv1D sınıfı da vardır. Ancak Conv2D resimsel uygulamalarda daha kullanışlıdır.
Conv2D sınıfının __init__ metodunun ilk 4 parametresi önemlidir. Bu 4 parametre sırasıyla uygulanacak filtrelerin sayısını, kernel'ın genişlik ve yüksekliğini,
stride miktarını ve padding yapılıp yapılmayacağını belirtir. Örneğin:
conv2 = Conv2D(32, (3, 3), padding='same', activation='linear')
Burada toplam 32 filtre uygulanmıştır. Kernel (3, 3) olarak alınmıştır. padding parametresi default "valid" durumdadır. Bu "valid" değeri
padding yapılmayacağı anlamına gelir. Bu parametre "same" geçilirse padding yapılır. Yani hedef resim kaynak resimle aynı büyüklükte olur.
Bu katman padding satır sütunlarını tamamen sıfırlarla doldurmaktadır. Biz burada strides parametresine bir şey girmedik. Bu parametrenin default değeri
(1, 1) biçimindedir. Yani kaydırma birer birer yapılır. Evrişim katmanlarındaki aktivasyon fonksiyonları da Dense katmanlarda olduğu gibi genellikle "relu"
alınmaktadır.
Evrişim katmanlarından sonra modele genellikle yine Dense katmanlar eklenir. Ancak Conv2D katmanın çıktısı çok boyutlu olduğu için onu
tek boyuta indirgemen gerekir. Bu tek boyuta indirgeme işlemi Keras'taki Flatten isimli katmanla yapılmaktadır. Örneğin:
model = Sequential(name='MNIST')
model.add(Conv2D(32, (3, 3), input_shape=(28, 28, 1), name='Conv2D-1', activation='relu'))
model.add(Conv2D(64, (3, 3), name='Conv2D-2', activation='relu'))
model.add(Flatten(name='Flatten'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda da belirtildiği gibi genellikle evrişim katmanlarında birden fazla filtre kullanılmaktadır. Birden filtre kullanıldığında
eğitilebilir parametrelerin sayısı hakkında çıkarımda bulunabilmek için bir örnek verelim:
model = Sequential(name='MNIST')
model.add(Conv2D(32, (3, 3), input_shape=(28, 28, 1), name='Conv2D-1', activation='relu'))
model.add(Conv2D(64, (3, 3), name='Conv2D-2', activation='relu'))
model.add(Flatten(name='Flatten'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
Bu modelden şöyle bir özet elde edilmiştir:
Model: "MNIST"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
Conv2D-1 (Conv2D) (None, 26, 26, 32) 320
Conv2D-2 (Conv2D) (None, 24, 24, 64) 18496
Flatten (Flatten) (None, 36864) 0
Hidden-1 (Dense) (None, 256) 9437440
Hidden-2 (Dense) (None, 128) 32896
Output (Dense) (None, 10) 1290
=================================================================
Total params: 9,490,442
Trainable params: 9,490,442
Non-trainable params: 0
_________________________________________________________________
Burada birinci evrişim katmanının çıktısı 26x26x32 tane nörondan oluşmaktadır. Ancak Conv2D katmanı sanki bu nöronları
26x26'lık 32 kanaldan oluşan bir resim gibi vermektedir. Bu evrişim katmanının çıktısı diğer evrişim katmanına bağlandığında
sanki diğer evrişim katmanı 26x26'lık 32 kanala sahip bir resim girdisi almış gibidir. Birinci katmandaki eğitilebilir parametrelerin
sayısı şöyle hesaplanmaktadır: Bu katmanda 3x3'lük kernel kullanılmıştır. Her kernel toplam 26x26'lık bir resim üretir. Bu 26x26'lık resme
ilişkin nöronların bias değeri aynıdır. Dolayısıyla bu katmandaki eğitilebilir parametrelerin sayısı 9 * 32 + 32'dir. Bu da 320 eder.
Yukarıda da belirtildiği gibi ikinci evrişim katmanının giridisi sanki 26x26'lık 32 kanallı resim gibidir. Buna evrişim uygulamak için
her kanal için ayrı bir filtre kullanılır. Bu durumda 32 tane 3 x 3'lük filtreye ihtiyaç duyulacaktır. En nihayetinde bir tane de bias değeri
işleme girecektir. O halde tek bir filtre için eğitilebilir parametrelerin sayısı 3 * 3 * 32 + 1 = 289 olacaktır. Bu filtrelerden toplam 64 tane
olduğuna göre bu değeri 64 ile çarparsak 289 * 64 = 18496 elde edilir. Burada ikinci evrişim katmanından elde edilen nöronların sayısı padding yapılmadığı için
24 * 24 * 64 = 36864 tanedir. Bu nöronlar Flatten katmanıyla tek boyutlu hale getirilmiştir. Sonra 256 nöronluk bir Dense katmana girdi yapılmıştır.
O halde bu Dense katmandaki eğitilebilir parametrelerin sayısı 36864 * 256 + 256 = 9437440 biçimindedir. Birinci Dense katmanın çıktısında
roplam 256 nöron vardır. Bu 256 nöron sonraki Dense katmana girdi yapılmıştır. Sonraki Dense katman 128 nörona sahiptir. O halde sonraki Dense katmandaki
eğitilebilir parametrelerin sayısı 256 * 128 + 123 = 32896 olacaktır. Nihayet çıktı katmanında 10 nöron vardır. İkinci katmanın çıktısındaki 128
nöron bu katmana girdi yapılmıştır. O halde çıktı katmanındaki eğitilebilir parametrelerin sayısı 128 * 10 + 10 = 1290 tanedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda daha önce yapmış olduğumuz MNIST örneğini evrişimsel katmanlar kullnarak yeniden yapıyoruz.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df_training = pd.read_csv('mnist_train.csv')
df_test = pd.read_csv('mnist_test.csv')
training_dataset_x = df_training.iloc[:, 1:].to_numpy()
training_dataset_y = df_training.iloc[:, 0].to_numpy()
test_dataset_x = df_test.iloc[:, 1:].to_numpy()
test_dataset_y = df_test.iloc[:, 0].to_numpy()
import matplotlib.pyplot as plt
plt.figure(figsize=(20, 40))
for i in range(50):
plt.subplot(10, 5, i + 1)
plt.title(str(training_dataset_y[i]), fontsize=14)
plt.imshow(training_dataset_x[i].reshape(28, 28), cmap='gray')
plt.show()
from tensorflow.keras.utils import to_categorical
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
training_dataset_x = training_dataset_x / 255
test_dataset_x = test_dataset_x / 255
training_dataset_x = training_dataset_x.reshape(-1, 28, 28, 1)
test_dataset_x = test_dataset_x.reshape(-1, 28, 28, 1)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Conv2D, Dense, Flatten
model = Sequential(name='MNIST')
model.add(Conv2D(32, (3, 3), input_shape=(3, 3, 1), name='Conv2D-1', activation='relu'))
model.add(Conv2D(64, (3, 3), name='Conv2D-2', activation='relu'))
model.add(Flatten(name='Flatten'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(training_dataset_x, ohe_training_dataset_y, epochs=20, batch_size=32, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Categorical Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['categorical_accuracy'])
plt.plot(hist.epoch, hist.history['val_categorical_accuracy'])
plt.legend(['Categorical Accuracy', 'Validation Categorical Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, ohe_test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
import glob
for path in glob.glob('test-images/*.jpg'):
image_data = plt.imread(path)
gray_scaled_image_data = np.average(image_data, axis=2, weights=[0.3, 0.59, 0.11])
gray_scaled_image_data = gray_scaled_image_data / 255
predict_result = model.predict(gray_scaled_image_data.reshape(1, 28, 28))
result = np.argmax(predict_result)
print(f'{path}: {result}')
#----------------------------------------------------------------------------------------------------------------------------
Evrişimsel ağlarda evrişim katmanlarında çok fazla eğitilebilir parametre oluşmaktadır. Yukarıdaki MNIST örneğinde toplam eğitilebilir
parametrelerin sayısı 9.5 milyon civarındadır. Üstelik bu örnekteki resimler 28x28 gri tonlamalıdır. Pratikte 28x28 çok küçük resimlerdir.
Ayrıca resimler genellikle karşımıza renkli biçimde gelmektedir. Eğitilebilir parametrelerin sayısının fazla olmasının şu dezavantajları vardır:
- Eğitim için gereken zaman fazlalaşır.
- Çok nöron olmasından kaynaklanan overfitting durumları oluşabilir.
- Eğitim sonucunda eitim bilgilerinin saklanması için gerekli olan disk alanı büyür.
İşte bu tür resim tanıma işlemlerinde eğitilebilir parametrelerin sayısını düşürmek için çeşitli teknikler kullanılmaktadır.
Bunun için akla gelecek yöntem evrişim katmanlarındaki kaydırma değerini (strides) değerini artırmaktır. Ancak kaydırma değerinin artırılması
resmin tanınması için dezavantaj oluşturmaktadır. Nöron sayılarını azaltmak için diğer bir yöntem "pooling" denilen yöntemdir.
Genellikle pooling yöntemi tercih edilmektedir.
Pooling bir grup dikdörtgensel bölgedeki pixel'ler yerine onları temsil eden tek bir pixel'in elde edilmesi yöntemidir. İki önemli
versiyonu vardır: "Max Pooing" ve "Average Pooling". Max Pooling yönteminde dikdörtgensel bölgedeki en büyük elemanlar alınır. Average Pooling
yönteminde ise dikdörtgensel bölgedeki elemanların ortalaması alınır. Uygulamada daha çok Max Pooling yöntemi tercih edilmektedir. Örneğin
aşağıda 4x4'lük pizel'lerden oluşan gri tonlamalı bir resim olsun:
112 62 41 52
200 15 217 21
58 92 81 117
0 21 45 89
Çerçevimiz 2x2'lik olsun. Bu 2x2'lik çerçeve resim üzerinde sağdan iki aşağıdan olacak şekilde kaydırılır ve toplam 4 bölge elde edilir:
112 62
200 15
41 52
217 21
58 92
0 21
81 117
45 89
İşte Max Pooling yönteminde bu çerçevelerin en büyük elemanları alınır ve aşağıdaki matris elde edilir:
200 217
92 117
Bu işlemin sonucunda elde edilen matris ilkinin karekökü kadardır. Pooling işlemleri de Keras'ta MaxPooling2D ve AveragePooling2D
gibi sınıflarla temsil edilmiştir. Sınıfların __init__ metotlarının parametrik yapıları şöyledir:
tf.keras.layers.MaxPooling2D(pool_size=(2, 2), strides=None, padding='valid', data_format=None, **kwargs)
tf.keras.layers.AveragePooling2D(pool_size=(2, 2), strides=None, padding='valid', data_format=None, **kwargs)
Metotların pool_size parametreleri çerçevenin büyüklüğünü belirtmektedir. Default olarak 2x2'lik çerçeve kullanılmaktadır. strides
parametreleri yine kaydırma miktarını belirtir. Default durumda kaydırma pools_size parametresiyle aynı değerdedir. padding parametresi yine
"valid" ya da "same" olabilir. "valid" padding yapılmayacağı, "same" ise padding yapılacağı anlamına gelir.
Tipik olarak Pooling katmanları her evrişim katmanından sonra uygulanır. Örneğin:
model = Sequential(name='MNIST')
model.add(Conv2D(32, (3, 3), input_shape=(28, 28, 1), name='Conv2D-1', activation='relu'))
model.add(MaxPooling2D(name='Pooling-1'))
model.add(Conv2D(64, (3, 3), name='Conv2D-2', activation='relu'))
model.add(MaxPooling2D(name='Pooling-2'))
model.add(Flatten(name='Flatten'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
Burada aşağıdaki gibi bir özet eld edilmiştir:
Model: "MNIST"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
Conv2D-1 (Conv2D) (None, 26, 26, 32) 320
Pooling-1 (MaxPooling2D) (None, 13, 13, 32) 0
Conv2D-2 (Conv2D) (None, 11, 11, 64) 18496
Pooling-2 (MaxPooling2D) (None, 5, 5, 64) 0
Flatten (Flatten) (None, 1600) 0
Hidden-1 (Dense) (None, 256) 409856
Hidden-2 (Dense) (None, 128) 32896
Output (Dense) (None, 10) 1290
=================================================================
Total params: 462,858
Trainable params: 462,858
Non-trainable params: 0
Görüldüğü gibi eğitilabilir parametrelerin sayısı oldukça azaltılmıştır. Şimdi eğitim daha hızlı yapılacaktır ve eitimin başarısı biraz daha
artacaktır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Renkli resimlerin sınıflandırılması için sık kullanılan deneme veri kümelerinden biri CIFAR-10 veri kümesidir. Bu veri kümesi
tensorflow.keras.datasets paketi içerisinde de cifar10 modülünde bulunmaktadır. CIFAR-10 veri kğmesinde her biri 32x32 pixel
olan 3 kanallı RGB resimler bulunmaktadır. Bu RGB resimler 10 farklı sınıfa ayrılmaktadır. Sınıflar şunlardır:
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
Veri kümesinin orijinal https://www.cs.toronto.edu/~kriz/cifar.html adresinden indirilebilir. Veri kümesinin CSV biçimi de
https://www.kaggle.com/datasets/fedesoriano/cifar10-python-in-csv adresinden indirilebilir. Veri kümesinin orijinali 5 tane eğitim dosyası
bir tane de test dosyası olacak biçimde indirilmektedir. Ancak bu dosyalar Python'ın pickle modülü ile seri hale getirilmiştir. Bizim bu dosyalardan hareketle
sözlük bçimine dönüştümemiz gerekir. Bu işlemi yaptıktan sonra elde edilen sözlüğün b'data' anahtarı ilgili resimlerin pizel'lerini,
b'labels' anahtarı ise ilgili resmin sınıflarını vermektedir. İşlem aşağıdaki gibi yapılabilir (indirilen dosyaların "cifar-10-batches-py" isimli
bir dizinde olduğunu varsayıyoruz):
import pickle
import glob
x_lst = []
y_lst = []
for path in glob.glob('cifar-10-batches-py/data_batch_*'):
with open(path, 'rb') as f:
d = pickle.load(f, encoding='bytes')
x_lst.append(d[b'data'])
y_lst.append(d[b'labels'])
import numpy as np
training_dataset_x = np.concatenate(x_lst)
training_dataset_y = np.concatenate(y_lst)
with open('cifar-10-batches-py/test_batch', 'rb') as f:
d = pickle.load(f, encoding='bytes')
test_dataset_x = d[b'data']
test_dataset_y = d[b'labels']
Aşağıda CIFAR-10 veri kümesinin evrişim ve pooling katmanlarıyla gerçekleştiriminin tüm kodları verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pickle
import glob
x_lst = []
y_lst = []
for path in glob.glob('cifar-10-batches-py/data_batch_*'):
with open(path, 'rb') as f:
d = pickle.load(f, encoding='bytes')
x_lst.append(d[b'data'])
y_lst.append(d[b'labels'])
import numpy as np
training_dataset_x = np.concatenate(x_lst)
training_dataset_y = np.concatenate(y_lst)
with open('cifar-10-batches-py/test_batch', 'rb') as f:
d = pickle.load(f, encoding='bytes')
test_dataset_x = d[b'data']
test_dataset_y = d[b'labels']
training_dataset_x = training_dataset_x / 255
test_dataset_x = test_dataset_x / 255
training_dataset_x = training_dataset_x.reshape(-1, 3, 32, 32)
training_dataset_x = np.transpose(training_dataset_x, [0, 2, 3, 1])
test_dataset_x = test_dataset_x.reshape(-1, 3, 32, 32)
test_dataset_x = np.transpose(test_dataset_x, [0, 2, 3, 1])
from tensorflow.keras.utils import to_categorical
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
import matplotlib.pyplot as plt
plt.figure(figsize=(5, 25))
for i in range(30):
plt.subplot(10, 3, i + 1)
plt.title(class_names[training_dataset_y[i]], pad=10)
plt.imshow(training_dataset_x[i])
plt.show()
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Conv2D, Dense, Flatten, MaxPooling2D
model = Sequential(name='CIFAR-10')
model.add(Conv2D(32, (3, 3), input_shape=(32, 32, 3), name='Conv2D-1', activation='relu'))
model.add(MaxPooling2D(name='Pooling-1'))
model.add(Conv2D(64, (3, 3), name='Conv2D-2', activation='relu'))
model.add(MaxPooling2D(name='Pooling-2'))
model.add(Conv2D(128, (3, 3), name='Conv3D-3', activation='relu'))
model.add(MaxPooling2D(name='Pooling-3'))
model.add(Flatten(name='Flatten'))
model.add(Dense(512, activation='relu', name='Hidden-1'))
model.add(Dense(256, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(training_dataset_x, ohe_training_dataset_y, epochs=20, batch_size=32, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Categorical Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['categorical_accuracy'])
plt.plot(hist.epoch, hist.history['val_categorical_accuracy'])
plt.legend(['Categorical Accuracy', 'Validation Categorical Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, ohe_test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import glob
for path in glob.glob('test-images/*.jpg'):
image_data = plt.imread(path)
image_data = image_data / 255
predict_result = model.predict(image_data.reshape(1, 32, 32, 3))
result = np.argmax(predict_result)
print(f'{path}: {class_names[result]}')
#----------------------------------------------------------------------------------------------------------------------------
Aslında CIFAR-10 veri kümesi tensorflow.keras.datasets paketi içerisinde cifar10 modülü biçiminde hazır olarak da bulunmaktadır.
Kers'taki hazır CIFAR-10 veri kümesi zaten 32x32x3'lük resim verilerini bize vermektedir. Aşağıdaki örnekte aynı CIFAR-10 veri kümesi
hazır olarak kullanılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.datasets import cifar10
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = cifar10.load_data()
training_dataset_x = training_dataset_x / 255
test_dataset_x = test_dataset_x / 255
from tensorflow.keras.utils import to_categorical
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
import matplotlib.pyplot as plt
plt.figure(figsize=(5, 25))
for i in range(30):
plt.subplot(10, 3, i + 1)
plt.title(class_names[training_dataset_y[i, 0]], pad=10)
plt.imshow(training_dataset_x[i])
plt.show()
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Conv2D, Dense, Flatten, MaxPooling2D
model = Sequential(name='CIFAR-10')
model.add(Conv2D(32, (3, 3), input_shape=(32, 32, 3), name='Conv2D-1', activation='relu'))
model.add(MaxPooling2D(name='Pooling-1'))
model.add(Conv2D(64, (3, 3), name='Conv2D-2', activation='relu'))
model.add(MaxPooling2D(name='Pooling-2'))
model.add(Conv2D(128, (3, 3), name='Conv3D-3', activation='relu'))
model.add(MaxPooling2D(name='Pooling-3'))
model.add(Flatten(name='Flatten'))
model.add(Dense(512, activation='relu', name='Hidden-1'))
model.add(Dense(256, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(training_dataset_x, ohe_training_dataset_y, epochs=2 , batch_size=32, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Categorical Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['categorical_accuracy'])
plt.plot(hist.epoch, hist.history['val_categorical_accuracy'])
plt.legend(['Categorical Accuracy', 'Validation Categorical Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, ohe_test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
import glob
for path in glob.glob('test-images/*.jpg'):
image_data = plt.imread(path)
image_data = image_data / 255
predict_result = model.predict(image_data.reshape(1, 32, 32, 3))
result = np.argmax(predict_result)
print(f'{path}: {class_names[result]}')
#----------------------------------------------------------------------------------------------------------------------------
CIFAR-100 veri kümesi tamamen CIFAR-10 veri kümesi gibidir. Ancak resimler 10 tane sınıfa değil 100 tane sınıfa ayrılmış durumdadır.
Yani biz CIFAR-100 veri kümesinde resimlerin 10 tane sınıftan hangisine ilişkin değil 100 tane sınıftan hangisine ilişkin olduğunu tahmin etmeye çalışırız.
Bu veri kümesi de yine aşağıdaki bağlantıdan indirilebilir:
https://www.cs.toronto.edu/~kriz/cifar.html
Buradaki dosya indirilip açıldığında cifar-100-python dizini içerisinde train ve test isimli iki dosya bulunacaktır. Bu dosyalar yine
Python'ın pickle modülü ile seri hale getirilmiştir. Bunların açılması gerekmektedir. Seri hale getirilmiş veriler açıldığında bir sözlük elde
edilir. y verilerini temsil eden "fine_labels" "coarse_labels" anahtarları bulunmaktadır. Buradaki "fine_labels" 100 sınıf, "coarse_labels" ise
20 sınıf belirtmektedir. Açım işlemi şöyle yapılabilir:
import pickle
with open('cifar-100-python/train', 'rb') as f:
training_dataset = pickle.load(f, encoding='bytes')
training_dataset_x = training_dataset[b'data']
training_dataset_y = training_dataset[b'fine_labels']
with open('cifar-100-python/test', 'rb') as f:
test_dataset = pickle.load(f, encoding='bytes')
test_dataset_x = test_dataset[b'data']
test_dataset_y = test_dataset[b'fine_labels']
Resimlerin ilişkin olduğu 100 sınıf şöyledir:
class_names = [
'apple', 'aquarium_fish', 'baby', 'bear', 'beaver', 'bed', 'bee', 'beetle',
'bicycle', 'bottle', 'bowl', 'boy', 'bridge', 'bus', 'butterfly', 'camel',
'can', 'castle', 'caterpillar', 'cattle', 'chair', 'chimpanzee', 'clock',
'cloud', 'cockroach', 'couch', 'crab', 'crocodile', 'cup', 'dinosaur',
'dolphin', 'elephant', 'flatfish', 'forest', 'fox', 'girl', 'hamster',
'house', 'kangaroo', 'keyboard', 'lamp', 'lawn_mower', 'leopard', 'lion',
'lizard', 'lobster', 'man', 'maple_tree', 'motorcycle', 'mountain', 'mouse',
'mushroom', 'oak_tree', 'orange', 'orchid', 'otter', 'palm_tree', 'pear',
'pickup_truck', 'pine_tree', 'plain', 'plate', 'poppy', 'porcupine',
'possum', 'rabbit', 'raccoon', 'ray', 'road', 'rocket', 'rose',
'sea', 'seal', 'shark', 'shrew', 'skunk', 'skyscraper', 'snail', 'snake',
'spider', 'squirrel', 'streetcar', 'sunflower', 'sweet_pepper', 'table',
'tank', 'telephone', 'television', 'tiger', 'tractor', 'train', 'trout',
'tulip', 'turtle', 'wardrobe', 'whale', 'willow_tree', 'wolf', 'woman',
'worm'
]
Aşağıda CIFAR-100 örneği bütün olarak verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pickle
with open('cifar-100-python/train', 'rb') as f:
training_dataset = pickle.load(f, encoding='bytes')
training_dataset_x = training_dataset[b'data']
training_dataset_y = training_dataset[b'fine_labels']
with open('cifar-100-python/test', 'rb') as f:
test_dataset = pickle.load(f, encoding='bytes')
test_dataset_x = test_dataset[b'data']
test_dataset_y = test_dataset[b'fine_labels']
training_dataset_x = training_dataset_x / 255
test_dataset_x = test_dataset_x / 255
import numpy as np
training_dataset_x = training_dataset_x.reshape(-1, 3, 32, 32)
training_dataset_x = np.transpose(training_dataset_x, [0, 2, 3, 1])
test_dataset_x = test_dataset_x.reshape(-1, 3, 32, 32)
test_dataset_x = np.transpose(test_dataset_x, [0, 2, 3, 1])
from tensorflow.keras.utils import to_categorical
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
class_names = [
'apple', 'aquarium_fish', 'baby', 'bear', 'beaver', 'bed', 'bee', 'beetle',
'bicycle', 'bottle', 'bowl', 'boy', 'bridge', 'bus', 'butterfly', 'camel',
'can', 'castle', 'caterpillar', 'cattle', 'chair', 'chimpanzee', 'clock',
'cloud', 'cockroach', 'couch', 'crab', 'crocodile', 'cup', 'dinosaur',
'dolphin', 'elephant', 'flatfish', 'forest', 'fox', 'girl', 'hamster',
'house', 'kangaroo', 'keyboard', 'lamp', 'lawn_mower', 'leopard', 'lion',
'lizard', 'lobster', 'man', 'maple_tree', 'motorcycle', 'mountain', 'mouse',
'mushroom', 'oak_tree', 'orange', 'orchid', 'otter', 'palm_tree', 'pear',
'pickup_truck', 'pine_tree', 'plain', 'plate', 'poppy', 'porcupine',
'possum', 'rabbit', 'raccoon', 'ray', 'road', 'rocket', 'rose',
'sea', 'seal', 'shark', 'shrew', 'skunk', 'skyscraper', 'snail', 'snake',
'spider', 'squirrel', 'streetcar', 'sunflower', 'sweet_pepper', 'table',
'tank', 'telephone', 'television', 'tiger', 'tractor', 'train', 'trout',
'tulip', 'turtle', 'wardrobe', 'whale', 'willow_tree', 'wolf', 'woman',
'worm']
import matplotlib.pyplot as plt
plt.figure(figsize=(5, 25))
for i in range(30):
plt.subplot(10, 3, i + 1)
plt.title(class_names[training_dataset_y[i]], pad=10)
plt.imshow(training_dataset_x[i])
plt.show()
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Conv2D, Dense, Flatten, MaxPooling2D
model = Sequential(name='CIFAR-100')
model.add(Conv2D(32, (3, 3), input_shape=(32, 32, 3), name='Conv2D-1', activation='relu'))
model.add(MaxPooling2D(name='Pooling-1'))
model.add(Conv2D(64, (3, 3), name='Conv2D-2', activation='relu'))
model.add(MaxPooling2D(name='Pooling-2'))
model.add(Conv2D(128, (3, 3), name='Conv3D-3', activation='relu'))
model.add(MaxPooling2D(name='Pooling-3'))
model.add(Flatten(name='Flatten'))
model.add(Dense(512, activation='relu', name='Hidden-1'))
model.add(Dense(256, activation='relu', name='Hidden-2'))
model.add(Dense(100, activation='softmax', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(training_dataset_x, ohe_training_dataset_y, epochs=20, batch_size=32, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Categorical Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['categorical_accuracy'])
plt.plot(hist.epoch, hist.history['val_categorical_accuracy'])
plt.legend(['Categorical Accuracy', 'Validation Categorical Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, ohe_test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import glob
for path in glob.glob('test-images/*.jpg'):
image_data = plt.imread(path)
image_data = image_data / 255
predict_result = model.predict(image_data.reshape(1, 32, 32, 3))
result = np.argmax(predict_result)
print(f'{path}: {class_names[result]}')
import itertools
for picture_data in itertools.islice(training_dataset_x[np.array(training_dataset_y) == class_names.index('bowl')], 100):
plt.figure(figsize=(1, 1))
plt.imshow(picture_data)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aslında CIFAR-100 verileri de tensorflow.keras.datasets paketi içerisindeki cifar100 modülünde bulunmaktadır. Aşağıda
aynı örneği bu modüldeki hazır verilerle gerçekleştiriyoruz.
#----------------------------------------------------------------------------------------------------------------------------
import pickle
with open('cifar-100-python/train', 'rb') as f:
training_dataset = pickle.load(f, encoding='bytes')
training_dataset_x = training_dataset[b'data']
training_dataset_y = training_dataset[b'fine_labels']
with open('cifar-100-python/test', 'rb') as f:
test_dataset = pickle.load(f, encoding='bytes')
test_dataset_x = test_dataset[b'data']
test_dataset_y = test_dataset[b'fine_labels']
training_dataset_x = training_dataset_x / 255
test_dataset_x = test_dataset_x / 255
import numpy as np
training_dataset_x = training_dataset_x.reshape(-1, 3, 32, 32)
training_dataset_x = np.transpose(training_dataset_x, [0, 2, 3, 1])
test_dataset_x = test_dataset_x.reshape(-1, 3, 32, 32)
test_dataset_x = np.transpose(test_dataset_x, [0, 2, 3, 1])
from tensorflow.keras.utils import to_categorical
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
class_names = [
'apple', 'aquarium_fish', 'baby', 'bear', 'beaver', 'bed', 'bee', 'beetle',
'bicycle', 'bottle', 'bowl', 'boy', 'bridge', 'bus', 'butterfly', 'camel',
'can', 'castle', 'caterpillar', 'cattle', 'chair', 'chimpanzee', 'clock',
'cloud', 'cockroach', 'couch', 'crab', 'crocodile', 'cup', 'dinosaur',
'dolphin', 'elephant', 'flatfish', 'forest', 'fox', 'girl', 'hamster',
'house', 'kangaroo', 'keyboard', 'lamp', 'lawn_mower', 'leopard', 'lion',
'lizard', 'lobster', 'man', 'maple_tree', 'motorcycle', 'mountain', 'mouse',
'mushroom', 'oak_tree', 'orange', 'orchid', 'otter', 'palm_tree', 'pear',
'pickup_truck', 'pine_tree', 'plain', 'plate', 'poppy', 'porcupine',
'possum', 'rabbit', 'raccoon', 'ray', 'road', 'rocket', 'rose',
'sea', 'seal', 'shark', 'shrew', 'skunk', 'skyscraper', 'snail', 'snake',
'spider', 'squirrel', 'streetcar', 'sunflower', 'sweet_pepper', 'table',
'tank', 'telephone', 'television', 'tiger', 'tractor', 'train', 'trout',
'tulip', 'turtle', 'wardrobe', 'whale', 'willow_tree', 'wolf', 'woman',
'worm']
import matplotlib.pyplot as plt
plt.figure(figsize=(5, 25))
for i in range(30):
plt.subplot(10, 3, i + 1)
plt.title(class_names[training_dataset_y[i]], pad=10)
plt.imshow(training_dataset_x[i])
plt.show()
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Conv2D, Dense, Flatten, MaxPooling2D
model = Sequential(name='CIFAR-100')
model.add(Conv2D(32, (3, 3), input_shape=(32, 32, 3), name='Conv2D-1', activation='relu'))
model.add(MaxPooling2D(name='Pooling-1'))
model.add(Conv2D(64, (3, 3), name='Conv2D-2', activation='relu'))
model.add(MaxPooling2D(name='Pooling-2'))
model.add(Conv2D(128, (3, 3), name='Conv3D-3', activation='relu'))
model.add(MaxPooling2D(name='Pooling-3'))
model.add(Flatten(name='Flatten'))
model.add(Dense(512, activation='relu', name='Hidden-1'))
model.add(Dense(256, activation='relu', name='Hidden-2'))
model.add(Dense(100, activation='softmax', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(training_dataset_x, ohe_training_dataset_y, epochs=20, batch_size=32, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Categorical Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['categorical_accuracy'])
plt.plot(hist.epoch, hist.history['val_categorical_accuracy'])
plt.legend(['Categorical Accuracy', 'Validation Categorical Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, ohe_test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import glob
for path in glob.glob('test-images/*.jpg'):
image_data = plt.imread(path)
image_data = image_data / 255
predict_result = model.predict(image_data.reshape(1, 32, 32, 3))
result = np.argmax(predict_result)
print(f'{path}: {class_names[result]}')
import itertools
for picture_data in itertools.islice(training_dataset_x[np.array(training_dataset_y) == class_names.index('bowl')], 100):
plt.figure(figsize=(1, 1))
plt.imshow(picture_data)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Keras'taki önemli bir callback sınıfı da EarlyStopping isimli callback sınıfıdır. Bu sınıfın amacı epoch'lar sonrasında sınama verilerindeki metrik
değerleri önceki epoch'lardaki sınama metrik değerleriyle karşılaştırıp eğer metrik değerlerde bir iyileşme olumuyorsa eğitimi sonlandırmaktır. Normal olarak
epoch'lar sırasında sınama metrik değerlerinin iyileşmesi beklenir. Sınama metrik değerlerinin iyileşmemesi durumunda eğitime devam etmek iyi bir fikir değildir.
Çünkü bu tür durumlarda eğitim verilerinden elde edilen metrik değerler gitgide iyileşir ancak sınama verilerindeki değerler iyileşmezse bir overfitting durumu oluşur.
Bu durum da eğitimin olumsuz bir yöne gittiğini gösterir. EarlyStopping callback sınıfının __init__ metodunun parametrik yapısı şöyledir:
tf.keras.callbacks.EarlyStopping(
monitor='val_loss',
min_delta=0,
patience=0,
verbose=0,
mode='auto',
baseline=None,
restore_best_weights=False
)
Burada monitor parametresi izlenecek metrik değeri belirtir. Metrik değerin başında "val_" öneki varsa bu sınamaya ilişkin metrik
değer anlamına gelir. min_delta parametresi iyileşme için minimum aralığı belirtmektedir. (Örneğin bu değer 0.01 girilirse ancak 0.01'den daha
fazla bir düşüş iyileşme kabul edilir.) patience parametresi üst üste kaç kez iyileşme olmazsa eğitimin sonlandırılacağını belirtir. Buraya tipik olarak
3, 5 gibi değerler girilebilir. verbose parametresi 1 girilirse ekrana bilgi yazıları basılır. mode parametresi "min", "max" ya da "auto"
girilebilir. "min" iyileşmenin düşüşle sağlandığını, "max" iyileşmenin yükselişle sağlandığını belirtir. mode ise metrik değere göre bunu
kendisi belirler. restore_best_weights parametresi True geçilirse eğitim sonlandırılana kadar en iyi metrik değerin bulunduğu epoch'a ilişkin
nöron ağırklık değerleri modele set edilir.
Aşağıdaki örnekte Boston Housing Price veri kümesinde "val_loss" metrik değeri üst üste üç kez iyileşmediği zaman eğitim sonlandırılmıştır.
restore_best_weights=True yapıldığı için model son epoch'taki ağırlık değerleriyle değil tüm epoch'lar arasındaki en iyi ağırlık değeriyle set edilecektir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Boston-Housing-Price')
model.add(Dense(64, activation='relu', input_dim=training_dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping('val_loss', patience=5, verbose=1, restore_best_weights=True)
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=500, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Mean Absolute Error Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['mae'])
plt.plot(hist.epoch, hist.history['val_mae'])
plt.legend(['Mean Absolute Error', 'Validation Mean Absolute Error'])
plt.show()
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
predict_data = np.array([[0.11747, 12.50, 7.870, 0, 0.5240, 6.0090, 82.90, 6.2267, 5, 311.0, 15.20, 396.90, 13.27]])
scaled_predict_data = mms.transform(predict_data)
predict_result = model.predict(scaled_predict_data)
for val in predict_result[:, 0]:
print(val)
model.save('boston.h5')
import pickle
with open('boston.pickle', 'wb') as f:
pickle.dump(mms, f)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte CIFAR-10 veri kümesinde "val_categorical_accuracy" metirk değeri üst üste 5 kere iyileşmediği zaman eğitim sonlandırılmıştır.
Bu örnekte de restore_best_weigts=True yapıldığı için model en iyi ağırlık değerleriyle set edilecektir.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.datasets import cifar10
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = cifar10.load_data()
training_dataset_x = training_dataset_x / 255
test_dataset_x = test_dataset_x / 255
from tensorflow.keras.utils import to_categorical
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
import matplotlib.pyplot as plt
plt.figure(figsize=(5, 25))
for i in range(30):
plt.subplot(10, 3, i + 1)
plt.title(class_names[training_dataset_y[i, 0]], pad=10)
plt.imshow(training_dataset_x[i])
plt.show()
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Conv2D, Dense, Flatten, MaxPooling2D
model = Sequential(name='CIFAR-10')
model.add(Conv2D(32, (3, 3), input_shape=(32, 32, 3), name='Conv2D-1', activation='relu'))
model.add(MaxPooling2D(name='Pooling-1'))
model.add(Conv2D(64, (3, 3), name='Conv2D-2', activation='relu'))
model.add(MaxPooling2D(name='Pooling-2'))
model.add(Conv2D(128, (3, 3), name='Conv3D-3', activation='relu'))
model.add(MaxPooling2D(name='Pooling-3'))
model.add(Flatten(name='Flatten'))
model.add(Dense(512, activation='relu', name='Hidden-1'))
model.add(Dense(256, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(monitor='val_categorical_accuracy', patience=5, verbose=1, restore_best_weights=True)
hist = model.fit(training_dataset_x, ohe_training_dataset_y, epochs=100 , batch_size=32, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Categorical Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['categorical_accuracy'])
plt.plot(hist.epoch, hist.history['val_categorical_accuracy'])
plt.legend(['Categorical Accuracy', 'Validation Categorical Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, ohe_test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
ModelCheckpoint sınıfı epoch'lar sırasında modelin belli durumlarda save edilmesi için kullanılmaktadır. Sınıfın __init__
metodunun parametrik yapısı şöyledir:
tf.keras.callbacks.ModelCheckpoint(
filepath,
monitor='val_loss',
verbose=0,
save_best_only=False,
save_weights_only=False,
mode='auto',
save_freq = 'epoch',
options=None,
initial_value_threshold=None,
**kwargs
)
Metodun birinci parametresi modelin save edileceği dosyanın yol ifadesini alır. Bu parametredeki isim formatlı olabilmektedir.
Metot bi,rden fazla save işlemi yapacaksa buradaki kalıp kullanılmaktadır. İkinci parametre yine izlenecek metrik değeri belirtmektedir.
save_best_only parametresi True girilirse yalnızca en iyi model save edilir. mode parametresi yine EarlyStopping sınıfındaki gibidir.
save_weights_only parametresi default durumda False biçimdedir. Bu parametre True geçilirse tüm model değil yalnızca ağırlıklar save
edilir. Yine verbose parametresi 1 girilirse ekrana bazı bilgiler yazılmaktadır. Örneğin bizim amacımız val_loss değerinin en iyi olduğu durumdaki
modeli save etmekse ModelCheckpoint nesnesini aşağıdaki gibi yaratabiliriz:
mcp = ModelCheckpoint('boston-checkpoint.h5', monitor='val_loss', save_best_only=True)
Burada save_best_only parametresi True girildiği için yalnızca en iyi model save edilecektir. Bu callback sınıfının amacı eğitimi erkenden sonlandırmak değildir.
Tüm epoch'ları uygulayıp iyi sonuçların save edilmesini sağlamaktadır. Metodun birinci parametresinde tek bir dosya ismi vermeyip bir kalıp biçiminde
formatlama bilgisi kullanılırsa bu durumda eğer save_best_only parametresi False geçilirse tüm epoch'lardaki ağırlıklar formatlama bilgisine uygun
dosya isimleri ile save edilir. Eğer save_best_only parametresi True geçilirse bu durumda yalnızca daha öncekilere göre daha iyi olan epoch değerleri
formatlama bilgisine uygun dosya isimleriyle save edilir. Eğer dosya isminde formatlama kullanılmazsa bu durumda save_best_only parametresi False geçilirse
son epoch'taki değerler save edilir. Eğer dosya isminde formatlama kullanılmazsa fakat save_best_only parametresi True geçilirse bu durumda en iyi model save
edilmiş olacaktır. Başka bir deyişle dosya ismindeki formatlama aslında "save işlemini başka bir dosya üzerinde yap" anlamına gelmektedir.
Formatlama bilgisinde "{epoch}" o andaki epoch değerini temsil eder. "{epoch:02d}" gibi bir format epoch değerini iki basamak olarak (tek basamaksa 0 ile doldurarak)"
oluşturma anlamına gelir. Örneğin:
mcp = ModelCheckpoint('boston-checkpoint-{epoch:02d}.h5', monitor='val_loss', save_best_only=True)
Burada val_loss metrik değeri daha öncekilere göre iyi olan epoch ile karşılaşıldığında "boston-checkpoint-NN" gibi (burada NN epoch numarasını belirtir)
bir dosyaya save işlemi yapılacaktır.
Aşağıdaki örnekte Boston Housing Price veri kğmesinde ModelCheckpoint sınıfı kullanılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Boston-Housing-Price')
model.add(Dense(64, activation='relu', input_dim=training_dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
from tensorflow.keras.callbacks import ModelCheckpoint
mcp = ModelCheckpoint('boston-checkpoint-{epoch:02d}.h5', monitor='val_loss', save_best_only=True)
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2, callbacks=[mcp])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, len(hist.epoch), 20))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Mean Absolute Error Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, len(hist.epoch), 20))
plt.plot(hist.epoch, hist.history['mae'])
plt.plot(hist.epoch, hist.history['val_mae'])
plt.legend(['Mean Absolute Error', 'Validation Mean Absolute Error'])
plt.show()
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
Tabii aslında istenirse EarlyStopping callback sınıfı ile ModelCheckpoint sınıfı birlikte de kullanılabilir. Aşağıda böyle bir
örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Boston-Housing-Price')
model.add(Dense(64, activation='relu', input_dim=training_dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
esc = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)
mcp = ModelCheckpoint('boston-checkpoint-{epoch:02d}.h5', monitor='val_loss', save_best_only=True)
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2, callbacks=[esc, mcp])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, len(hist.epoch), 20))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Mean Absolute Error Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, len(hist.epoch), 20))
plt.plot(hist.epoch, hist.history['mae'])
plt.plot(hist.epoch, hist.history['val_mae'])
plt.legend(['Mean Absolute Error', 'Validation Mean Absolute Error'])
plt.show()
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
Yazıların sınıflandırılması ve onlardan anlam çıkartılması için kullanılan önemli tekniklerden biri de "word embedding" denilen tekniktir.
Biz şimdiye kadar IMDB ve Retures gibi örneklerde yazıları sütun sayısı tüm vocabulary kadar olan vektörlerle temsil ettik.
Bu yöntemin en önemli dezavantajları her yazının büyük bir vektöre dönüştürülmesi ve sözcükler arasında bağlamsal bir ilişkinin kurulamamasıydı.
Biz bu tekniğe "vektörizasyon yöntemi" demiştik. İşte "word embedding" denilen teknik bu teknikten daha ileri bir tekniktir.
Word embedding yukarıda belirttiğimiz iki dezavantajı azaltmaktadır.
Word embedding yönteminde yazı içerisindeki sözcüklerin her biri eşit uzunlukta gerçek değerlere sahip vektörlerle ifade edilir.
Örneğin yazının içerisinde 100 tane sözcük olsun. Vektör uzunluğunun da 32 olduğunu varsayalım. Bu durumda bu yazı 100x32
boyutunda bir matrisle temsil edilecektir. Pekiyi her sözcüğün bir vektörle ifade edilmesinin anlamı nedir? Bu vektör nasıl oluşturulmaktadır?
Bu vektörler oluşturulduktan sonra bunların arasında Öklit uzaklığı (yani uzayda iki nokta arasındaki uzaklık) birbirine yakın sözcüklerin daha az
biribirine uzak sözcüklerin daha fazla olacağı biçimdedir. Örneğin sözcükleri iki elemanlı vektörlerle temsil edelim. Bu iki elemanlı vektörler düzlemde
(iki boyutlu uzayda) bir nokta belirtirler. Bu durumda birbirlerine yakın anlamdaki sözcükler birbirlerine daha yakın, uzak anlamdaki sözcükler
daha uzak olacaktır. Tabii buradaki vektörler örneğin 32 eleman uzunluğunda olursa aslında 32 boyutlu bir uzaydaki nokta gibi ele alınmaktadır.
Pekiyi bu vektörler nasıl oluşturulmaktadır? Bu konuda çeşitli algoritmalar önerilmiştir. Örneğin Google'ın "Word2Vect" algoritması Stanford'ın "GloVe"
algoritması Facebook'un "fastText" algoritması en fazla kullanılanlardandır. Tensorflow ve Keras "Word2Vect" algoritmasını kullanmaktadır.
Bu algoritmalar biraz karmaşık biçimdedir. Uygulamacının bu algoritmaların işleyişini bilmesine gerek yoktur. Ancak bu algoritmalar eğitim sırasında
bu vektörleri oluşturan algoritmalardır.
Word embedding işlemleri aslında sözcüklerden anlam çıkartmaya ve onları bir bağlama oturtmaya çalışmaktadır. Bu nedenle word embedding
işlemlerinin uygulanması yazılardan anlam çıkartlması biçimindeki faaliyetlerde iyileşme sağlamaktadır.
Keras'ta word embedding işlemleri katmanlarla yapılmaktadır. Bu işlemi yapan Embedding denilen bir katman vardır. Uygulamacı tipik olarak
ın girdi katmanını Embedding katmanına, bu katmanın çıktılarını diğer ara katmanlara bağlamaktadır. Embedding katmanının ilk iki parametresi
zorunlu parametrelerdir. Birinci parametre tüm yazılardaki tüm sözcüklerin (vocabulary) sayısını belirtmektedir. İkinci parametre ise her sözcük için oluşturulacak vektörün uzunluğunu belirtmektedir. Genellekle bu değerler 8, 16, 32, 64 biçiminde alınmaktadır. input_length parametresi yazıların sözcük uzunluğunu belirtmektedir.
Burada tüm yazıların aynı miktarda sözcüklerden oluşması gerekmektedir. Embedding katmanı için her yazının aynı miktarda sözcükten oluşması gerektiğini belirtmiştik. (Halbuki vektörüzasyon işleminde böyle bir gereklilik yoktur.) Oysa gerçekte her yazı (örneğin yorum) farklı miktarda sözcükten oluşabilmektedir. O halde uygulamacının
her yazıyı sanki eşit miktarda sözcükten oluşuyormuş gibi bir biçime dönüştürmesi gerekmektedir. Bunun için genellikle "padding" yöntemi
kullanılmaktadır. Padding yazının başının ya da sonunun boş sözcüklerle doldurulması işlemidir. Bunun için tensorflow.keras.utils modülü üçeisindeki
pad_sequences fonksiyonu kullanılabilir. Embedding katmanının kullanımına şöyle bir örnek verilebilir:
model.add(Embedding(30000, 32, input_length=100))
Burada tüm yazılardaki tüm sözcükler 30000 tanedir. Yazıdaki her sözcük 32 elemanlı bir vektörle temsil edilecektir. Her yazı ise 100 sözcükten
oluşacaktır.
Embedding katmanının girdisi (yani modelin girdi katmanı) yazıdaki sözcük indekslerinin numaralarından oluşmalıdır. Bu durumda uygulamacının
önce yine vocabulary'deki her sözcüğe birer numara vermesi sonra da yazıları bu numaralardan oluşan birer dizi haline getirmesi gerekir.
Embedding katmanındaki eğitilebilir parametrelerin sayısı "vocabulary sayısı * vektör uzunluğu" kadardır. Yani yukarıdaki örnekte Embedding katmanındaki
parametre sayısı 30000 * 32 tane olacaktır.
pad_sequences fonksiyonun parametrik yapısı şöyledir:
tf.keras.utils.pad_sequences(sequences, maxlen=None, dtype='int32', padding='pre', truncating='pre', value=0.0)
pad_sequences fonksiyonu her biri dolaşılabilir nesnelerden oluşan dolaşılabilir nesneleri parametre olarak almaktadır. (Örneğin NumPy dizilerinden oluşan listeler olabilirya da listelerden oluşan listeler olabilir.) İkinci parametre hedeflenen sütun uzunluğunu belirtir. dtype parametresi hedef matristeki
elemanların dtype türünü belirtmektedir. padding ve trucanting parametreleri padding ve kırma işleminin baş taraftan mı son taraftan mı yapılacağını belirtmektedir.
Burada 'pre' baş tarafı 'post' son tarafı belirtir. value parametresi ise padding yapılacak değeri belirtmektedir. İşlemin sonucunda iki boyutlu bir NumPy dizisi elde edilir. Örneğin:
from tensorflow.keras.utils import pad_sequences
a = [[1, 2, 3], [3, 4, 5, 6, 7], [10], [11, 12]]
result = pad_sequences(a, 20, padding='post')
print(result)
Embedding katmanının çıktısı iki boyutludur. Çıktı yazı sayısı kadar satırlardan oluşan vektör uzunluğu kadar sütunlardan oluşan bir yapıdadır.
Embedding katmanı Dense katmanlara bağlanacaksa Dense katmanlar tek boyutlu girdi istedikleri için bu katmanlardan önce Flatten katmanıyla
çıktının tek boyuta indirgenmesi gerekmektedir. Bu durumda IMDB örneği için katman yapısı şöyle ayarlanabilir:
Girdi Katmanı --> Embedding --> Flatten --> Dense --> Dense --> Dense (Sigmoid)
Aşağıda IMDB veri kümesinde word embedding uygulaması örnek olarak verilmiştir. Biz bu uygulamada önce Embedding katmanını kullandık.
Sonra bu katmanın çıktısını Flatten katmanıyla düzleştirdikten sonra iki Dense katmana soktuk. Bu örnekte her bir yorumun 250 sözcükten
oluşmasını sağladık. Yukarıda da belirtildiği gibi bu tür uygulamalarda sözcük sayıları ortalama bir değerde tutulabilir ya da en uzun yazının
sözcük uzunluğu olarak alınabilir. Yine bu örneğimizde word embedding işleminden her sözcük için 64'lük bir vektör elde ettik. Duruma göre buradaki
vektör uzunluğu ikinin kuvvetleri olacak biçimde (bunun çok özel bir anlamı ypktur) 128, 256 alınabilir.
#----------------------------------------------------------------------------------------------------------------------------
TExT_SIZE = 250
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
cv.fit(df['review'])
import numpy as np
dataset_y = np.zeros(len(df), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
import re
text_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
from tensorflow.keras.utils import pad_sequences
dataset_x = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, Flatten, Dense
model = Sequential(name='IMDBEmbedding')
model.add(Embedding(len(cv.vocabulary_), 64, input_length=TExT_SIZE, name='Embedding'))
model.add(Flatten(name='Flatten'))
model.add(Dense(128, activation='relu', name='Dense-1'))
model.add(Dense(128, activation='relu', name='Dense-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(patience=5, restore_best_weights=True)
hist = model.fit(training_dataset_x, training_dataset_y, epochs=100, batch_size=32, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_texts = ['the movie was very good. The actors played perfectly. I would recommend it to everyone.', 'this film is awful. The worst film i have ever seen']
predict_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in predict_texts]
predict_data = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
predict_result = model.predict(predict_data)
for result in predict_result[:, 0]:
if result > 0.5:
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de word embedding işlemleri için görsel bir açıklama üzerinde duralım. Word embedding algoritmaları semantik olarak
birbirine yakın sözcüklerin Öklit uzaklıklarının daha yakın, semantik olarak biribine uzak sözcüklerin Öklit uzaklıklarının daha
uzak olacak biçimde ayarlama yapmaktadır. Biz de aşağıda IMDB örneğinden hareketle bu durumu görsel olarak göstereceğiz.
Biz burada Embedding katmanınan 2'li vektörler elde edip bu ikili değerleri grafikte noktalarla göstereceğiz. Böylece görsel olarak
hangi sözcüklere ilişkin noktaların biribirine yakın olduğu anlaşılabilecektir.
Keras'ta biz daha önce belli bir katmanın ağırlık değerlerini katman sınıflarının get_weights metotlarıyla elde etmiştik. Aslında
Keras bize belli bir katmanın çıktı nöronlarının değerlerini verebilmektedir. Yani biz isterssek belli bir katmana giriş uygulayıp ondan
çıktı nöronlarının değerlerini alabiliriz. Bunun için tensorflow.keras.backend modülündeki function isimli fonksiyon kullanılmaktadır.
Biz bu fonksiyona katmanın geri ve çıktı tensörlerini verdiğimize fonksiyon bize bir fonksiyon verir. O fonksiyon da bizden girdiyi alıp
katmanın çıktılarını verecektir.
#----------------------------------------------------------------------------------------------------------------------------
TExT_SIZE = 250
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
cv.fit(df['review'])
import numpy as np
dataset_y = np.zeros(len(df), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
import re
text_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
from tensorflow.keras.utils import pad_sequences
dataset_x = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, Flatten, Dense
model = Sequential(name='IMDBEmbedding')
model.add(Embedding(len(cv.vocabulary_), 2, input_length=TExT_SIZE, name='Embedding'))
model.add(Flatten(name='Flatten'))
model.add(Dense(128, activation='relu', name='Dense-1'))
model.add(Dense(128, activation='relu', name='Dense-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(patience=5, restore_best_weights=True)
hist = model.fit(training_dataset_x, training_dataset_y, epochs=100, batch_size=32, validation_split=0.2, callbacks=[esc])
from tensorflow.keras.backend import function
embedding_func = function(model.layers[0].input, model.layers[0].output)
text = 'good bad awful perfect extraordinary well impressive average magnificent disgusting best poor ok terrible cool worst'
words = re.findall(r'(?u)\b\w\w+\b', text)
word_indexes = [cv.vocabulary_[word] for word in words]
input_vect = pad_sequences([word_indexes], TExT_SIZE, padding='post', truncating='post')
output_vect = embedding_func(input_vect)
word_values = output_vect[0, :len(word_indexes), :]
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 10))
plt.title('Word Embedding Visual Representation')
plt.scatter(word_values[:, 0], word_values[:, 1])
for i, name in enumerate(words):
plt.annotate(name, (word_values[i, 0] + 0.005, word_values[i, 1] + 0.005))
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda IMDB örneğinin tensorflow.keras.datasets içerisindeki hazır verilerek kullanılarak gerçekleştirimi verilmiştir.
Keras'taki bu hazır veri kümesi zaten bize yazıları sözcük indeksleri olarak vermektedir. Ayrıca get_word_index fonksiyonu da
bize vocabulary sözlüğü vermektedir. load_data fonksiyonunda istenirse num_words parametresi ile yorum yazılarının en fazla kullanılan
n tane sözcükten oluşması sağlanabilmektedir. Bu parametre girilmezse toplam vocabulary genişliği get_word_index fonksiyonuyla verilen
sözlükteki eleman sayısından bir fazladır.
#----------------------------------------------------------------------------------------------------------------------------
TExT_SIZE = 250
from tensorflow.keras.datasets import imdb
(training_dataset_x , training_dataset_y), (test_dataset_x, test_dataset_y) = imdb.load_data()
from tensorflow.keras.utils import pad_sequences
training_dataset_x = pad_sequences(training_dataset_x, TExT_SIZE, padding='post', truncating='post')
test_dataset_x = pad_sequences(test_dataset_x, TExT_SIZE, padding='post', truncating='post')
vocabulary = imdb.get_word_index()
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, Flatten, Dense
model = Sequential(name='IMDBEmbedding')
model.add(Embedding(len(vocabulary) + 1, 64, input_length=TExT_SIZE, name='Embedding'))
model.add(Flatten(name='Flatten'))
model.add(Dense(128, activation='relu', name='Dense-1'))
model.add(Dense(128, activation='relu', name='Dense-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(patience=5, restore_best_weights=True)
hist = model.fit(training_dataset_x, training_dataset_y, epochs=100, batch_size=32, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_texts = ['the movie was very good. The actors played perfectly. I would recommend it to everyone.', 'this film is awful. The worst film i have ever seen']
import re
predict_vectors = [[vocabulary[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in predict_texts]
predict_data = pad_sequences(predict_vectors, TExT_SIZE, padding='post', truncating='post')
predict_result = model.predict(predict_data)
for result in predict_result[:, 0]:
if result > 0.5:
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Birbirini izleyen ve aralarında sırasal bir bağlantı olan verilere "zamansal veriler (temporal data)" denilmektedir.
Örneğin yağmurun yağıp yağmayacağını tahmin etmek için her 10 dakikada bir ölçüm alındığını düşünelim. Bu ölçüm verileri
zamansal verilerdir. Çünkü birbirinden kopuk değil bir sıra izlemektedir. Yağmur bir anda yağmamaktadır. Bir süreç içerisinde
yağmaktadır. Belli bir andaki ölçüm değerlerinden yağmurun yağıp yağmayacağı anlaşılmayabilir. Ancak geriye doğru bir grup ölçüm bize
gidişat hakkında daha iyi bilgiler verebilir. İşte eğitim sırasında verilerin kopuk kopuk değil peşi sıra bir bağlam
içerisinde değerlendirilmesi önemli olmaktadır. Bu işlemin "evrişim (convolution)" yoluyla yapılabildiğini görmüştük.
Zamansal veriler yukarıdaki yağmurun yağıp yağmayacağına ilişkin 10'ar dakikalık ölçümler gibi olmak zorunda değildir. Aslında
yazılardaki sözcükler de bu bağlamda zamansal verilere benzemektedir. Yazıdaki sözcüklerin anlamı ondan önce gelen ve ondan sonra gelen sözcüklerle
daha iyi anlaşılabilir. O halde yazıların anlamlandırılmasında da evrişim işlemi uygulanabilir. Biz daha önce resimler üzerinde
evrişim uygulamıştık. Oradaki evrişim işlemine "iki boyutlu evrişim işlemi" denilmektedir. Bunun nedeni o örneklerde alınan filtrenin (kernel)
iki yönlü (sağa ve aşağıya) kaydırılmasıdır. İşte zamansal verilerde uygulanan evrişim tek boyutludur. Tek boyutlu evrişim demek filrenin tek
boyutta kaydırılması demektir. Metin anlamlandırma işlemlerinde tek boyutlu evrişim uygulanmaktadır.
Tek boyutlu evrişim işleminde filtre büyüklüğü tek boyutludur (yani tek bir sayıdna oluşur). Bu sayı evrişime sokulacak satırların
sayısını belirtmektedir. Filtrenin genişliği evrişime sokulacak verilerin sütun sayısı kadardır. Örneğin:
x x x x x x x x x x
x x x x x x x x x x
x x x x x x x x x x
x x x x x x x x x x
x x x x x x x x x x
x x x x x x x x x x
x x x x x x x x x x
...................
Bunlar evrişime sokulacak verileri temsil ediyor olsun. Filteyi (kernel) 3 olarak olarak almış olalım. Bu durumda filtre
aşağıdaki gibi bir yapıya sahip olacaktır:
F F F F F F F F F F
F F F F F F F F F F
F F F F F F F F F F
Buradaki filte ilk üç satır ile çakıştırılır. Dot-product yapılır ve bir değer elde edilir. Sonra filtre aşağıya doğru kaydırılır ve aynı işlem yinelenir.
Eğer padding uygulanırsa sonuç olarak elde edilecek matris "asıl verinin satır sayısı x 1" büyüklüğünde olacaktır.
Keras'ta tek boyutlu evirişim işlemi için Conv1D katman sınıfı bulundurulmuştur. Conv1D sınıfının __init__ metodunun ilk parametresi
filtre sayısını belirtir. İkinci parametre filtrenin (kernel) boyutunu belirtmektedir. Bu tek bir sayıdan oluşur. (Yani yukarıdaki örnektefiltrenin satır uzunluğu)
Yine metodun strides ve padding parametreleri vardır. Bu padding parametresi "valid" ise padding uygulanmaz, "same" ise padding uygulanır.
Aşağıda IMDB örneğinde word embedding yapıldıktan sonra bir kez tek boyutlu evrişim işlemi uygulanmıştır. Modeli nkatmanları şöyledir:
Embedding --> Conv1D --> Flatten --> Dense --> Dense --> Dense (Output)
#----------------------------------------------------------------------------------------------------------------------------
TExT_SIZE = 250
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
cv.fit(df['review'])
import numpy as np
dataset_y = np.zeros(len(df), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
import re
text_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
from tensorflow.keras.utils import pad_sequences
dataset_x = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, Conv1D, Flatten, Dense
model = Sequential(name='IMDBEmbedding')
model.add(Embedding(len(cv.vocabulary_), 64, input_length=TExT_SIZE, name='Embedding'))
model.add(Conv1D(64, 3, padding='same', activation='relu', name='Conv1D'))
model.add(Flatten(name='Flatten'))
model.add(Dense(128, activation='relu', name='Dense-1'))
model.add(Dense(128, activation='relu', name='Dense-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(patience=5, restore_best_weights=True)
hist = model.fit(training_dataset_x, training_dataset_y, epochs=10, batch_size=32, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_texts = ['the movie was very good. The actors played perfectly. I would recommend it to everyone.', 'this film is awful. The worst film i have ever seen']
predict_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in predict_texts]
predict_data = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
predict_result = model.predict(predict_data)
for result in predict_result[:, 0]:
if result > 0.5:
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Aslında tıpkı resimlerde olduğu gibi bu tür zamansal verilerde evrişim işlemi bir kez değil birden çok kez uygulanmalıdır.
Yine evrişimler arasında nöron sayılarını azaltmak için Pooling işlemi uygulanabilir. Ancak Conv1D katmanından sonra uygulanacak
Pooling işleminin de tek boyutlu olması gerekmektedir. Bunun için MaxPooling1D ve AveragePooling1D sınıfları bulundurulmuştur.
Tek boyutlu pooling işlemlerinde pool_szie için tek bir sayı belirtilmektedir. Bu sayı satır sayısıdır. Pooling işlemi
burada belirtilen satır sayısı kadar satır üzerinde ve onların her sütununda uygulanmaktadır. Örneğin pooling işlemine sokacağımız
veriler şöyle olsun:
x x x x x x x x x x
x x x x x x x x x x
x x x x x x x x x x
x x x x x x x x x x
x x x x x x x x x x
x x x x x x x x x x
x x x x x x x x x x
...................
Burada MaxPooling1D sınıfını kullanıp pool_Size parametresini 3 geçirelim bu durumda ilk üç satır ele alınıp onların sütunlarının
en büyük elemanları elde edilecektir. Sonra default durumda pencere üç aşağıya kaydırılıp aynı işlem o üçlü için de yapılacaktır.
Bu işlemin sonucunda aynı sütun sayısına sahip ancak satır sayısı üç kat daha az olan bir matris elde edilir.
Aşağıdaki örnekte IMDB veri kümesi üzerinde önce word embedding sonra evrişim ve pooling işlemleri uygulanmıştır. Modelin katmanları şöyledir:
Embedding --> Conv1D --> MaxPooling1D --> Conv1D --> MaxPooling1D --> Flatten --> Dense --> Dense --> Dense (Output)
#----------------------------------------------------------------------------------------------------------------------------
TExT_SIZE = 250
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
cv.fit(df['review'])
import numpy as np
dataset_y = np.zeros(len(df), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
import re
text_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
from tensorflow.keras.utils import pad_sequences
dataset_x = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, Conv1D, MaxPooling1D, Flatten, Dense
model = Sequential(name='IMDBEmbedding')
model.add(Embedding(len(cv.vocabulary_), 64, input_length=TExT_SIZE, name='Embedding'))
model.add(Conv1D(64, 3, padding='same', activation='relu', name='Conv1D-1'))
model.add(MaxPooling1D(2, name='MaxPooling1D-1'))
model.add(Conv1D(64, 3, padding='same', activation='relu', name='Conv1D-2'))
model.add(MaxPooling1D(2, name='MaxPooling1D-2'))
model.add(Flatten(name='Flatten'))
model.add(Dense(128, activation='relu', name='Dense-1'))
model.add(Dense(128, activation='relu', name='Dense-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(patience=5, restore_best_weights=True)
hist = model.fit(training_dataset_x, training_dataset_y, epochs=10, batch_size=32, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_texts = ['the movie was very good. The actors played perfectly. I would recommend it to everyone.', 'this film is awful. The worst film i have ever seen']
predict_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in predict_texts]
predict_data = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
predict_result = model.predict(predict_data)
for result in predict_result[:, 0]:
if result > 0.5:
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda zamansal (temporal) verilerden bahsetmiştik. Belli periyotlarda toplanan veriler, yazıdaki sözcükler, bir videodaki
frame'ler tipik zamansal verilerdir. Biz yukarıda zamansal veriler üzerinde tek boyutlu evrişim işlemi yaparak bağlamsal bir etki
yaratmaya çalıştık. İşte bağlamsal etki yaratmanın diğer bir yolu da "geri beslemeli ağlardır (recurrent neural network kısaca RNN)".
Geri beslemeli ağlar aslında bir hafıza oluşturmaya çalışır. Bu ağların temel fikri çıkışın girişle işleme sokularak
ağa uygulanmasıdır. Örneğin zamansal verilerde bir sonraki giriş ile bir önceki çıkış işleme sokulur ve yeni giriş olarak uygulanır.
Çıkış ile giriş sürekli bir biçimde işleme sokulup girdi oluşturulursa bu durum bir hazfızanın oluşmasını sağlamaktadır.
Örneğin bir yazıda "adam mağazaya girdi, sonra bir kitap aldı" cümlesinde kitabı alanın mağazaya giren kişi olduğu sonucunun çıkartılması
için bir hafıza gerekmektedir. Çıkışın girişe uygulanması Markov zincirlerini de çağrıştırmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şimdiye kadar üzerinde çalıştığımız ağlara "ileri beslemeli ağlar (feed forward netwoks)" diyebiliriz. Halbuki biz bu bölümde
"geri beslemeli ağlar (feed backword networks)" üzerinde duracağız. Buradaki "geri besleme" İngilizce daha çok "recurrent" sözcüğü
ile ifade edilmektedir. Bu nedenle geri beslemeli ağlar için genellikle İngilizce "recurrent newural networks" terimi kullanılmaktadır.
Bu da "RNN" biçiminde kısaltılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Geri beslemeli ağlardaki geri besleme bir katman biçiminde oluşturulmaktadır. Bu katmanda yine n tane nörun bulunur. Ancak bu katmana
girdiler tek hamede değil parça parça verilir. Girdinin her parçasından bir çıktı elde edilir. Sonra bu çıktı girdinin sonraki
parçasıyla işleme sokularak yine geri besleme katmanına sokulur. Böylece katmanın her çıktısı sonraki girişle işleme sokularak
bir hafıza oluşturulmaya çalışılır. Tabii geri besleme katmanı genellikle tek başına kullanılmaz. Bu katmanın çıktısı daha öncedeleri yaptığımız
gibi dense katmanlara verilir. Yani geri besleme katmanı genellikle derin ağlardaki ilk katmanları oluşturmaktadır.
Geri besleme katmanında önceki çıktı sonraki girdi ile işleme sokulurken yine ağırlık değerleriyle dot-product yapılmaktadır.
Biz parçalı girdilerin geri besleme katmanına sokulması sırasında kullanılan ağırlık değerlerin W ile, çıktıların gidilerle
işleme sokulmasında kullanılan ağırlık değerlerini U ile gösterebiliriz. Bu durumda geri besleme katmanındaki nöronların çıkış
değerleri şöyle olacaktır:
geri besleme katmanındaki nöronların çıkış değerleri = activation(dot(W, xi+1) + dot(U, Oi) + b)
Pekiyi girdi katmanına her 32'lik vektörlerden oluşan zamansal değerler uygulanmış olsun. Geri besleme katmanında da toplam 64 nöronun bulunduğunu düşünelim.
Bu durumda 32'lik giriş uygulandığında geri besleme katmanının çıktısından 64 değer elde edilecektir. Bu 64 çıkış sonraki 32'lik girişle yukarıdaki
gibi işleme sokulacaktır. Pekiyi bu katmanda tahmin edilecek parametrelerin sayısı ne olacaktır? Her zamansal veri 64 tane nöronla dense
bağlandığına göre 32 * 64 tane W değeri söz konusu olacaktır. Öte yandan her 64'lük çıktı yeniden 64 nörona girdi yapılacağına göre 64 tane U değeri
söz konusu olacaktır. Nihayet 64 nöronun her birinin bir bias değeri olduğuna göre toplam bu katmandaki tahmin edilecek parametrelerin sayısı
32 * 64 + 64 * 64 + 64 = 6208 tane olacaktır.
Aşağıdaki örnekte bböyle bir katmanın nasıl hesap yaptığı rastgele verilerle oluşturulmuştur. Bu programda TIMESPEC parçalı verilerin sayısını (yani
kaç parça olduğunu), INPUT_FEATURES parçalı verilerin kaç elemanlı vektörler olduğunu, RECURRENT_LAYER_SIZE ise geri besleme katmanındaki nöron sayısını belirtmektedir.
Bu programda zamansal her veri uygulandığında katmandan elde edilen her çıktı biriktirilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
TIMESPEC = 100 # ardışıl uygulanacak eleman sayısı
INPUT_FEATURES = 32 # girdi katmanındaki nöron sayısı
RECURRENT_LAYER_SIZE = 64 # geri besleme katmanındaki nöron sayısı
def activation(x):
return np.maximum(0, x)
inputs = np.random.random((TIMESPEC, INPUT_FEATURES))
W = np.random.random((RECURRENT_LAYER_SIZE, INPUT_FEATURES))
U = np.random.random((RECURRENT_LAYER_SIZE, RECURRENT_LAYER_SIZE))
b = np.random.random((RECURRENT_LAYER_SIZE,))
outputs = []
output_t = np.random.random((RECURRENT_LAYER_SIZE,))
for input_t in inputs:
output_t = activation(np.dot(W, input_t) + np.dot(U, output_t) + b)
outputs.append(output_t)
total_outputs = np.concatenate(outputs, axis=0)
print(total_outputs)
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi yukarıdaki gibi bir geri besleme katmanının çıktısı nasıl diğer dense katmanlara bağlanacaktır? Burada iki durum söz konusu olabilir.
Bu geri besleme katmanının nihai tek bir çıktısı sonraki dense katmana bağlanabilir. Ya da her zamansal girdinin çıktıları briktirilip
düzleştirildikten sonra dense katmana bağlanabilir. Yalnızca son parçalı girdinin dense katana bağlanması bilgi kayıplarına yol açmaktadır.
Dolayısıyla bunların biriktirilip düzleştirildikten sonra dense katmana verilmesi daha uygun bir yöntemdir.
Aslında Keras'ta yukarıdaki işlemin aynısını yapan SimpleRNN isimli bir katman zaten hzaır biçimde bulunmaktadır. SimpleRNN sınıfının
__init__ metodunun parametrik yapısı şöyledir:
tf.keras.layers.SimpleRNN(
units,
activation='tanh',
use_bias=True,
kernel_initializer='glorot_uniform',
recurrent_initializer='orthogonal',
bias_initializer='zeros',
kernel_regularizer=None,
recurrent_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
recurrent_constraint=None,
bias_constraint=None,
dropout=0.0,
recurrent_dropout=0.0,
return_sequences=False,
return_state=False,
go_backwards=False,
stateful=False,
unroll=False,
**kwargs
)
Buradaki nunits geri besleme katmanındaki nöron sayısını belirtir. Geri besleme katmanlarının default aktivasyon fonksiyonu "tanh"
biçiminde alınmıştır. Hiperbolik tanjant fonksiyonu bu katmanlarda en çok tercih edilen aktivasyon fonksiyonudur. Fonksiyonun diğer önemli parametresi
return_sequences isimli parametredir. Bu parametre True geçilirse (default durum False biçimdedir) bu durumda katman her zamansal girişin çıktılarını
biriktirir. Eğer bu parametre True geçilmezse bu biriktirme yapılmaz. Dolayısıyla sonraki katmana yalnızca son zamansal verinin çıktısı sokulur.
Pekiyi SimpleRNN katmanının girdisi nasıl olmalıdır? İşte bu katmanın girdisi bir matris olmalıdır. Matrisin her satırı zaman veriyi belirtmektedir.
Keras bu durumda bu matrisib her bir satırını zamansal veri biçiminde ele alır ve önceki çıktıyla işlkeme sokar.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda da belirtildiği gibi geri ebeslemeli ağlar aslında zamansal girdiler için kullanılmaktadır. Bir yazıyı anlamaya çalışan
ağlar da aslında yukarıda da belirtitğimiz gibi zamansal girdi biçimindedir. Tabii buradaki zamansal veriler aslında yazıdaki sözcüklerdir.
Bu sözcükler de bir vektörle ifade edilmelidir. Biz her sözcüğü bir vektörle ifade edebilmemiz gerekir. Bunun için ise akla word embedding
yöntemi gelmektedir. O halde yazsal örneklerde tipik olarak SimpleRNN katmanının girdisi Embedding katmanının çıktısı yapılır. Başka bir
deyişle yazı önce Embedding katmanına sokulur. Bu katmanın çıktısı SimpleRNN katmanına verilir. O zaman modelin katman yapısı
şöyle olacaktır:
Embedding --> SimpleRNN --> Dense ---> Dense ---> Dense (output)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Daha önce üzerinde çalıştığımız IMDB veri kümesi için aşağıdaki gibi bir geri beslemeli model kullanılabilir:
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, SimpleRNN, Flatten, Dense
model = Sequential(name='IMDB-SimpleRNN')
model.add(Embedding(len(cv.vocabulary_), 64, input_length=TExT_SIZE, name='Embedding'))
model.add(SimpleRNN(64, name='SimpleRNN', return_sequences=True))
model.add(Flatten(name='Flatten'))
model.add(Dense(128, activation='relu', name='Dense-1'))
model.add(Dense(128, activation='relu', name='Dense-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
Burada önce bir Embedding katman kullanılmıştır. Bu katmandan çıktı olarak her biri 64 sütundan, TExT_SIZE kadar satırdan oluşan
64'lü zamansal değerler elde edilmiştir. Bu 64'lü girişler 64 nörondan oluşan SimpleRNN katmanına zamansal girdi yapıkmıştır. SimpleRNN
katmanında return sequences=True parametresinin girildiğine dikkat ediniz. Bu durumda her bir sözcüğün çıktısı olan 64'lük vektörler
bir matris biçiminde biriktirilmiştir. Sonra bunlar Flatten katmanı ile düzleştirilip Dense katmanlara verilmiştir. Burada eğer SimpleRNN
katmanında return_sequences=True girilmezse Dense katmana en son elde edilen 64'lük çıktı verilir ki bu durum bu model için uygun değildir.
Aşağıda bu uygulamanın tüm kodları verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
TExT_SIZE = 250
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
cv.fit(df['review'])
import numpy as np
dataset_y = np.zeros(len(df), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
import re
text_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
from tensorflow.keras.utils import pad_sequences
dataset_x = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, SimpleRNN, Flatten, Dense
model = Sequential(name='IMDB-SimpleRNN')
model.add(Embedding(len(cv.vocabulary_), 64, input_length=TExT_SIZE, name='Embedding'))
model.add(SimpleRNN(64, name='SimpleRNN', return_sequences=True))
model.add(Flatten(name='Flatten'))
model.add(Dense(128, activation='relu', name='Dense-1'))
model.add(Dense(128, activation='relu', name='Dense-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(patience=5, restore_best_weights=True)
hist = model.fit(training_dataset_x, training_dataset_y, epochs=100, batch_size=32, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_texts = ['the movie was very good. The actors played perfectly. I would recommend it to everyone.', 'this film is awful. The worst film i have ever seen']
predict_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in predict_texts]
predict_data = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
predict_result = model.predict(predict_data)
for result in predict_result[:, 0]:
if result > 0.5:
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Aslında uygulamada geri besleme katmanı yalnızca bir tane değil birden fazla kullanılmaktadır. Her geri besleme hafızayı
biraz daha güçlendirebilmektedir. Tabii bir SimpleRNN katmanını başka bir SimpleRNN katmanına bağlayacaksak önceki SimpleRNN katmanında
return_sequences=True yapılmalıdır. Çünkü sonraki SimpleRNN katmanı öncekinde olduğu gibi zamansal (yani matris biçiminde) bir girdi
bekleyecektir. Birden fazla SimpleRNN katmanı kullanıldığında Dense katmanların sayısı azaltılabilir. Örneğin:
SimpleRNN --> SimpleRNN --> Dense(output)
Burada İkinci SimpleRNN katmanının çıktısı doğrudan çıktı katmanına verilmiştir.
Aşağıda IMDB örneğinde İki SimpleRNN katmanı bir de çıktı için Dense katman kullanılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
TExT_SIZE = 250
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
cv.fit(df['review'])
import numpy as np
dataset_y = np.zeros(len(df), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
import re
text_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
from tensorflow.keras.utils import pad_sequences
dataset_x = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, SimpleRNN, Flatten, Dense
model = Sequential(name='IMDB-SimpleRNN')
model.add(Embedding(len(cv.vocabulary_), 64, input_length=TExT_SIZE, name='Embedding'))
model.add(SimpleRNN(32, name='SimpleRNN-1', return_sequences=True))
model.add(SimpleRNN(32, name='SimpleRNN-2', return_sequences=True))
model.add(Flatten(name='Flatten'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(patience=5, restore_best_weights=True)
hist = model.fit(training_dataset_x, training_dataset_y, epochs=100, batch_size=32, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_texts = ['the movie was very good. The actors played perfectly. I would recommend it to everyone.', 'this film is awful. The worst film i have ever seen']
predict_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in predict_texts]
predict_data = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
predict_result = model.predict(predict_data)
for result in predict_result[:, 0]:
if result > 0.5:
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yukarıdaki örneklerden elde edilen sonuçlar aslında bu hali ile SimpleRNN katmanının model üzerinde ciddi bir iyileşme sağlamadığı
yönündedir. Daha çnce yapmış olduğumuz evrişim işlemi daha iyi bir sonucun elde edilmesine yol açmıştır. Pekiyi bu durumda
geri besleme IMDB örneğinde fayda sağlamayacak mıdır? Aslında geri besleme bir hazfıza oluşturmaktadır. Ancak SimpleRNN tek başına
bu hafıza oluşumu için yeterli olmamaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay sinir ağlarında "overfitting" durumunu azaltmak için kullanılan teniklere "düzenleme (regularization)" teknikleri denilmektedir.
Bu bağlamda çeşitli düzenleme teknikleri geliştirilmiştir. En yaygın tekinikler "L1 düzenlemesei", "L2 düzenlemesi" ve "dropout"
düzenlemeleridir. Biz burada dropout düzenlemeleri üzerinde duracağız. L1 ve L2 düzenlemeleri başka başlıklarda ele alınacaktır.
Dropout düzenlemesi 2014 yılında bazı deneysel çalışmalar eşliğinde bulunmuştur. Bu teknikte bir katmandaki nöronların bazıları rastgele biçimde
katmandan atılmaktadır. Böylece ağın zeberlediği yanlış şeylerin unutturulması sağlanmaktadır. Dropout uygularken belli bir olasılık belirtilir.
Bu olasılık o katmandaki nöronların atılma olasılığıdır. Tipik olarak 0.2 ile 0.5 arasındaki değerler çok kullanılmaktadır.
Bazı uygulamacılar girdi katmanlarında 0.8'e varan daha yüksek olasılıkları kullanmaktadır. Buradaki atılma olasılığı bir yüzde belirtmemektedir.
Buradaki olasılık katmandaki her nöron için ayrı ayrı uygulanan olasılıktır. Yani örneğin biz dropout olsılığını 0.1 yaptığımızda bu katmanın öncesindeki
katmanda 10 böron varsa bu durum bu 10 nöronun kesinlikle bir tanesinin atılacağı anlamına gelmemektedir. Dropout tüm çıktı katmanı dışındaki tüm katmanlara uygulanabilir.
Keras'ta dropout işlemi Dropout isimli bir katman ile temsil edilmiştir. Dropout sınıfının __init__ metdounun parametrik
yapısı şöyledir:
tf.keras.layers.Dropout(rate, noise_shape=None, seed=None, **kwargs)
Metodun birinci parametresi droput olasılığını belirtmektedir. Droput katmanı ondan önceki katmanın nöronlarını atmaktadır.
Eğitim sırasında hep batch işleminde katmandaki aynı nöronlar atılmamaktadır. Eğitim sırasında katmanın farklı nöronları atılarak
işlemler yapılmaktadır.
Katmandaki nöronların bir kısmı dropout işlemiyle atıldığında dot product sonucunda elde edilen değer toplamı azalır. Bu durumu
ortadan kaldırmak için genellikle uygulamacılar dropout işleminde atılmayan nöronların çıktılarını atılan nöronların olsılığı kadar artırırlar.
Yani örneğin bir katmandaki dropout olasılığı 0.20 ise bu katmanda atılmayan nöronların çıktıları da o oranda artırılmaktadır.
Matematiksel olarak bu işlem 1 / (1 - rate) olarak yapılmaktadır. Keras'ın Dropout katmanı böyle davranmaktadır.
Dropout işlemi yalnızca eğitimde uygulanan bir işlemdir. Ağ eğitildikten sonra test ve kestirim işlemlerinde dropout uygulanmaz.
Aşağıda dropout işleminin etkisine yönelik bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.layers import Dropout
import numpy as np
data = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], dtype='float')
dropout_layer = Dropout(0.8, input_dim=10)
result = dropout_layer(data, training=True)
print(result)
result = dropout_layer(data, training=True)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de IMDB örneğinde Dropout kullanalım. Aşağıdaki örnekte katman mimarisi şyle uygulanmıştır:
Embedding --> SimpleRNN --> Dropout(0.3) --> Dense ---> Dropout (0.3) --> Dense (output)
Buradaki dropout olasılıkları deneme yanılma yöntemiyle belirlenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
TExT_SIZE = 250
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
cv.fit(df['review'])
import numpy as np
dataset_y = np.zeros(len(df), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
import re
text_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
from tensorflow.keras.utils import pad_sequences
dataset_x = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, Dropout, SimpleRNN, Flatten, Dense
model = Sequential(name='IMDB-SimpleRNN')
model.add(Embedding(len(cv.vocabulary_), 64, input_length=TExT_SIZE, name='Embedding'))
model.add(SimpleRNN(64, name='SimpleRNN', return_sequences=True))
model.add(Dropout(0.3, name='Dropout-1'))
model.add(Flatten(name='Flatten'))
model.add(Dense(128, activation='relu', name='Dense-1'))
model.add(Dropout(0.3, name='Dropout-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(patience=5, restore_best_weights=True)
hist = model.fit(training_dataset_x, training_dataset_y, epochs=100, batch_size=32, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_texts = ['the movie was very good. The actors played perfectly. I would recommend it to everyone.', 'this film is awful. The worst film i have ever seen']
predict_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in predict_texts]
predict_data = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
predict_result = model.predict(predict_data)
for result in predict_result[:, 0]:
if result > 0.5:
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Zamansal verilerle ilgili bir örnek "hava sıcaklığının tahmin edilmesi" olabilir. Hava sıcaklığı birtakım faktörlere bağlıdır.
Belli veriler eşliğinde o andaki ya da belli zaman sonrasındaki hava durumu tahmin edilmeye çalışabilir. Örneğin biz birtakım ölümleri
alıp yarınki hava sıcaklığını bugünden tahmin etmeye çalışabiliriz. Tabii hava sıcaklığı bir anda değişmemektedir. Birbirini izleyen
birtakım olaylar bunda etkilidir. Aynı dırım örneğin borsalarda da benzerdir. Bir kağıdın fiyatı birtakım olaylar sonucunda bir bağlam içerisinde
değişmektedir.
Hava durumu tahmini için "Jena Climate" isimli bir veri kümesi vardır. Bu veri kümesi aşağıdaki bağlantıdan indirilebilir:
https://www.kaggle.com/datasets/mnassrib/jena-climate?resource=download
Bu veri kümesi 10'ar dakikalık periyotlarla havaya ilişkin birtkaım değerlerin ölçülerek saklanmasıyla oluşturulmuştur.
Veri sütunlardan biri (üçüncü sütun) hava sıcaklığını belirtmektedir. Bu veri kümesi çeşitli amaçlarla kullanılabilmektedir.
Örneğin tipik olarak "gelecek kestirimi" yani şimdiki değerlerden hareketle belli bir zaman sonraki hava sıcaklığının tahmin edilmesi gibi örneklerde
bu veri kümesinden deneme amaçlı faydalanılmaktadır.
Jena Climate örneğinde bizim amacımız belli bir zamandaki ölçüm değerinden hareketle bir gün sonraki hava ısısını tahmim etmek olsun.
Böyle bir modelin eğitimi için bizim bazı düzenlemeler yapmamız geekir. Burada eğitimde kullanılacak x değerlerine karşı gelen y değerleri (havanın ısısı)
bir gün sonraki değerler olmalıdır. Veri kümesinde bir gün sonraki değerler 24 * 60 // 10 = 144 ilerideki değerlerdir. O halde bizim
eğitim verilerini oluştururken her x için 144 ilerideki y değerini eşleştirmemiz gerekir. Bu işlemler çeşitli biçimlerde yapılabilir.
Ayrıca veri kğmesinde ölümün yapıldığı tarih ve zaman bilgisi de vardır. Tarih bilgisinin bizim için önemi yoktur. Ancak ölçümün yapıldığı zaman
önemli bir özellik olabilir. Bundan faydalanmamız uygun olabilir. Pekiyi zamansal veri hangi ölçek türündendir? İşte tarih ve zaman bilgileri
uygulamaya göre aralık ölçeğinde (nümerik) ya da nominal (kategorik) ölçekte değerlendirilebilir. Eğer zaman bilgisi kategorik bir bilgi olarak
ele alınacaksa "one hot encoding" yapılmalıdır. Nümerik olarak ele alınacaksa örneğin gece saat 12'den itibaren geçen dakika sayısı gibi
bir değer kullanılabilir. Bu işlemler aşağıdaki gibi yapılabilir:
TIME_PERIOD = 24 * 60 // 10 # 144
import pandas as pd
df = pd.read_csv('jena_climate_2009_2016.csv')
dttm = df.iloc[:, 0]
df.iloc[:, 0] = dttm.str[10:]
df = pd.get_dummies(df, columns=['Date Time'], dtype='int8')
dataset_df_y = df.iloc[:, 1]
dataset_df_x = df.drop('T (degC)', axis=1)
dataset_x = dataset_df_x.iloc[:-TIME_PERIOD].to_numpy()
dataset_y = dataset_df_y.iloc[TIME_PERIOD:].to_numpy()
Tabii bu işlemin yapılmasının başka alternatif yolları da vardır. Aşağıda örnek bir bütün olarak verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
TIME_PERIOD = 24 * 60 // 10
import pandas as pd
df = pd.read_csv('jena_climate_2009_2016.csv')
dttm = df.iloc[:, 0]
df.iloc[:, 0] = dttm.str[10:]
df = pd.get_dummies(df, columns=['Date Time'], dtype='int8')
dataset_df_y = df.iloc[:, 1]
dataset_df_x = df.drop('T (degC)', axis=1)
dataset_x = dataset_df_x.iloc[:-TIME_PERIOD].to_numpy()
dataset_y = dataset_df_y.iloc[TIME_PERIOD:].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.preprocessing import StandardScaler
mms = StandardScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense, Dropout
model = Sequential(name='Boston-Housing-Price')
model.add(Dense(128, activation='relu', input_dim=training_dataset_x.shape[1], name='Hidden-1'))
model.add(Dropout(0.2, name='Dropout-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dropout(0.2, name='Dropout-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(patience=10, restore_best_weights=True)
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=200, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Mean Absolute Error Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['mae'])
plt.plot(hist.epoch, hist.history['val_mae'])
plt.legend(['Mean Absolute Error', 'Validation Mean Absolute Error'])
plt.show()
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
Yukarıdaki Jena Climate örneğini şimdi de SimplRNN katmanıyla geri beslemeli ağ ile yapalım. Burada RECURRENT_TIME_PERIOD isimli
sembolik sabitimiz son kaç ölçümün değerinin geri beslemeye sokulacağını belirtmektedir. PREDICT_TIME_PERIOD ise ne kadar sonraki hava sıcaklığının
tahmin edileceğini belirtmektedir. Biz bu örnekte ardışık RECURRENT_TIME_PERIOD kadar bilgileri birere birer kaydırarak oluşturmak isterssekbu çok fazla
belleğin harcanmasına yol açacaktır. Bu tür durumlarda parçalı verilerle eğitim denenmelidir. Biz de aşağıdaki örnekte DataGenerator isimli
sınıf yoluyla parçalı eğitim uyguladık. Eğitim sırasında her defasında bizim sınıfımızın __getitem__ metodu çağrılacak ve biz de bu meotta
bir batch kadar bilgiyi verceğiz. Bu durumda bizim bu mettotta x verisi olarak (BATCH_SIZE, RECURRENT_TIME_PERIOD, x.shape[1]) boyutunda
bir bilgiyle geri dönmemiz gerekir. Benzer biçimde y verisi için de BATCH_SIZE kadar veri oluşturmamız gerekmektedir.
Buradaki örnekte örneğin biz saat 10:00'dan itibaren 10'ar dakika aralıklarla son 50 veriyi oluşturarak yarınki hava ısısını tahmin etmek
istemekteyiz. Yukarıdaki örnekte biz yalnızca tek bir veriden tahmin yapmıştık. Halbuki bu örnekte peşi sıra giden bir grup
veriyi kullanarak tahmin yapmak istemekteyiz.
#----------------------------------------------------------------------------------------------------------------------------
PREDICT_TIME_PERIOD = 24 * 60 // 10 # 144
RECURRENT_TIME_PERIOD = 50
BATCH_SIZE = 32
import pandas as pd
df = pd.read_csv('jena_climate_2009_2016.csv')
dttm = df.iloc[:, 0]
df.iloc[:, 0] = dttm.str[10:]
df = pd.get_dummies(df, columns=['Date Time'], dtype='int8')
dataset_y = df.iloc[:, 1].to_numpy()
dataset_x = df.drop('T (degC)', axis=1).to_numpy()
from sklearn.model_selection import train_test_split
temp_training_dataset_x, test_dataset_x, temp_training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, shuffle=False)
from sklearn.preprocessing import StandardScaler
mms = StandardScaler()
mms.fit(temp_training_dataset_x)
temp_scaled_training_dataset_x = mms.transform(temp_training_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
training_dataset_x, validation_dataset_x, training_dataset_y, validation_dataset_y = train_test_split(temp_scaled_training_dataset_x, temp_training_dataset_y, test_size=0.2, shuffle=False)
import numpy as np
from tensorflow.keras.utils import Sequence
class DataGenerator(Sequence):
def __init__(self, x, y, batch_size, shuffle=True):
super().__init__()
self.x = x
self.y = y
self.batch_size = batch_size
self.shuffle = shuffle
self.total_batch = (len(x) - (PREDICT_TIME_PERIOD + RECURRENT_TIME_PERIOD)) // self.batch_size
self.indexes = list(range(self.total_batch))
def __len__(self):
return self.total_batch
def __getitem__(self, batch_no):
start_index_x = self.indexes[batch_no] * self.batch_size;
start_index_y = start_index_x + RECURRENT_TIME_PERIOD + PREDICT_TIME_PERIOD
stop_index_y = start_index_x + RECURRENT_TIME_PERIOD + PREDICT_TIME_PERIOD + self.batch_size
y = np.zeros(self.batch_size)
x = np.zeros((self.batch_size, RECURRENT_TIME_PERIOD, self.x.shape[1]))
for i in range(self.batch_size):
x[i] = self.x[start_index_x + i:start_index_x + RECURRENT_TIME_PERIOD + i]
y = self.y[start_index_y: stop_index_y]
return x, y
def on_epoch_end(self):
if self.shuffle:
np.random.shuffle(self.indexes)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import SimpleRNN, Flatten, Dense, Dropout
model = Sequential(name='Jena-Climate')
model.add(SimpleRNN(32, name='SimpleRNN-1', input_shape=(RECURRENT_TIME_PERIOD, training_dataset_x.shape[1]), return_sequences=True))
model.add(Flatten(name='Flatten'))
model.add(Dropout(0.2, name='Dropout-1'))
model.add(Dense(128, activation='relu', input_dim=training_dataset_x.shape[1], name='Hidden-1'))
model.add(Dropout(0.2, name='Dropout-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(patience=10, restore_best_weights=True)
hist = model.fit(DataGenerator(training_dataset_x, dataset_y, BATCH_SIZE), validation_data=DataGenerator(validation_dataset_x, validation_dataset_y, BATCH_SIZE, False), batch_size=BATCH_SIZE, epochs=200, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Mean Absolute Error Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['mae'])
plt.plot(hist.epoch, hist.history['val_mae'])
plt.legend(['Mean Absolute Error', 'Validation Mean Absolute Error'])
plt.show()
eval_result = model.evaluate(DataGenerator(scaled_test_dataset_x, test_dataset_y, BATCH_SIZE))
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_data = test_dataset_x[1000:1000 + RECURRENT_TIME_PERIOD].reshape(1, RECURRENT_TIME_PERIOD, -1)
predict_result = model.predict(predict_data)
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
Geri beslemeli ağlarda (RNN) çıktnın bir sonraki girdi ile işlemi sokulması ağa belli bir hadıza kazandırmaktadır. Ancak bu hafıza
"vanishing gradient" denilen matematiksel problem yüzünden yüzeyselleşmektedir. Başka bir deyişle ağ eğitim sırasında öncekileri unutup son
veriler üzerinde hafıza oluşturabilmektedir. Başka bir deyişle oluşturulan hafıza "kısa süreli (short term)" olmaktadır.
Çıktının sürekli girdiye verilmesi ilk girdilerin belli bir zaman sonra unutulmasına yol açmaktadır. "Vanishing gradient" problemi
bunun matematiksel açıklmasına denilmektedir. Böylece geri beslemeli ağlar SimplRNN katmanıyla ancak kısa dönemli bir hafıza oluşturabilmektedir.
İşte geçmişin ağda daha iyi hatırlanması için "vanishing gradient" denilen problemi engellemek amacıyla ağda çeşitli değişiklikler önerilmiştir.
Bunlardan en önemlilerinden biri LSTM (Long Short Term Memory) modeldir.
LSTM modelinde yine SimpleRNN modelinde olduğu gibi bir önceki çıktı bir sonraki girdi ile işleme sokulmaktadır. Ancak ağa başka bir girdi
bileşeni daha eklenmiştir. Bu girdi bileşeni ağın geçmiş bilgileri unutmasını engellemeyi hedeflemektedir. LSTM katmanındaki
ağa eklenen ilave "carry" girişinin nasıl olup da "vanishing gradient" denilen probleme iyi geldiği konusu biraz karmaşıktır.
Ancak ağa eklenen bu yeni giriş ağın tahmin edilecek parametre sayısını da artrmaktadır. Keras'ta LSTM ağları için tensorflow.keras.layers
modülünde LSTM isimli bir sınıf bulundurulmuştur. Bu sınıfın genel kullanımı SimpleRNN sınıfına çok benzemektedir. LSTM katmanının
ağa eklediği eğitilebilir parametrelerin sayısı katmanın girdisi n tane nörondan çıktısı da m tane nörondan oluşmak üzere 4 * (n * m + m * m + m)
kadar olmaktadır.
Aşağıda daha önce yapmış olduğumuz IMDB örneğinin LSTM ile yeniden gerçekleştirimi verilmişti. Bu ağın başarısını diğer modellerle karşılaştırabilirsiniz.
#----------------------------------------------------------------------------------------------------------------------------
TExT_SIZE = 250
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
cv.fit(df['review'])
import numpy as np
dataset_y = np.zeros(len(df), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
import re
text_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
from tensorflow.keras.utils import pad_sequences
dataset_x = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, LSTM, Dropout, Flatten, Dense
model = Sequential(name='IMDB-SimpleRNN')
model.add(Embedding(len(cv.vocabulary_), 64, input_length=TExT_SIZE, name='Embedding'))
model.add(LSTM(64, name='LSTM', return_sequences=True))
model.add(Dropout(0.2, name='Dropout-1'))
model.add(Flatten(name='Flatten'))
model.add(Dense(64, activation='relu', name='Dense-1'))
model.add(Dropout(0.2, name='Dropout-21'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(patience=5, restore_best_weights=True)
hist = model.fit(training_dataset_x, training_dataset_y, epochs=100, batch_size=32, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_texts = ['the movie was very good. The actors played perfectly. I would recommend it to everyone.', 'this film is awful. The worst film i have ever seen']
predict_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in predict_texts]
predict_data = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
predict_result = model.predict(predict_data)
for result in predict_result[:, 0]:
if result > 0.5:
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Ağa uzun dönem hafıza kazandırmaya çalışan diğer bir yöntem de GRU (Gated Recurrent Unit) isimli yöntemdir. Bu yöntemde de yine
ağa üçüncü bir giriş uygulanmaktadır. GRU yöntemi de Keras'ta tensorflow.keras.layers modülündeki GRU katman sınıfıyla temsil edilmiştir.
Dolayısıyla uygulamacı LSTM yerine GRU katmanını kullandığında yöntemi değiştirmiş olur. GRU sınıfının parametrik yapısı da LSTM sınıfına benzemektedir.
LSTM ile GRU arasında şu farklılıklar söz konusudur:
- GRU katmanında uzun dönem hafıza kazandırmak için uygulanan üçüncü giriş 2 bileşene saipken, LSTM'de 3 bileşene sahiptir.
Solayısıyla GRU katmanı LSTM katmanına göre daha az eğitilebilir parametreye sahiptir. Genel GRU olarak katmanı LSTM katmanından daha
yalın görünümdedir.
- GRU katmanında daha az bileşen olduğu için bu katmanın eğitimi LSTM katmanına göre daha hızlı yapılabilmektedir. Ayrıca GRU katmanıdaha düşük miktardaki
eğitim verileri için bu nedenden dolayı daha uygun olabilmektedir.
- LSTM katmanı GRU katmanına göre daha iyi performans gösterme eğilimindedir. Yani ikisi arasındaki tercih hız ve duyarlılık ihtiyacına göre
değişebilmektedir.
Yukarıdaki IMDB örneğini aşağıda GRU katmanıyla yeniden veriyoruz.
#----------------------------------------------------------------------------------------------------------------------------
TExT_SIZE = 250
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
cv.fit(df['review'])
import numpy as np
dataset_y = np.zeros(len(df), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
import re
text_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
from tensorflow.keras.utils import pad_sequences
dataset_x = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, GRU, Dropout, Flatten, Dense
model = Sequential(name='IMDB-GRU')
model.add(Embedding(len(cv.vocabulary_), 64, input_length=TExT_SIZE, name='Embedding'))
model.add(GRU(64, name='GRU', return_sequences=True))
model.add(Dropout(0.2, name='Dropout-1'))
model.add(Flatten(name='Flatten'))
model.add(Dense(64, activation='relu', name='Dense-1'))
model.add(Dropout(0.2, name='Dropout-21'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(patience=5, restore_best_weights=True)
hist = model.fit(training_dataset_x, training_dataset_y, epochs=100, batch_size=32, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_texts = ['the movie was very good. The actors played perfectly. I would recommend it to everyone.', 'this film is awful. The worst film i have ever seen']
predict_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in predict_texts]
predict_data = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
predict_result = model.predict(predict_data)
for result in predict_result[:, 0]:
if result > 0.5:
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Geri beslemeli ağlarda biz önceki çıktıyı sonraki girdi ile ilişkilendiriyorduk. SimpleRNN katmanında "vanishing gradient" denilen
problem yüzünden geçmişe ilişkin hafıza bozuluyordu. Bunun için LSTM ve GRU geri beslemesi kullanılıyordu. Bu geri besleme modellerinde
geçmişin daha iyi anımsanması sağlanıyordu. Ancak önceki çıktının sonraki girdiyle işleme sokulması bazı durumlarda yeterli olmamaktadır.
Örneğin metinlerin anlamlandırılmasında önce bir şeyden bahsedilip sonra o şey hakkında bilgi verildiğinde önce bahsedilen şeyin ne olduğu ancak
sonradan anlaşılmaktadır. Örneğin bir kişinin bir dükkana girdiği belirtilmiş olabilir. Sonra da bu dükkanın eczane olduğu söylenmiş olabilir.
Bu durumda eğer o dükannın baştan eczane olduğu bilinse daha iyi bir çıkarım yapılabilir. Bazı dillerin gramerleri bu biçimde oluşturulmuştur.
Örneğin İngilizce'de "the book on the table ..." biçimindeki bir cümlede kitabın masanın üzerinde olduğu sonradan anlaşılmaktadır.
Ancak "masanın üzerindeki kitap ..." cümlesinde kitabın masaüstünde olduğu baştan anlaşılmaktadır. Tabii zamansal verilerin söz konusu olduğu bazı uygulamalarda çift yönlü geri beslemenin bir faydası olmamakta hatta modelin başarısını düşürmektedir. Örnein Jena Cliamate veri kümesinde çift yönlü bir beslemenin
ık bir faydası olmayacaktır.
İşte geri beslemenin önceki çıkışın sonraki girişle ilişkilendirilmesinin yanı sıra bunun tersinin de yapılması yani sonraki çıkışın önceki girişle
ilişkilendirilmesi daha iyi bir öğrenmeye yol açabilektedir. Bu tür geri beslemeli ağlara "çift yönlü (bidriectional)" geri beslemeli ağlar
denilmektedir. Çift yönlü geri besleme ağı daha karmaşık bir hale getirmektedr. Dolayısıyla eğitilebilir parametrelerin sayısını artırmaktadır.
Ancak bazı uygulamalarda daha iyi bir sonucun elde edilmesine olanak vermektedir.
Çift yönlü geri beslemenin her zaman tek yönlü geri beslemeden daha iyi sonuç vereceği söylenemez. Uygulamacının iki yöntemi de
denemesi tavsiye edilmektedir.
Keras'ta çift yönlü geri besleme işlemi tensorflow.keras.layers modülündeki Bidirectional sınıfı ile yapılmaktadır. Bu sınıf dekoratör kallıbı biçiminde
oluşturulmuştur. Biz bu sınıfa LSTM ya da GRU katman nesnelerini veririz. Sınıf da onu çift yönlü hale getirir. Sınıfın __init__
metodunun parametrik yapısı şöyledir:
tf.keras.layers.Bidirectional(
layer,
merge_mode='concat',
weights=None,
backward_layer=None,
**kwargs
)
Bu durumda bizim ağı çift yönlü yapmak için tek yapacağımız şey LSTM ya da GRU katmanını Bidirectional katmanına vermektir. Örneğin:
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, Bidirectional, LSTM, Dropout, Flatten, Dense
model = Sequential(name='IMDB-LSTM-Bidirectional')
model.add(Embedding(len(cv.vocabulary_), 64, input_length=TExT_SIZE, name='Embedding'))
model.add(Bidirectional(LSTM(64, name='LSTM', return_sequences=True), name='Bidirectional'))
model.add(Dropout(0.2, name='Dropout-1'))
model.add(Flatten(name='Flatten'))
model.add(Dense(64, activation='relu', name='Dense-1'))
model.add(Dropout(0.2, name='Dropout-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
Aşağıdaki örnekte IMDB veri kümesinde LSTM katmanı çift yönlü hale getirilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
TExT_SIZE = 250
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
cv.fit(df['review'])
import numpy as np
dataset_y = np.zeros(len(df), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
import re
text_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
from tensorflow.keras.utils import pad_sequences
dataset_x = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, Bidirectional, LSTM, Dropout, Flatten, Dense
model = Sequential(name='IMDB-LSTM-Bidirectional')
model.add(Embedding(len(cv.vocabulary_), 64, input_length=TExT_SIZE, name='Embedding'))
model.add(Bidirectional(LSTM(64, name='LSTM', return_sequences=True), name='Bidirectional'))
model.add(Dropout(0.2, name='Dropout-1'))
model.add(Flatten(name='Flatten'))
model.add(Dense(64, activation='relu', name='Dense-1'))
model.add(Dropout(0.2, name='Dropout-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(patience=5, restore_best_weights=True)
hist = model.fit(training_dataset_x, training_dataset_y, epochs=100, batch_size=32, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_texts = ['the movie was very good. The actors played perfectly. I would recommend it to everyone.', 'this film is awful. The worst film i have ever seen']
predict_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in predict_texts]
predict_data = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
predict_result = model.predict(predict_data)
for result in predict_result[:, 0]:
if result > 0.5:
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte IMDB veri kğmesinde GRU katmanı çift yönlü hale getirilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
TExT_SIZE = 250
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
cv.fit(df['review'])
import numpy as np
dataset_y = np.zeros(len(df), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
import re
text_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
from tensorflow.keras.utils import pad_sequences
dataset_x = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, Bidirectional, GRU, Dropout, Flatten, Dense
model = Sequential(name='IMDB-GRU')
model.add(Embedding(len(cv.vocabulary_), 64, input_length=TExT_SIZE, name='Embedding'))
model.add(Bidirectional(GRU(64, name='GRU', return_sequences=True), name='Bidirectional'))
model.add(Dropout(0.2, name='Dropout-1'))
model.add(Flatten(name='Flatten'))
model.add(Dense(64, activation='relu', name='Dense-1'))
model.add(Dropout(0.2, name='Dropout-21'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(patience=5, restore_best_weights=True)
hist = model.fit(training_dataset_x, training_dataset_y, epochs=100, batch_size=32, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_texts = ['the movie was very good. The actors played perfectly. I would recommend it to everyone.', 'this film is awful. The worst film i have ever seen']
predict_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in predict_texts]
predict_data = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
predict_result = model.predict(predict_data)
for result in predict_result[:, 0]:
if result > 0.5:
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Metinsel çıktı üretimi (text generation) "yapay yaratıcılık (artificial creativity)" alanında önemli bir uygulama konusudur.
Bu sayede belli bir tarza uygun yapay yazılar üretilebilmektedir. Bu tür uygulamalarda çeşitli modeller kullanılabilmektedir.
Biz basit bir model üzerinde duracağız. Elimizde belli bir yazarın yazmış olduğu metinler bulunuyor olsun. Biz bu metinlerdeki
harfleri tahmin edebilecek bir sinir ağı modeli oluşturabiliriz. Bunun için belli bir karakter uzunluğunda bir yazıdan sonra gelen karakterleri
elde ederiz. Böylece problemi belli uzunlukta yazıdan sonra gelen karakterin tahmin edilmesi haline dönüştürebiliriz. Örneğin yazı parçalarını
30 karakter olarak belirleyelim. Yazarın her 30 karakterden oluşan yazı parçalarını ve ondan sonra gelen karakterleri elde edip
bunu bir veri kümesi biçimine dönülştürebiliriz. Böylece 30 karakter verildiğinde 31'inci karakteri tahmin edebilen bir
model elde edilmiş olur. Biz de ilk 30 karakter uydurarak sürkli kaydırma yöntemiyle 31'inci karakteri tahmin ede ede yapay bir metin oluşturabiliriz.
Buradaki model çok sınıflı lojistik regresyon problemidir. Tabii burada 30 karakterli yazıların her bir karakterinin one hot encoding
yöntemi ile sayısallaştırılması gerekmektedir. Benzer biçimde 30 karakterden sonra gelen ve y verilerini oluşturankarakterlerin de
one hot encoding yapılması gerekir.
Pekiyi yukarıdaki gibi bir uygulamayı karakter temelinde değil de sözcük temelinde yapabilir miyiz? Burada sözcük temelinde yapmak için önemli bir problem
lojistik regresyondaki sınıf sayılarının fazlalığı olacaktır. Çünkü bu modelde her sözcük ayrı bir kategoriyi temsil edecektir. Oysa her bir karakterin
ayrı bir kategoriyi temsil etmesi çok daha az sayıda sınfın oluşması anlamına gelir. Ancak yine de yeteri miktarda veri ve CPU güzü varsa
bu uygulama sözcük temelinde de yürütülebilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Keras'ın Sequential modelinde Sequential sınıfının add metoduyla modele katman nesnelerini ekliyorduk. Ancak bu katman nesneleri hep
modelin sonuna ekleniyordu. Ayrıca Sequential modelde yalnızca bir tane girdi katmanı ve yalnızca bir tane çıktı katmanı
bulunabiliyordu. Oysa bazı uygulamalarda girdiler ve çıktılar birden fazla çeşit olabilmektedir. Örneğin ağın girdisi hem bir resim
hem de bir yazı olabilmektedir. Benzer biçimde çıktı da hem bir kategorik değer hem de gerçek bir değerden oluşabilir.
Bir resim ve bir yazı içeren girdiler söz konusu olsun. Kişi resme balkıp ilgili soruyu yanıtlıyor olsun. Burada girdi yalnızca bir resim değil
aynı zamanda bir metin de içermektedir. Biz şimdiye kadar yalnızca resimlerden ve yazılardan girdiler oluşturduk. Bunların ikisini bir arada
kullanmadık. Böyle bir modelin girdisi için iki girdi katmanının bulunyor olması gerekmektedir. Halbuki Sequentil modelde modelin
tek bir girdi katmanı olabilmektedir. Benzer biçimde bazen çıktının da birden fazla olması istenebilmektedir. Örneğin ağ hem bir yazının kategorisini
belirleyebilir hem de yazıdaki beğeni miktarını tespşt etmeye çalışabilir. Birden fazla çıktı katmanına sahip olan modeller de
Sequential sınıfı ile oluşturulamamaktadır.
İşte bu tür gereksinimlerden dolayı Sequential model yetersiz kalabilmektedir. Bu nedenle modele "fonksiyonel" bir alternatif oluşturulmuştur.
Fonksiyonel model daha esnektir.
Fonksiyonel modelde katman nesneleri ayrı ayrı yaratılır. Bunların birbirlerine bağlanması katman sınıflarının "fonksiyon çağırma
operatör metotları (__call__ metotları)" yoluyla yapılmaktadır. Girdi için Input isimli hazır bir fonksiyon kullanılmaktadır.
Input fonksiyonu aslında tensorflow'da bir Tensor nesnesini temsil etmektedir.
Örneğin:
inp = Input(...)
d1 = Dense(...)
result = d1(inp)
d2 = Dense(...)
result = d2(result)
d3 = Dense(...)
result = d3(result)
d4 = Dense(...)
out = d4(result)
Burada önce Input fonksiyonu ile girdi tensörü oluşturulmuştur. Sonra bu girdi tensörü Dense sınıfının fonksiyon çağırma operatör fonksiyonuna argüman olarak verilmiştir.
Buradan da bir tensör nesnesi elde edilmiştir. Bu nesne de sonraki Dense nesnesinin fonksiyon çağırma operatörüne argüman yapılmıştır. İşlemler bu biçimde devam ettrilmiştir.
Tabii yukarıdaki işlemler aslında daha sade aşağıdaki gibi de yapılabilir:
inp = Input(...)
result = Dense(...)(inp)
result = Dense(...)(result)
result = Dense(...)(result)
out = Dense(result)
Burada bir girdi bir de çıktı tensörü elde edilmiştir. Bunun bir model nesnesi haline getirilmesi gerekmektedir. Bunun için
tensorflow.keras modülündeki Model sınıfı kullanılmaktadır. Model sınıfının __init__ metodunun iki önemli parametresi vardır:
inputs ve outputs. inputs girdi tensörünü, outputs ise çıktı çıktı tensörünü almaktadır. Yukarıdaki bağlantıyı biz model nesnesi haline
şöyle getirebiliriz:
model = Model(inputs=inp, outputs=out, name='MyModel')
Burada oluşturmaya çalıştığımız modelin Sequential eşdeğeri şöyledir:
model = Sequential('MyModel')
model.add(Dense(..., input_dim=N))
model.add(Dense(...))
model.add(Dense(...))
model.add(Dense(...))
Aslında asıl olan fonksiyonel modeldir. Sequential sınıfı fonksiyonel model kullanılarak yazılmış yardımcı bir sınıftır.
Görüldüğü gibi fonksiyonel modelde önce Input sınıfı yoluyla bir tensör oluşturulmuştur. Sonra bu tensör sonraki katmana verilmiş oradan da
bir tensör elde eidlmiştir. İşlemler bu biçimde devam ettrilimiştir.
Tensör kavramı kurusumuzu Tensorflow kütüphanesinin anlatıldığı bölümde ele alınacaktır.
Aşağıda daha önce yapmış olduğumuz "Boston Housing Price" örneği fonksiyonel bir biçimde oluşturulmuştur. Moelin oluşturulduğu yerin
kodları şöyledir:
from tensorflow.keras import Model
from tensorflow.keras.layers import Input, Dense
inp = Input(shape=training_dataset_x.shape[1], name='Input')
result = Dense(64, activation='relu', name='Hidden-1')(inp)
result = Dense(64, activation='relu', name='Hidden-2')(result)
out = Dense(1, activation='linear', name='Output')(result)
model = Model(inputs=inp, outputs=out)
model.summary()
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
from tensorflow.keras import Model
from tensorflow.keras.layers import Input, Dense
inp = Input(shape=training_dataset_x.shape[1], name='Input')
result = Dense(64, activation='relu', name='Hidden-1')(inp)
result = Dense(64, activation='relu', name='Hidden-2')(result)
out = Dense(1, activation='linear', name='Output')(result)
model = Model(inputs=inp, outputs=out)
model.summary()
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=200, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Mean Absolute Error Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['mae'])
plt.plot(hist.epoch, hist.history['val_mae'])
plt.legend(['Mean Absolute Error', 'Validation Mean Absolute Error'])
plt.show()
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
predict_data = np.array([[0.11747, 12.50, 7.870, 0, 0.5240, 6.0090, 82.90, 6.2267, 5, 311.0, 15.20, 396.90, 13.27]])
scaled_predict_data = mms.transform(predict_data)
predict_result = model.predict(scaled_predict_data)
for val in predict_result[:, 0]:
print(val)
model.save('boston.h5')
import pickle
with open('boston.pickle', 'wb') as f:
pickle.dump(mms, f)
#----------------------------------------------------------------------------------------------------------------------------
Tabii yukarıdaki örnekte aslında Sequential model uygulamakla fonksiyonel model uygulamak arasında bir fark yoktur. Ancak
yukarıda da belirttiğimiz gibi bazı durumlarda fonksiyonel model mecburen kullanılmaktadır. Fonksiyonel modlin kullanılmasını
gerektiren en önemli nedenler biri çok girişle ve/veya çok çıkışlı modellerdir. Bu tür modellerde fonksiyonel olarak girişler ve çıkışlar
oluşturulur. Sonra bunlar Model sınıfının __init__ metodunun inputs ve outputs parametrelerinde bir liste ya da demet biçiinde
girilir.
Çok girdili fonksiyonel model oluştururken girdi katmanlarının birleştirilmesi gerekmektedir. Bu işlem tensorflow.keras.layers
modülündeki concatenate fonksiyonu ile yapılmaktadır. Bu fonksiyonun birinci "inputs" parametresi birleştirilecek tensörleri
bir liste ya da demet olarak almaktadır. Örneğin iki girişli tek çıkışlı bir model fonksiyonel olarak şöyle oluşturulabilir:
inp1 = Input(...)
inp2 = Input(...)
result = concatenate([inp1, inp2])
result = Dense(...)(result)
result = Dense(...)(result)
out = Dense(...)(result)
model = Model(inputs=[inp1, inp2], outputs=out)
Burada şöyle bir model oluşturulmuştur:
inp1 ---->
Dense ---> Dense ----> Dense (output)
inp2 ---->
Pekiyi yukarıdaki gibi iki girişli bir modelin eğitimi ve kestirimi nasıl yapılacaktır? Normal olarak biz fit metodunda
eğitim için gereken training_dataset_x ve training_dataset_y verilerini metodu veriyorduk. Ancak yukarıdaki örnekte girdi iki tanedir.
İşte bu tür durumlarda fit metodunda girdiler bir liste ya da demet biçiminde girilmektedir. Örneğin:
model.fit([training_dataset_x1, training_dataset_x2], training_dataset_y, ...)
Benzer biçimde modelin test edilemsi sırasında da evaluate metodunda yine x verileri bir liste ya da demet biçiminde verilmelidir.
Örneğin:
eval_ result = model.evaluate([test_dataset_x1, test_dataset_x2], test_dataset_y)
Benzer biçimde kestirim işleminde de predict metodunda x verileri bir liste ya da demet biçiminde girilir. Örneğin:
predict_result = model.predict([predict_data_image_x, predict_data_text_x])
print(predict_result[0])
Aşağıda iki girişli bir Keras modeli verilmiştir. Girilkerden bir tanesi (32, 32, 3) bıyutunda resim diğeri ise bir yazıdır.
Biz bu örnekte modeli rastgele verilerle eğttik. Çünkü amacımız modelin nasıl oluşturulduğunu göstermekti. Modeli eğittikten sonra test ettik ve kestirimde bulunduk.
#----------------------------------------------------------------------------------------------------------------------------
IMAGE_SHAPE = (32, 32, 3)
TExT_LENGTH = 40
VOCAB_SIZE = 30000
TOTAL_ITEM = 1000
from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, Flatten, Embedding, Conv1D, MaxPooling1D, concatenate, Dense
input_image = Input(shape=IMAGE_SHAPE, name='Image-Input')
result = Conv2D(32, (3, 3), input_shape=(32, 32, 3), name='Conv2D-1', activation='relu')(input_image)
result = MaxPooling2D(name='Pooling-1')(result)
result = Conv2D(64, (3, 3), name='Conv2D-2', activation='relu')(result)
result = MaxPooling2D(name='Pooling-2')(result)
result = Conv2D(128, (3, 3), name='Conv3D-3', activation='relu')(result)
result = MaxPooling2D(name='Pooling-3')(result)
image_output = Flatten(name='Flatten-1')(result)
input_text = Input(shape=(TExT_LENGTH, ), name='Text-Input')
result = Embedding(VOCAB_SIZE, 64, name='Embedding')(input_text)
result = Conv1D(64, 3, padding='same', activation='relu', name='Conv1D-1')(result)
result = MaxPooling1D(2, name='MaxPooling1D-1')(result)
result = Conv1D(64, 3, padding='same', activation='relu', name='Conv1D-2')(result)
result = MaxPooling1D(2, name='MaxPooling1D-2')(result)
text_output = Flatten(name='Flatten-2')(result)
concatenated_output = concatenate([image_output, text_output])
result = Dense(512, activation='relu', name='Hidden-1')(concatenated_output)
result = Dense(256, activation='relu', name='Hidden-2')(result)
out = Dense(1, activation='sigmoid', name='Output')(result)
from tensorflow.keras import Model
model = Model(inputs=[input_image, input_text], outputs=out)
model.summary()
# generate random data
import numpy as np
dataset_image_x = np.random.randint(0, 256, (TOTAL_ITEM, ) + IMAGE_SHAPE)
dataset_text_x = np.random.randint(0, VOCAB_SIZE, (TOTAL_ITEM, TExT_LENGTH))
dataset_y = np.random.randint(0, 2, TOTAL_ITEM)
"""
manuel separation
test_zone = int(TOTAL_ITEM * 0.80)
training_dataset_image_x = dataset_image_x[:test_zone]
training_dataset_text_x = dataset_text_x[:test_zone]
training_dataset_y = dataset_y[:test_zone]
test_dataset_image_x = dataset_image_x[test_zone:]
test_dataset_text_x = dataset_text_x[test_zone:]
test_dataset_y = dataset_y[:test_zone]
"""
from sklearn.model_selection import train_test_split
training_dataset_image_x, test_dataset_image_x, training_dataset_text_x, test_dataset_text_x, training_dataset_y, test_dataset_y = train_test_split(dataset_image_x, dataset_text_x, dataset_y, test_size=0.2)
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit([training_dataset_image_x, training_dataset_text_x], training_dataset_y, batch_size=32, epochs=100, validation_split=0.2)
eval_result = model.evaluate([test_dataset_image_x, test_dataset_text_x], test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_data_image_x = np.random.randint(0, 256, (1, 32, 32, 3))
predict_data_text_x = np.random.randint(0, VOCAB_SIZE, (1, TExT_LENGTH))
predict_result = model.predict([predict_data_image_x, predict_data_text_x])
print(predict_result[0])
#----------------------------------------------------------------------------------------------------------------------------
Çok çıkışlı modeller de benzer biçimde fonksiyonel olarak oluşturulmaktadır. Örneğin bir modelin iki çıktısı olabilir. Çıktılar bir tanesi
"olumlu", "olumsuz" biçiminde iki sınıflı kategorik bir çıktı iken diğeri lojistik olmayan regresyon çıktısı olabilir. Örneğin aşağıdaki gibi
bir model oluşturacak olalım:
Dense (sigmoid activation)
Input --> Dense ---> Dense
Dense (linear activation)
Bu modelin oluşturulması aşağıdaki gibi yapılabilir:
inp = Input(...)
result = Dense(...)(inp)
result = Dense(...)(result)
out1 = Dense(...)(result)
out2 = Dense(...)(result)
model = Model(inputs=inp, outputs=[out1, out2])
model.compile(...)
model.fit(training_dataset_x, [trainingdataset_y1, training_dataset_y2], ...)
eval_result = model.evaluate(test_dataset_x, [test_dataset_y1, test_dataset_y2])
predict_result = model.predict(predict_data) # çıktı iki tane olacak
Çok çıkışlı modellerde en küçüklenmeye çalışılan loss fonksiyonu nasl olacaktır? Bilindiği gibi loss fonksiyonu problemin türüne göre farklı seçilmektedir.
Örneğin problemde çıktılardan biri ikili sınıflandırmaya ilişkinse o çıktı için "binary-crossentropy" uygun olur. Çıktılardan diğeri
lojistik olmayan regresyona ilişkinse bu çıktı için "mean_squred_error" uygun olur. Biz bugüne kadar compile metodunda loss fonksiyonu olarak
bir tane fonksiyon verdik. Ancak aslında çok çıkışlı modeller için compile metonda her çıkış için ayrı bir loss fonksiyonu verilebilmektedir.
Bunun için loss parametresinde bir liste ya da sözlük girilebilir. Liste girilecekse buradaki sıranın Model nesnesi oluşturulurken outputs
parametresindeki sıraya uygun olması gerekmektedir. Eğer çıktılar için loss fonksiyonları sözlük nesnesi biçiminde girilecekse bu durumda sözlüğün anahtarları
katman nesnelerinin isimlerinden değerleri de loss fonksiyonlarının isimlerinden (ya da loss fonksiyonlarının kendisinden) oluşturulur. Örneğin:
model.compile(optimizer='rmsprop', loss=['binary_crossentropy', 'mse'], ...)
Ya da örneğin:
model.compile(optimizer='rmsprop', loss={'Output-1': 'binary_corssentropy, 'Output-2': 'mse'}, ...)
Pekiyi iki tane loss fonksiyonu olduğuna göre hangisi minimize edilmeye çalışılacaktır? Normal olarak Keras bu iki loss fonksiyonun
ortalamasını minimize etmeye çalışmaktadır. Ancak loss fonksiyonlarının verdiği değerlerin skalaları farklı olabilir. Bu durumda
çıktıların önem dereceleri compile metodunun loss_weights parametresi ile belirtilebilmektedir. Bu parametre ağırlıklı ortalama için gereken ağırlıkları
belirtmektedir. Örneğin:
model.compile(optimizer='rmsprop', loss={'Output-1': 'binary_corssentropy, 'Output-2': 'mse'}, loss_weights={'Output-1': 1, 'Output-2': 0.1}, ...)
Çok çıktılı modellerde birden fazla metrik değerin kullanılması uygun olur. Zira metrik değerler de aslında çıktının türüne göre değişmektedir.
İşte her çıktının metrik değerleri yine bir sözlük nesnesi biçiminde ya da bir liste listesi biçiminde belirtilebilmektedir. Örneğin:
model.compile(optimizer='rmsprop', loss={'Output-1': 'binary_corssentropy, 'Output-2': 'mse'},
loss_weights={'Output-1': 1, 'Output-2': 0.1}, metrics={'Output-1': ['binary_accuracy'], 'Output-2': ['mse']}, ...)
Ya da örneğin:
model.compile(optimizer='rmsprop', loss={'Output-1': 'binary_corssentropy, 'Output-2': 'mse'},
loss_weights={'Output-1': 1, 'Output-2': 0.1}, metrics=[['binary_accuracy'], ['mse']], ...)
Modelin test edilmesinde yine çıktı olarak iki test veri kğmesi kullanılır. Örneğin:
eval_result = model.evaluate(test_dataset_x, [test_dataset_y1, test_dataset_y2])
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict metodu biz bir liste vermektedir. Listenin elemanları sırasıyla çıktı değerlerinden oluşmaktadır. Tabii bu çıktı değerleri
aslında iki boyutlu bir Numpy dizisi biçimindedir. Örneğin:
predict_result = model.predict(predict_data.reshape(1, -1))
print(predict_result[0][0, 0], predict_result[1][0, 0])
Aşağıdaki örnekte bir tane girdi iki tane çıktı olan model kullanılmıştır. Bu modelin eğitim ve test veri kümeleri rastgele değerlerle oluşturulmuştur.
#----------------------------------------------------------------------------------------------------------------------------
TOTAL_ITEM = 1000
NFEATURES = 10
from tensorflow.keras import Model
from tensorflow.keras.layers import Input, Dense
inp = Input(shape=(NFEATURES, ), name='Input')
result = Dense(64, activation='relu', name='Dense-1')(inp)
result = Dense(64, activation='relu', name='Dense-2')(result)
out1 = Dense(1, activation='sigmoid', name='Output-1')(result)
out2 = Dense(1, activation='linear', name='Output-2')(result)
model = Model(inputs=inp, outputs=[out1, out2])
model.summary()
model.compile(optimizer='rmsprop', loss={'Output-1': 'binary_crossentropy', 'Output-2': 'mse'}, loss_weights={'Output-1': 800, 'Output-2': 1}, metrics={'Output-1': ['binary_accuracy'], 'Output-2': ['mse']})
# generate random data
import numpy as np
dataset_x = np.random.random((TOTAL_ITEM, NFEATURES))
dataset_y1 = np.random.randint(0, 2, TOTAL_ITEM)
dataset_y2 = np.random.randint(0, 100, TOTAL_ITEM)
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y1, test_dataset_y1, training_dataset_y2, test_dataset_y2 = train_test_split(dataset_x, dataset_y1, dataset_y2, test_size = 0.2)
hist = model.fit(training_dataset_x, [training_dataset_y1, training_dataset_y2], batch_size=32, epochs=100, validation_split=0.2)
eval_result = model.evaluate(test_dataset_x, [test_dataset_y1, test_dataset_y2])
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
# generate random data for prediction
predict_data = np.random.random(NFEATURES)
predict_result = model.predict(predict_data.reshape(1, -1))
print(predict_result[0][0, 0], predict_result[1][0, 0])
#----------------------------------------------------------------------------------------------------------------------------
Resim tanıma ve resimsel işlemler ve metinsel işlemler işlemler için son 10 yılda "vanishing gradient" denilen problemi
azaltmak amacıyla çeşitli derin ağ mimarileri önerilmiştir. Bu mimarilerin dayandığı temel bira karmaşıktır. Ancak son yıllarda
bu mimariler pek çok endüstriyel uygulamada tercih edilmektedir. Örneğin "denset" ya da "resnet" denilen mimariler resim tanımada
en yaygın kullanılanlar arasındadır. Bu mimariler pek çok katman içedği için uygulamacı tarafından oluşturulması zahmetli mimarilerdir.
İşte bu nedenle bu popüler ve yeni derin ağ mimarileri tensorflow.keras.aplication modülünde hazır bir biçimde bulundurulmaktadır.
Hazır bulundurulan bu mimarilere ilişkin modeller genel olarak çıktı katmanı içermezler. Bu modellerde çıktı katmanları uygulamacı tarafından fonksiyonel biçimde
oluşturulmaktadır. Siz de rsim tanıma ve sınıflandırma tarzı uygulamalr için densnet gibi resnet gibi hazır mimarileri kullanabilirsiniz.
Kers içerisinde hazır bulundurulan bu modellerin kullanımları birbirlerine çok benzemektedir. İstenirse bu modeller daha önce eğitilmiş (predefined)
ırlık değerleriyle de kullanılablmektedir.
Yukarıda sözünü ettiğimiz bu hazır modeller oldukça derin bir mimariye sahiptir. Dolayısıyla bu modellerin eğitilmesi daha önce yaptığımız
modellere göre daha fazla zaman almaktadır. Eğitim için saatlerce zaman gerekebilmektedir.
Örneğin biz popüler bir mimari olan densenet121'i kullanmak isteyelim. Bunun için önce bir DenseNet121 nesnesi yaratılır:
from tensorflow.keras.applications.densenet import DenseNet121
dn121 = DenseNet121(include_top=False, weights=None, input_shape=(32, 32, 3))
Nesne yaratılırken girdi resimlerinin boyutları input_shape parametresiyle belirtilmektedir. weights parametresi ya None girilebilir ya da
"imagenet" girilebilir. Eğer bu parametre "imagenet" olarak girilirse model eğitilirken ağırlık değerleri rastgele değerlerle değil "imagenet"
veritabanındaki önceden eğitilmiş olan bir modelin ağırlık değerleri ile başlatılacaktır. include_top parametresi modelin tepesinde bir Dense katmanın
bulundurulup bulundurulmayacağına ilişkindir. Bu parametre False geçilebilir.
DenseNet121 nesnesi kendi içerisinde modelin katmanlarını tutmaktadır. Bu modelde çıktı katmanı yoktur. Ancak girdi katmanı vardır. Dolayısıyla
programcı bu modelin temel kısmına çıktı katmanlarını eklemelidir. Sınıfın input örnek özniteliği bize girdi tensörünü, output parametresi ise modelin çıktı
tensörünü vermektedir. Uygulamacı tipik olarak nesnenin output örnek özniteliğinden faydalanarak çıktı katmanını fonksiyonel biçimde modele bağlar.
Örneğin:
from tensorflow.keras.applications.densenet import DenseNet121
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras import Model
dn121 = DenseNet121(include_top=False, input_shape=(32, 32, 3), weights=None)
result = Flatten(name='Flatten')(dn121.output)
out = Dense(10, activation='softmax', name='Output')(result)
model = Model(inputs=dn121.input, outputs=out)
model.summary()
Modelin output katmanı bize çok boyutlu bir matris vermektedir. Biz de onu düzleştirip çıktı katmanına bağladık.
Aşağıda DenseNet121 modelinin CIFAR-10 veri kümesine uygulanması örneği verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.datasets import cifar10
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = cifar10.load_data()
training_dataset_x = training_dataset_x / 255
test_dataset_x = test_dataset_x / 255
from tensorflow.keras.utils import to_categorical
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
from tensorflow.keras.applications.densenet import Resnet101
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras import Model
rn101 = Resnet101(include_top=False, input_shape=(32, 32, 3), weights=None)
result = Flatten(name='Flatten')(rn101.output)
out = Dense(10, activation='softmax', name='Output')(result)
model = Model(inputs=rn101.input, outputs=out)
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping('val_loss', patience=5, verbose=1, restore_best_weights=True)
hist = model.fit(training_dataset_x, ohe_training_dataset_y, batch_size=32, epochs=100, callbacks=[esc], validation_split=0.2)
model.save('cifar10.h5')
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Categorical Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['categorical_accuracy'])
plt.plot(hist.epoch, hist.history['val_categorical_accuracy'])
plt.legend(['Categorical Accuracy', 'Validation Categorical Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, ohe_test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
import glob
for path in glob.glob('test-images/*.jpg'):
image_data = plt.imread(path)
image_data = image_data / 255
predict_result = model.predict(image_data.reshape(1, 32, 32, 3))
result = np.argmax(predict_result)
print(f'{path}: {class_names[result]}')
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Diğer hazır modellerin de kullanımı benzerdir. Örneğin yine son yıllarda çok popüler olan resim tanıma için kullanılan Resnet mimarisine
ilişkin hazır model de tamamen yukarıdaki denset örneğinde olduğu gibidir. Yukarıdaki örneğin ResNe modeline uygulanışı aşağıda
verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.datasets import cifar10
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = cifar10.load_data()
training_dataset_x = training_dataset_x / 255
test_dataset_x = test_dataset_x / 255
from tensorflow.keras.utils import to_categorical
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
from tensorflow.keras.applications.densenet import ResNet101
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras import Model
rn101 = ResNet101(include_top=False, input_shape=(32, 32, 3), weights=None)
result = Flatten(name='Flatten')(rn101.output)
out = Dense(10, activation='softmax', name='Output')(result)
model = Model(inputs=rn101.input, outputs=out)
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping('val_loss', patience=5, verbose=1, restore_best_weights=True)
hist = model.fit(training_dataset_x, ohe_training_dataset_y, batch_size=32, epochs=100, callbacks=[esc], validation_split=0.2)
model.save('cifar10.h5')
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Categorical Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['categorical_accuracy'])
plt.plot(hist.epoch, hist.history['val_categorical_accuracy'])
plt.legend(['Categorical Accuracy', 'Validation Categorical Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, ohe_test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
import glob
for path in glob.glob('test-images/*.jpg'):
image_data = plt.imread(path)
image_data = image_data / 255
predict_result = model.predict(image_data.reshape(1, 32, 32, 3))
result = np.argmax(predict_result)
print(f'{path}: {class_names[result]}')
#----------------------------------------------------------------------------------------------------------------------------
Makine öğrenmesi uygulamalarında kullanılan en önemli araçlardan biri de "Auto ML" araçlarıdır. Bu araçlar pek çok yükü
uygulamacının üzerinden alarak kendileri yapmaktadır. Auto ML araçlarının uygulamacı için yaptığı işlemler şunlardır:
- Özellik seçimi (feature selection)
- Özelliklerin indirgenmesi (dimentionality feature reduction)
- Verilerin ölçeklendirmesi (feature scaling)
- Kategorik verilerin sayısallaştırılması (one hot encoding, ...)
- Verilerin kullanıma hazır hale getirilmesi için gereken diğer işlemler
- Model seçimi (model selection)
- Modelin çeşitli parametrelerinin (hyperparameters) uygun biçimde ayarlanması (hyperparameter tuning)
- Modelin kullanıma hazır hale getirilmesi (model deployment)
Yukarıdaki işlemlerin hepsini tüm Auto ML araçları yapamaktadır. Bu konuda araçlar arasında farklılıklar bulunmaktadır.
Bir ML probleminde karşılaşılan en önemli aşamalardan biri model seçimi ve modelin çeşitli parametrelerinin uygun biçimde konumlandırılmasıdır.
Örneğin bir resim tanıma işleminde bizim problemimize özgü hangi mimarinin daha iyi olduğu ve bu mimarideki katmanlardaki nöron sayılarının
ne olması gerektiği, optimizasyon algoritmasındak öğrenme hızının (leraning rate) ayarlanması model oluştururken deneme yanılma yöntemiyle
belirlenmektedir. Bu tür araçlar bu sıokıcı deneme yanılma yöntemlerini bizim için kendileri uygulamaktadır.
Auto ML araçlarından bazıları yapay sinir ağları için oluşturulmuştur. BAzıları ise kursumuzun sonraki bölümlerinde ele alacağımız istatistiksel
yöntemleri uygulamak için oluşturulmuştur. Bazı Auto ML araçları ise kurumuzun son bölümünde ele alacağımız "pekiştirmeli öğrenme (reinforcement learning)"
uygulamaları için tasarlanmıştır.
Yapay sinir ağları için kullanılan Auto MKL araçlarının en yaygın kullanılanı "AutoKeras" isimli araçtır. Biz de bu bölümde AutoKeras'ın
kullanımı üzerine duracağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
AutoKeras aracının resmi sitesi şöyledir:
https://autokeras.com/
Bu sitede oldukça basit örneklerle aracın nasıl kullanılacağııklanmıştır.
AutoKeras'ı kurmak için aşağıdaki komut uygulanabilir:
pip install autokeras
Biz kütüphaneyi aşağıdaki gibi import ederek kullanacağız:
import autokeras as ak
AutoKeras kütüphanesinde temelde 6 sınıf vardır:
ImageClassifier
ImageRegressor
TextClassifier
TextRegressor
StructuredDataClassifier
StructuredDataRegressor
ImageClassifier sınıfı resimleri sınıflandırmak için, ImageRegressor sınıfı resimlerden sınıf değil sayısal değer elde etmek için (örneğin resimdeki yaşını
tespit etme problemi), TextClassifier sınıfı yazıları sınıflandırmak için, TextRegressot sınıfı yazılardan sayısal değer elde etmek için (örneğin yazının
konu ile alaka dercesini tespit etmeye çalışan problem), StructuredDataClassifier sınıfı resim ve yazı dışındaki farklı türlere ilişkin sütunlara sahip
sınıflandırma modelleri için ve StructuredDataRegressor farklı türlere ilişkin sütunlara sahip lojistik olmayan regresyon problemleri için kullanılmaktadır.
Bu sınıflar kullanılırken uygulamacı özellik ölçeklemesi, değerlerin sayısal hale dönüştürülmesi, one hot encoding gibi işlemleri
kendisi yapmaz. Bu işlemleri zaten bu sınıfların kendisi yapmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
ImageClassifier sınıfının tipik kullanımı şöyledir:
1) Önce ImageClassifer sınıfı türünden bir nesne yaratılır. Sınıfın __init__ metodunun parametrik yapısı şöyledir:
autokeras.ImageClassifier(
num_classes=None,
multi_label=False,
loss=None,
metrics=None,
project_name="image_classifier",
max_trials=100,
directory=None,
objective="val_loss",
tuner=None,
overwrite=False,
seed=None,
max_model_size=None,
**kwargs
)
Görüldüğü gibi parametreler default değerler almaktadır. num_classes parametresi çıktının kaç sınıflı olduğunu belirtmektedir.
Default durumda sınıf sayısı otomatik olarak belirlenmeye çalışılır (yani training_dataset_y içerisindeki farklı değerlerin sayısı ile
belirlenmeye çalışılmaktadır.) max_trials parametresi en fazla kaç modelin deneneceğini belirtmektedir. Tabii bu değer ne kadar yüksek
tutulursa o kadar iyi sonuç elde edilir. Ancak en iyi modelin bulnması süreci uzayacaktır. Burada max_trials parametresi ile denenecek model sayısı
demekle yalnızca katmansal farklılık kastedilmemektedir. Örneğin katmansal yapı aynı olsa bile hyper parametre farklılıkları da farklı bir model olarak
değerlendirilmektedir. Dolayısıyla programcının iyi bir sonuç elde etmek için max_trials parametresini yüksek bir değerde tutması uygun olur.
Yüksek değerler fit işleminin birkaç gün sürmesine yol açabilmektedir. Bunun için uygulamacı cloud sistemlerini kullanabilir.
objective parametresi model karşılaştırılırken neye göre karşılaştırılacağını belirtmektedir. AutoKeras her model işlemi için bir proje dizini oluşturmaktadır. Bu dizin'in ismi directory parametresi
ile ayarlanmaktadır. Bu parametre girilmezse dizin'in ismi project_name parametresi ile belirtilen modelin ismi biçiminde alınır. Metodun overwrite parametresi default durumda False biçimdedir.
False değeri metodun daha önce oluşturulnuş olan bilgileri kullanacağını belirtmektedir. True değeri ise her defasında eski proje bilgileri olsa bile yeni
değerleri onun üzerine yazazağı anlamına gelmektedir. Metrik değerler metrics parametresiyle verilebilmektedir.
2) Modelin derlenmesi işlemi uygulamacı tarafından yapılmaz. Dolayısıyla uygulamacı doğrudan fit işlemi yapar. Buradaki fit metodu
tamamen Keras'taki fit metodu gibidir. fit metodu için programcı training_dataset_x ve training_dataset_y verilerini verir.
Metodun parametreleri Keras'ta gördüğümüz gibidir. epochs parametresi her denenecek model için ne kadar epoch uygulanacağını belirtir.
batch_size parametresi default durumda 32'dir. fit işlemi sırsında resimlerin girdileri üç boyutlu olmalıdır. RGB resimler için idthxheightx3
gray scale resimler için widthxheightx1 biçiminde resimler kullanılmalıdır.
3) En iyi model AutoKeras tarafından bulunduktan sonra model test edilmelidir. Yine modelin tespi için ImageClassifier sınıfının
evaluate metodu kullanılmaktadır.
4) Elde edilen en iyi model istenirse Keras'ın Model sınıfına dönüştürülebilir. Bunun için ImageClassifier sınıfının export_model
metodu kullanılmalıdır. Programcı artık bu işlemden sonra modelini save edebilir. Daha önce görmüş olduğumuz işlemleri bu
model nesnesi üzerinde uygulayabilir.
Yukarıda da belirttiğimiz gibi eğer biz ImageClassifier nesnesini yaratırken overwrite parametresini True geçmezsek aslında aynı
proje bir daha çalıştırıldığında eski kalınan yerden devam edilir. Çünkü proje için açılan dizinde tüm deneme bilgileri ve model bilgiler ve kalınan
yer not alınmaktadır.
Aşağıdaki örnekte CIFAR-100 veri kümesi için AutoKeras'ın ImageClassifier sınıfı kullanılmıştır. Burada max_tralis değerini
5'te tuttuk.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.datasets import cifar100
class_names = [
'apple', 'aquarium_fish', 'baby', 'bear', 'beaver', 'bed', 'bee', 'beetle',
'bicycle', 'bottle', 'bowl', 'boy', 'bridge', 'bus', 'butterfly', 'camel',
'can', 'castle', 'caterpillar', 'cattle', 'chair', 'chimpanzee', 'clock',
'cloud', 'cockroach', 'couch', 'crab', 'crocodile', 'cup', 'dinosaur',
'dolphin', 'elephant', 'flatfish', 'forest', 'fox', 'girl', 'hamster',
'house', 'kangaroo', 'keyboard', 'lamp', 'lawn_mower', 'leopard', 'lion',
'lizard', 'lobster', 'man', 'maple_tree', 'motorcycle', 'mountain', 'mouse',
'mushroom', 'oak_tree', 'orange', 'orchid', 'otter', 'palm_tree', 'pear',
'pickup_truck', 'pine_tree', 'plain', 'plate', 'poppy', 'porcupine',
'possum', 'rabbit', 'raccoon', 'ray', 'road', 'rocket', 'rose',
'sea', 'seal', 'shark', 'shrew', 'skunk', 'skyscraper', 'snail', 'snake',
'spider', 'squirrel', 'streetcar', 'sunflower', 'sweet_pepper', 'table',
'tank', 'telephone', 'television', 'tiger', 'tractor', 'train', 'trout',
'tulip', 'turtle', 'wardrobe', 'whale', 'willow_tree', 'wolf', 'woman',
'worm']
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = cifar100.load_data()
import autokeras as ak
ic = ak.ImageClassifier(max_trials=5, overwrite=True)
hist = ic.fit(training_dataset_x, training_dataset_y, epochs=5)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Categorical Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.plot(hist.epoch, hist.history['categorical_accuracy'])
plt.plot(hist.epoch, hist.history['val_categorical_accuracy'])
plt.legend(['Categorical Accuracy', 'Validation Categorical Accuracy'])
plt.show()
model = ic.export_model()
eval_result = ic.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
model.summary()
model.save('image-classifier-best-model.h5')
import numpy as np
import glob
for path in glob.glob('test-images/*.jpg'):
image_data = plt.imread(path)
image_data = image_data / 255
predict_result = model.predict(image_data.reshape(1, 32, 32, 3))
result = np.argmax(predict_result)
print(f'{path}: {class_names[result]}')
#----------------------------------------------------------------------------------------------------------------------------
ImageRegressor sınıfı bir resimden hareketle bir değerin tahmin edilmesi tarzı problemlerde kullanılmaktadır. Sınıfın kullanım biçimi tamamen
ImageClassifier sınıfında olduğu gibidir. __init__ metodunun parametrik yapısı şöyledir:
autokeras.ImageRegressor(
output_dim=None,
loss="mean_squared_error",
metrics=None,
project_name="image_regressor",
max_trials=100,
directory=None,
objective="val_loss",
tuner=None,
overwrite=False,
seed=None,
max_model_size=None,
**kwargs
)
Bu sınıfın kullanımına örnek için MNIST veri kümesinden faydalanacağız. Aslında MNIST veri kümesinde çıktı 0, 1, 2, ..., 9
biçiminde kategorik bir veridir. Ancak biz bunu sanki sayısal bir veri gibi örneğimizde ele alacağız.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.datasets import mnist
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = mnist.load_data()
import autokeras as ak
ir = ak.ImageRegressor(max_trials=5)
hist = ir.fit(training_dataset_x, training_dataset_y, epochs=5)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Mean Absolute Error Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.plot(hist.epoch, hist.history['mae'])
plt.plot(hist.epoch, hist.history['val_mae'])
plt.legend(['Mean Absolute Error', 'Validation Mean Absolute Error'])
plt.show()
model = ir.export_model()
model.summary()
model.save('image-classifier-best-model.h5')
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
TextClassifer sınıfı yazıları sınıflandırmak için kullanılmaktadır. Öneğin daha önce yapmış olduğumuz "sentiment analysis" örnekleri
TextClassifier sınıfıyla yapılabilir. Sınıfın __init__ metodunun parametrik yapısı ImageClassifer'da olduğu gibidir. TextClassifier sınıfında fit
işleminde training_dataset_x yazılardan oluşan bir NumPy dizisi olabilir. training_dataset_y kategorik değerleri ilişkin bir NumPy dizisi olabilir
ya da sayısallaştırılmış kategorik değerlern oluşabilir. Geri kalan işlemleri AutoKeras kendisi yapmaktadır. Öneğin AutoKeras
yazının parse edilmesi işlemini, vektörel hale getirilmesi işlemini, word embedding işlemini kendisi yapmaktadır. Yani uygulamacı
yalnızca yazıları fit metoduna vermektedir.
Aşağıda IMDB örneği TextClassifier sınıfıyla yapılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
dataset_x = df['review'].to_numpy()
dataset_y = df['sentiment'].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
import autokeras as ak
tc = ak.TextClassifier(max_trials=3)
hist = tc.fit(training_dataset_x, training_dataset_y, epochs=10)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
model = tc.export_model()
model.summary()
model.save('text-classifier-best-model.h5')
eval_result = tc.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
texts = ['the movie was very good. The actors played perfectly. I would recommend it to everyone.', 'this film is awful. The worst film i have ever seen']
for predict_text in texts:
predict_result = tc.predict(texts)
if predict_result[0, 0] > 0.5:
print('Positive')
else:
print('Negative')
model.save('imdb.h5')
#----------------------------------------------------------------------------------------------------------------------------
Şimdiye kadar kullanmadığımız bir veri kümesi de spam veri kümesidir. Bu veri kğmesinde birtyakım e-postalar ve onların spam olup
olmadığı bilgileri vardır. Böylece bir e-posta geldiğinde spam filtresi oluşturulabilemktedir. Bugün kullanılan spam filtrelerinin
çoğu çeşitli makine öğrenmesi teknikleriyle oluşturulmuştur. Veri kümesi "spam_ham_dataset.csv" ismiyle aşağıdaki bağlantıdan indirilebilir:
https://www.kaggle.com/datasets/venky73/spam-mails-dataset?resource=download
Burada Pandas'ın read_csv fonksiyonuyla okuma yapılabilir. "label" isimli sütun "spam" ya da "ham" değerlerindne oluşmaktadır.
label_num aynı değerlerin 0 ve 1 ile sayısallaştırılmış halini içermektedir. E-posta yazıları ise "text" isimli sütunda bulunmaktadır.
AutoKeras'ın TextClassifier sınıfıyla yukarıdakine benzer biçimde bu veri kümesi için model oluşturulabilir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('spam_ham_dataset.csv')
dataset_x = df['text'].to_numpy()
dataset_y = df['label_num'].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
import autokeras as ak
tc = ak.TextClassifier(max_trials=3)
hist = tc.fit(training_dataset_x, training_dataset_y, epochs=10)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
model = tc.export_model()
model.summary()
model.save('text-classifier-best-model.h5')
eval_result = tc.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
texts = ['the movie was very good. The actors played perfectly. I would recommend it to everyone.', 'this film is awful. The worst film i have ever seen']
for predict_text in texts:
predict_result = tc.predict(texts)
if predict_result[0, 0] > 0.5:
print('Positive')
else:
print('Negative')
model.save('imdb.h5')
#----------------------------------------------------------------------------------------------------------------------------
Resim ve yazı dışındaki sınıflandırma problemleri için AutoKeras'ta StructuredDataClassifier sınıfı kullanılmaktadır. Sının __init__ metodunun
parametrik yapısı benzerdir:
autokeras.StructuredDataClassifier(
column_names=None,
column_types=None,
num_classes=None,
multi_label=False,
loss=None,
metrics=None,
project_name="structured_data_classifier",
max_trials=100,
directory=None,
objective="val_accuracy",
tuner=None,
overwrite=False,
seed=None,
max_model_size=None,
**kwargs
)
StructuredDataClassifer sınıfında girdi olarak iki boyutlu NumPy matrisi verilir. Özellik ölçeklemesi ve kategorik verilerin
sayısal biçime dönüştütülmesi gibi işlemler sınıf tarafından yapılmaktadır. y verileri yine yazı içeren bir NumPy dizisi olarak ya da
bunların sayısallaştırılmış haliyle girilebilmektedir.
Bu sınıfın bir uygulaması olarak Titanik veri kümesini kullanacağız. Titanik kümesi Titanik'te yolcu olanların hayatta kalıp kalmayacağına yönelik
hazırlanmış bir veri kümesidir. Böylece veri kümesindeki çeşitli özellekler bilindikten sonra kişinin o faciada hayatta kalıp kalamayacağı tahmin
edilmeye çalışılmaktadır.
Modelden elde edilen history verilerinin içerisinde val_xxx verileri bulunmayabilir. Çünkü model denemesi yapılırken bazı modellerde
veriler az ise biz validation_split ile belirtsek bile validation verileri kullanılmayabilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('titanic.csv')
dataset_x = df.drop(columns=['Survived'], axis=1)
dataset_y = df['Survived'].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
import autokeras as ak
sdc = ak.StructuredDataClassifier(max_trials=10, overwrite=True)
hist = sdc.fit(training_dataset_x, training_dataset_y, epochs=100, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.plot(hist.epoch, hist.history['loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.plot(hist.epoch, hist.history['accuracy'])
plt.legend(['Binary Accuracy', 'Validation Binary Accuracy'])
plt.show()
model = sdc.export_model()
model.summary()
model.save('text-classifier-best-model.tf', save_format='tf')
eval_result = sdc.evaluate(test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
Son AutoKeras sınıfı da StructuredDataRegressor sınıfıdır. Bu sınıf yine resimsel ve metinsel olmayan regresyon problemleri
için kullanılmaktadır. Sınıfın __init__ metdounun parametrik yapısı yine diğer sınıflardakine oldukça benzerdir:
autokeras.StructuredDataRegressor(
column_names=None,
column_types=None,
output_dim=None,
loss="mean_squared_error",
metrics=None,
project_name="structured_data_regressor",
max_trials=100,
directory=None,
objective="val_loss",
tuner=None,
overwrite=False,
seed=None,
max_model_size=None,
**kwargs
)
AutoKeras modellerinde yine callback nesneleri kullanılabilmektedir. Örneğin eğer biz AutoKeras sınıflarının fit metotlarının callbacks
parametresine EarlyStopping callback nesnesi yerleştirirsek bu durumda denen model belirlediğimiz patience değerine bağlı olarak erken sonlandırılabilecektir.
Aşağıdaki örnekte Boston verileri üzerinde StructuredDataRegressor sınıfı kullanılmıştır ve EarlyStopping callback nesnesinden de
faydalanılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Anımsanacağı gibi makine öğrenmesi kabaca üç bölümde ele alınıyordu:
1) Denetimli Öğrenme (Supervised Learning)
2) Denetimsiz Öğrenme (Unsupervised Learning)
3) Pekiştirmeli Öğrenme (Reinforcement Learning)
Biz şimdiye kadar "yapay sinir ağları ile denetimli öğrenme" konularını inceledik. Tabii denetimli öğrenme yalnızca yapay sinir ağlarıyla değil
istatistiksel ve matematiksel başka yöntemlerle de gerçekleştirilebilmektedir.
Denetimli öğrenme diye x ve y verileri arasında eğitim yoluyla bir ilişki kurma hedefinde olan öğrenme yöntemlerine denilmektedir.
Yani denetimli öğrenmede bir eğitim süreci vardır. Eğitimden sonra kestirim yapılabilmektedir. Örneğin elimizde elma, armut ve
kayısı olmak üzere üç meyve olsun. Biz önce eğitim sırasında hangi resmin ne olduğunu modele veririz. Model bunlardan hareketle x ve y
değerleri arasında bir ilişki kurar. Sonra biz bir resim verdiğimizde onun el ma, armut mu, kayısı olduğunu model bize tahmin eder.
Denetimsiz (unsupervised) modellerde elimizde yalnızca x verileri vardır. Dolayısıyla biz bu öğrenme yöntemlerinde bir eğitim uygulamayız.
Denetimsiz öğrenmede biz modele birtakım verileri veririz. Model bu verileri inceler. Bunlar arasındakiş benzerlik ve farklılıkalrdan hareketle
bunları gruplayabilir. Dolayısıyla bu grupla için bir y veri kümesine ihtiyaç duyulmaz. Örneğin elimizde bol miktarda elma, armut,
kayısı resimleri olsun. Biz modele "bu resimler bazı bakımlardan birbirlerine benziyor, birbirlerine benzeyenleri gruplandır" deriz.
Model de aslında hangi resmin elma, hangi resmin armut ve hangi resmin kayısı olduğunu bilmeden bunların benzerliklerine bakarak bunları bir araya
getirebilmektedir. Burada dikkat edilmesi gereken nokta bir eğitim sürecinin olmamasıdır. Pekiyi denetimsiz öğrenmede kestirim yapılabilir mi?
Evet yapılabilir. Örneğin biz modele bir resim verip onun hangi gruba daha fazla benzediğini sorabiliriz. Bu bir çeşit kestirimdir.
Denetimsi öğrenme için çeşitli yöntemler bulunmaktadır. Ancak denetimsiz öğrenme konusunun %70 kadar "küemele (clustering)"
denilen yöntem ile ilgilidir. Bu nedenle denetimsiz öğrenme denildiğinde akla ilk gelen yöntem kümeleme yöntemidir.
Biz de önce kümeleme yöntemlerini göreceğiz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Makine öğrenmesinde "sınıflandırma (classification)" ve "kümeleme (clustering)" kavramları farklı anlamlarda kullanılmaktadır.
Sınıflandırma belli bir olgunun önceden belirlenmiş sınıflardan birine atanması ile ilgilidir. Kümeleme ise bu sınıfların bizzat
oluşturulması ile ilgidir. Yani sınıflandırmada sınıflar zaten bellidir. Kümelemede ise sınıflar benzerliklerden ve farklılıklardan hareketle
oluşturulmaya çalışılmaktadır. Dolayısıyla sınıflandırma "denetimli (supervised)" yöntem grubunu belirtirken, kümeleme "denetimsiz (unsupervised)"
bir yöntem grubunu belirtmektedir.
Elimizde hem x ve hem y verileri varken genellikle denetimli öğrenme yöntemleri tercih edilmektedir. Ancak bazen elimizde yeteri kadar
x verileri olduğu halde y verileri olmayabilir. Örneğin anomali içeren banka işlemlerini tespit etmek isteyelim. Elimizde
anomali içerdiğini açıkça bildiğimiz fazlaca y verisi olmayabilir. Bazen x ve y verilerinin çeşitliliğinden dolayı denetimli öğrenme
uygun yöntem olmaktan çıkabilir. Örneğin bir dosyanın virüslü olupolmadığına yönelik bir model oluşturmak isteyelim.
Elimizde virüslü dosyalarla virisiz dosyalar bulunuyor olabilir. Ancak virüs yöntemleri sürekli değişebilmektedir. Bu durumda yeni
veriler daha oluşmadan biz eğitimi yapamayız. Bu tür durumlarda denetimsiz kümeleme tarzı yöntemler tercih edilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Kümeleme işlemleri aslında istatistikte uzun süredir kavram olarak biliniyordu. Bu işlemlere istatistikte "kümeleme analizi (cluster analysis)"
denilmektedir. Ancak son yirmi yıldır kümeleme işlemleri bir makine öğrenmesi yöntemi olarak kullanılma başlandı ve bu konuda
eskisinden çok daha fazla yöntemler ve teknikler geliştirildi. Bu nedenle kümeleme kavramı artık klasik istatis bağlamında değil daha çok
makine öğrenmesi bağlamında ele alınmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Kümeleme benzer olanların ya da benzer olmayanların bir araya getirilmesi süreci olduğuna göre benzerlik nasıl ölçülmektedir?
Yani bir veri kümesinde satırlar varlıkları temsil eder. İki birbirine benzer olması nasıl ölçülecektir? Benzerlik insan sezgisi ile ilgili
bir kavramdır. Oysa makine öğrenmesinde benzerlik ancak sayısal yöntemle somut hale getirilebilir.
İşte bir veri tablosundaki satırlar n boyutlu uzayda bir nokta gibi düşünülebilir. Benzerlik ise "uzaklık (distance)" temeline dayandırılabilmektedir.
Eğer n boyutlu uzayda iki nokta arasındaki uzaklık yakın ise bu iki nokta benzer uzak ise bu iki nokta benzer değildir.
Ancak uzaklık da aslına farklı yöntemlerle hesaplanabilmektedir. En yaygın kullanılan uzaklık ölçütü "öklit uzaklığı (euclidean distance)"
denilen ölçüttür. Öklit uzaklığı n boyutlu uzayda iki nokta arasındaki uzaklıktır. Ancak "hamming uzaklığı (Hamming distance)",
gibi "Manhattan uzaklığı (Manhattan distance)" gibi çeşitli uzaklık ölçütleri değişik problemlerde bazen tercih edilebilmektedir.
İki boyutlu kartezyen koordinat sistemni aslında iki özellikli (sütunlu) bir veri tablosu gibidir. Bu durumda bu veri tablosunun her bir satırı
aslında düzlemde bir nokta belirtir. Bu iki nokta arasındaki Ökl,it uzaklığı noktalar a ve b olmak üzere sqrt((ax - bx) ** 2 + (ay - by) ** 2)
biçimindedir. N boyutlu uzaydaki iki nokta arasındaki uzaklık da benzer biçimde hesaplanmaktadır. Her boyun karşılıklı bileşenlerinin farklarının
karelerinin toplamının karekörü alınır. NumPy kullanarak N boyutlu uzayda iki nokta arasındaki Öklit uzaklığını hesaplayan bir fonklsiyon basit bir biçimde
şöyle yazılabilir:
import numpy as np
def euclidean_distance(a, b):
return np.sqrt(np.sum((a - b) ** 2))
a = np.array([1, 4, 6, 2])
b = np.array([4, 2, -1, 7])
d = euclidean_distance(a, b)
print(d)
Öklit uzaklığı hesaplamak için NumPy içerisinde pratik bir fonksiyon yoktur. Ancak SciPy içerisinde spatial paketindeki distance fonksiyonu
Öklit uzaklığı hesaplamaktadır:
from scipy.spatial import distance
a = np.array([1, 4, 6, 2])
b = np.array([4, 2, -1, 7])
dst = distance.euclidean(a, b)
Ökltit uzaklığının dışında daha az kullanılıyor olsa da birkaç uzaklık tanımı daha vardır. Manhattan uzaklığı (Manhattan distance)
iki nokta arasındaki birbirine dik doğrularla gidilebilen uzaklıktır. Matematiksel olarak a ve b noktalar i'ise uzayın boyutları
olmak üzere sum(abs(ai - bi)) biçimindedir. Numpy'da bu uzaklık şöyle ifade edilebilir:
import numpy as np
def manhattan_distance(a, b):
return np.sum(np.abs(a - b))
a = np.array([1, 2, 3, 4])
b = np.array([5, 6, 7, 8])
mdist = manhattan_distance(a, b)
print(mdist)
Manhattan uzaklığı SciPy kütüphanesinde scipy.spatial.distance modülünde cityblock fonksiyonuyla hesaplanabilmektedir.
from scipy.spatial.distance import cityblock
mdist = cityblock(a, b)
print(mdist)
Özellikle görüntü işleme gibi sayısal işaret işleme uygulamalarında "Hamming uzaklığı (Hamming distance)" denilen bir uzaklık da kullanılmaktadır.
Hamming uzaklığı "ikili (binary)" kategorik sütunlara sahip noktalar için kullanılmaktadır. Farklı olanların toplam sayısının ortalaması ile ölçülmektedir. Örneğin:
ankara
ayazma
Bu iki yazının hamming uzaklığı 4'tür. Örneğin:
from scipy.spatial.distance import hamming
a = np.array([1, 0, 0, 1])
b = np.array([1, 1, 0, 0])
hdist = hamming(a, b)
print(hdist) # 0.5
Kosinüs uzaklığı da bazı uygulamalarda kullanılmaktadır. İki nokta arasındaki açının kosinüsü ile hesaplanmaktadır.
Aslında daha pek çok uzaklık türü tanımlanmıştır. Bu uzaklıklar için scipy.spatial.distance modülündeki fonksiyonları inceleyiniz.
Uzaklıklar sütunsal biçimde hesaplandığına göre sütunlar arasındaki skala farklılıkları uzaklık hesabını olumsuz etkleyecektir. Örneğin
üç sütüunda oluşan aşağıdaki gibi bir veri kümesi olsun:
F1 F2 F3
0.2 123 1234678
0.4 567 2567865
0.7 328 1876345
... ... ...
Burada iki nokta arasındaki uzaklıkta asıl etkili olan sütun üçüncü sütundur. Birinci sütunun neredeyse hiçbir etkisi yoktur.
O halde bizim bu tür uygulamalarda sütunların skalalarını benzer hale getirmemiz gerekir. Yani kümeleme işlemlerinde özellik
ölçeklemesi uygulamalıdır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yüzün üzerinde kümeleme algoritması oluşturulmuştur. Bazı algoritmalar bazı algoritmaların biraz değiştirilmiş biçimleri gibidir.
Ancak bazı algoritmalar tamamen farklı fikirlere dayanmaktadır. Kümeleme algoritmaları kendi aralarında algortimanın dayandığı gikir
bakımından beş gruba ayrılabilir:
1) Ağırlık Merkezi (Centroid) Tamelli Algoritmalar
2) Bağlantı (Connectivity) Temelli Algoritmalar (Hiyerarşik Kümeleme Algoritmaları)
3) Yoğunluk Temelli (Density Based) Algoritmalar
4) Dağılım Temelli (Distribution Based) Algoritmalar
5) Bulanık Temelli (Fuzzy Based) Algortimalar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Kümeleme algortimalarının en popüler olanı ve en bilineni K-Means denilen algoritmadır. K-Means algoritması oldukça verimlidir.
Algoritmik karmaşıklığı diğerlerine göre daha iyidir. Ancak uç noktalardan daha fazla etkilenme potansiyeline sahiptir.
K-Means algoritmasının tipik işleyişi şöyledir:
1) Küme sayısının uygulamacı tarafından biliniyor olması gerekir. Algoritma kğme sayısını kendisi bulmamaktadır. Küme sayısı
bizzat uygulamacı tarafından algoritmaya söylenmektedir. Bu sayının k olduğunu varsayalım.
2) k tane küme için işin başında rastgele k tane ağırlık merkezi belirten nokta üretilir.
3) Tüm noktaların ağırlık merkezine uzaklıkları hesaplanır. Noktalar hangi ağırlık merkezine daha yakınsa o kümenin içerisine
dahil edilir. Artık ilk kümeleme yapılmıştır.
4) Kümelerin yeni ağırlık merkezleri küme içerisindeki noktalardan hareketle hesaplanır. Küme içerisindeki noktaların ağırlık merkezleri
her boyutun kendi aralarındaki ortalamaları ile hesaplanmaktadır. Örneğin x, y, z boyutlarına sahip a, b, c noktalarının ağırlık merkezleri şöyle hesaplanır:
centroidx = (ax + bx + cx) / 3
centroidy = (ay + by + cy) / 3
centroidz = (az + bz + cz) / 3
Aslında burada yapılan işlem noktalar dataset biçiminde iki boyutlu bir NumPy matrisi biçiminde ise np.mean(dataset, axis=0) işlemidir.
5) Tüm noktaların yeniden bu yeni ağırlık merkezlerine uzaklığı hesaplanır. Hangi noktalar hangi ağırlık merkezine daha yakınsa o kğmeye dahil edilir.
Böylece bazı noktalar küme değiştirecekir. Sonra 4'üncü adıma geri dönülür ve işlemler bu biçimde devam ettirlir.
6) Eğer yeni ağırlık merkezine göre hiçbir nokta küme değiştirmiyorsa artık yapılacak bir şey yoktur ve algoritma sonlandırılır.
Bu algoritmik yönteme "Lloyd" algoritması denilmektedir.
Bu yöntemde performas ölçütü olarak neyi kullanabiliriz? En çok kullanılan performans ölçütü "atalet (inertia)" denilen ölçüttür.
Atalet her noktanın kendi ağırlık merkezine uzaklığının karelerinin toplamına denilmektedir. Bu aslında istatistikteki varyans işlemi gibidir.
K-Means yönteminde algoritma ratgele alınmış ağırlık merkezleriyle başlatılmaktadır. Algoritmanın her çalıştırılmasında ağırlık merkezleri
rastgele alındığına göre bu durum her çalıştırmada farklı kümelemenin yapılabileceği anlamına gelmektedir. Yani biz algoritmayı iki kez çalıştırdığımızda
aynı noktaların aynı kümelerde bulunmadığını görebiliriz. Pekiyi bu durumda en iyi çözümü naısl belirleyebiliriz? Bu durumda tipik olarak izlenen yöntem
algoritmayı n defa çalıştırığ her çözümün ataletine bakmak ve atalaeti en düşük olan çözümü asıl çözüm olarak kabul etmektir.
Algoritmanın başlangıcında rastgele nokta seçmek çin çeşitli yöntemler de önerilmiştir. Bunlardan en yaygın kullanılanı "kmeans++" denilen yöntemdir.
Pekiyi biz KMeans algoritmasında kestirimde bulunabilir miyiz? Eğer bir kez kümeleme yapılmışsa yeni bir nokatnın bu kümelerden hangisinin içerisine girebileceği
basit bir biçimde noktanın tüm ağırlık merkezlerine uzaklığına bkılarak belirlenebilir. Yani bu yöntem bize bir kestirim yapma olanağı da sağlamaktadır.
Aşağıda biraz kusurlu da olsa basit bir biçimde K-Means kümeleme işlemini yapan bir fonksiyon örneği verilmiştir. Fonksiyonun parametrik
yapısı şöyledir:
kmeans(dataset, nclusters, centroids=None)
Fonksiyonun birinci parametresi kümelenecek olan noktaları belirtmektedir. İkinci parametre oluşturulacak küme sayını belirtir. Üçüncü
parametre başlangıçtaki rastgele ağırlık merkezlerini almaktadır. Bu parametre için argüman girilmezse başlangıçtaki ağırlık merkezleri rastgele
bir biçimde alınmaktadır. Fonksiyon dörtlü bir demete geri dönmektedir. Demetin birinci elemanı noktaların tel tek 0 orijinli olarak hangi kümeler
içerisinde bulunduğu bilgisidir. İkinci eleman NumpPy dizilerinden oluşan bir liste biçimindedir. Bu listenin içerisindeki dizilerde sırasıyla ilgili
kümedeki noktalar bulunmaktadır. Demetin çüncü elemanı nihai durumdaki ağırlık merkezlerini vermektedir. Son eleman ise noktaların kendi ağırlık merkezlerine uzaklıklarının
karelerinin toplamını vermektedir. Buna atalet (inertia) da denilmektedir.
Aşağıdaki örnekte kullanılan "points.csv" dosyasının içeriği şöyledir:
7,8
2,4
6,4
3,2
6,5
5,7
3,3
1,4
5,4
7,7
7,6
2,1
#----------------------------------------------------------------------------------------------------------------------------
NCLUSTERS = 3
import numpy as np
from scipy.spatial.distance import euclidean
def kmeans(dataset, nclusters, centroids=None):
nrows = dataset.shape[0]
clusters = np.full(nrows, -1)
if centroids == None:
centroids = rand_centroids(dataset, nclusters)
change_flag = True
while change_flag:
change_flag = False
for i in range(nrows):
min_val = np.inf
min_index = -1
for k in range(nclusters):
if not np.any(np.isnan(centroids[k])):
result = euclidean(dataset[i], centroids[k])
if result < min_val:
min_val = result
min_index = k
if clusters[i] != min_index:
change_flag = True
clusters[i] = min_index
for i in range(nclusters):
idataset = dataset[clusters == i]
centroids[i] = np.mean(idataset, axis=0) if len(idataset) else np.nan
dataset_clusters = []
inertia = 0
for i in range(nclusters):
idataset = dataset[clusters == i]
dataset_clusters.append(idataset)
inertia += np.sum((idataset - centroids[i]) ** 2) if len(idataset) > 0 else 0
return clusters, dataset_clusters, centroids, inertia
def rand_centroids(dataset, nclusters):
ncols = dataset.shape[1]
centroids = np.zeros((nclusters, ncols), dtype=np.float32)
for i in range(ncols):
maxi = np.max(dataset[:, i])
mini = np.min(dataset[:, i])
rangei = maxi - mini
centroids[:, i] = mini + rangei * np.random.random(nclusters)
return centroids
dataset = np.loadtxt('points.csv', delimiter=',', dtype='float32')
clusters, dataset_clusters, centroids, inertia = kmeans(dataset, NCLUSTERS)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(NCLUSTERS):
plt.scatter(dataset_clusters[i][:, 0], dataset_clusters[i][:, 1])
plt.scatter(centroids[:, 0], centroids[:, 1], 60, color='red', marker='s')
legends = [f'Cluster-{i}' for i in range(1, NCLUSTERS + 1)]
legends.append('Centroids')
plt.legend(legends)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
K-Means kümeleme algoritması sklearn.cluster modülü içerisinde KMeans isimli sınıfla gerçekleştirilmiştir. SInıfın __init__ metodunun
parametrik yapısı şöyledir:
KMeans(n_clusters=8, *, init='k-means++', n_init='warn', max_iter=300, tol=0.0001, verbose=0, random_state=None, copy_x=True, algorithm='lloyd')
Metodun birinci parametresi ayrıştırılacak küme sayısını belirtmektedir. Metodun init parametresi başlangıçtaki rastgele ağırlık merkezlerinin
nasıl oluşturulacağını belirtmektedir. Bu parametrenin default değeri "kmeans++" biçimindedir. Metodun n_init parametresi
algoritmanın kaç kere çalıştırılıp en iyisinin bulunacağını belirtmektedir. Bu parametrenin eskiden default değeri 10'du.
Ancak güncel versiyonlarda 1 değeri kullanılmaya başlanmıştır. max_iter parametresi bir çalıştırmanın toplamda en fazla kaç iterasyon süreceğini
belirtmektedir. Bu parametrenin default değerinin 300 olduğunu görüyorsunuz. Yani algoritma 300 adımda kararlı noktaya gelmezse
sonlandırılmaktadır. Metodun algrithm parametresi kullanılacak algoritmanın varyasyonunu belirtmektedir. Bu parametrenin default değeri
"llyod" biçimindedir. K-Means algoritmaları arasında küçük farklılıklar vardır. Yukarıda açıkladığımız algoritma MacQueens algoritmasıdır.
LLyod algoritması aynı işlemi ağırlık merkezi temelli yapar. İki algoritma özünde aynıdır. Ancak noktaların durumuna göre hız açısından farklılıklar
söz konusu olabilmektedir.
KMeans nesnesi yaratıldıktan sonra kümeleme algoritması fit metodu ile gerçekleştirilir. fit metodu parametre olarak veri kümesini
iki boyutlu bir matris biçiminde bizden alır ve kümelemeyi yapar ve nesnenin kendisiyle geri döner. Kümeleme işlemi bittikten sonra nesnenin aşağıdaki özniteliklerinden
önemli bilgiler elde edilebilmektedir:
cluster_centers_: Bu öznitelik nihai durumdaki ağırlık merkezlerini vermektedir.
labels_: Her noktanın hangi küme içerisinde yer aldığına yönelik NumPy dizisini belirtir. Buradaki kümeler 0'dan başlanarak numaralandırılmıştır. Böylece
aslında hangi noktaların hangi kümelerin içerisinde odluğu da dataset[km.labels_ == n] işlemi ile elde edilebilr.
inertia_: Bu öznitelik tüm noktaların kendi ağırlık merkezlerine uzaklıklarının karelerinin toplamını vermektedir.
n_iter_: Bu öznitelik sonuca varmak için kaç iterasyonun uygulandığını bize verir.
n_features_in_: Veri kümesindeki sütunların sayısıdır.
Sınıfın transform metodu burada önemli bir işlem yapmamaktadır. transform metoduna biz birtakım noktalar verdiğimizde metot bize o noktaların tüm
ırlık merkezlerine uzaklığını vermektedir. Benzer biçimde fit_transform metodu da önce fit işlemi yapıp kümelemeyi gerçekleştirir sonra da
transform işlemi yapar. Ancak bu sınıfta transform ve fit_transform çok kullanılan metotlar değildir. Yani:
km.fit(dataset)
result = km.transform(dataset)
işlemi ile:
result = fit_transform(dataset)
aynıdır. Yani fit_transform işlemi ile biz önce K-Means algoritmasını uygulayıp sonra her noktanın tüm ağırlık merkezlerine uzaklıklarını
elde ederiz.
Sınıfın predict metodu bizden alınan noktaların hangi kümeler içerisinde yer alabileceğini belirtmektedir. Yani aslında metot
aldığı noktaların tüm ağırlık merkezlerine uzaklığını hesap edip en yakın ağırlık mrkezinin ilişkin olduğu kğmeyi vermektedir.
Sınıfın fit_predict isimli metodu önce fit işlemi yapıp sonra predict işlemi yapmaktadır. Yani:
predict_result = km.fit(dataset).predict(dataset)
İşleminin eşdeğeri şöyledir:
predict_result = fit_predict(dataset)
Aşağıdaki KMeans sınıfının kullanımına ilişkin bir örnek verilmiştir. Buradaki "points.csv" aşağıdaki bir örnek dosyadır:
7,8
2,4
6,4
3,2
6,5
5,7
3,3
1,4
5,4
7,7
7,6
2,1
#----------------------------------------------------------------------------------------------------------------------------
NCLUSTERS = 3
import numpy as np
dataset = np.loadtxt('points.csv', delimiter=',', dtype='float32')
import matplotlib.pyplot as plt
plt.title('Points')
plt.scatter(dataset[:, 0], dataset[:, 1])
plt.show
from sklearn.cluster import KMeans
km = KMeans(n_clusters=NCLUSTERS, n_init=10)
km.fit(dataset)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(NCLUSTERS):
plt.scatter(dataset[km.labels_ == i, 0], dataset[km.labels_ == i, 1])
plt.scatter(km.cluster_centers_[:, 0], km.cluster_centers_[:, 1], 60, color='red', marker='s')
legends = [f'Cluster-{i}' for i in range(1, NCLUSTERS + 1)]
legends.append('Centroids')
plt.legend(legends)
plt.show()
x = np.array([[1, 2], [5, 7], [8, 9]], dtype='float32')
result = km.transform(x)
print(result)
predict_result = km.predict(x)
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de K-Means kümeleme yöntemini daha önce yaptığımız "iris veri kümesi" üzerinde deneylim. Anımsanacağı gibi "iris.csv"
veri kümesinde ilk sütun sıra numalarından oluşuyordu son sütun da zambakların sınıflarını belirtiyordu. Toplamda üç zambak sınıfının
olduğunu biliyoruz. O zaman biz bu veri kümesini üç kümeye ayıralım. Tabii özellik ölçeklemesini de uygulayalım.
Aşağıdaki örnekte biz kümelenmiş olan noktaların grafiğini doğrudan çizdirmiyoruz. Çünkü bu örnekte dört özellik vardır.
Bizim iki boyutlu grafik çizebilmemiz için iki özelliğin olması gerekir. Biz de henüz görmemiş olsak da dört özelliği "PCA (Prinsiple Component Analysis)"
denilen teknik ile iki özelliğe indirgeyip grafiği öyle çizdik.
#----------------------------------------------------------------------------------------------------------------------------
NCLUSTERS = 3
import pandas as pd
df = pd.read_csv('iris.csv')
dataset = df.iloc[:, 1:-1].to_numpy()
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
scaled_dataset = mms.fit_transform(dataset)
from sklearn.cluster import KMeans
km = KMeans(n_clusters=NCLUSTERS, n_init=20)
km.fit(scaled_dataset)
print(km.labels_)
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
decomposed_dataset = pca.fit_transform(dataset)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(NCLUSTERS):
plt.scatter(decomposed_dataset[km.labels_ == i, 0], decomposed_dataset[km.labels_ == i, 1])
# plt.scatter(centroids[:, 0], centroids[:, 1], 60, color='red', marker='s')
legends = [f'Cluster-{i}' for i in range(1, NCLUSTERS + 1)]
plt.show()
import numpy as np
predict_data = np.array([[5.0,3.5,1.6,0.6], [4.8,3.0,1.4,0.3], [4.6,3.2,1.4,0.2]], dtype='float64')
transformed_predict_data = mms.transform(predict_data)
predict_result = km.predict(transformed_predict_data)
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
K-Means yönteminde bizim işin başında noktaları kaç kümeye ayıracağmızı bilmemiz gerekmektedir. Pekiyi biz bunu nasıl belirleyebiliriz?
Bazen problemin kendi içeisinde zaten küme sayısı bilinmektedir. Örneğin birisi bize elma, armut, kayısı, şeftali, karpuz resimleri vermiş olsun.
Ama biz hangi resmin hangiyi meyveye ilişkin olduğunu bilmiyor olalım. Bu problemde biz resimlerin beş farklı meyveye ilişkin olduunu zaten bilmekteyiz.
Ancak hangi meyvelerin hangi resimlerle ilişkili oludğunu bilmemekteyiz. Tabii pek çok durumda biz küme sayısını da bilmiyor durumda oluruz.
En iyi küme syaısının belirlenmesi için temelde iki yöntem çok kullanılmaktadır:
1) Dirsek Yötmei (Elbow Method)
2) Silhouette Yöntimi (Silhouette Method)
Dirsek yönteminde önce 1'den başlanarak n'e kadar küme sayıları ile kümeleme yapılır. Her kümedeki toplam atalet elde edilir.
(Toplam ataletin KMeans sınıfının inertia_ elemanı ile verildiğini anımsayınız. Toplam atalet her noktanın kendi ağırlık merkezine
uzaklığının kareleri toplamıdır.) Sonra yatay eksende küme sayısı düşey eksende toplam atalet olacak biçimde bir grafik çizilir.
Bu grafikta eğrinin yataya geçtiği nokta gözle tespit edilir. Eğrinin yataya geçtiği noktaya "dirsek noktası (elbow point)" denilmektedir.
Aşağıdaki örnekte daha önce kullanmış olduğumuz "points.csv" noktaları için dirsek grafiği çizilmiştir. Bu örnekte toplam ataletler
aşağıdaki gibi bir liste içlemi ile toplanmıştır:
total_inertias = [KMeans(n_clusters=i, n_init=10).fit(dataset).inertia_ for i in range(1, 10)]
fit metodu nesnenin kendisine geri döndüğü için kompaks bir yazım sağlanmıştır.
Aşağıdaki örnekte dirsek noktası gözle 3 olarak tespit edilmiştir. Örnekte kullanılan "points.csv" dosyasının içeriği şöyledir:
7,8
2,4
6,4
3,2
6,5
5,7
3,3
1,4
5,4
7,7
7,6
2,1
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('points.csv', delimiter=',')
from sklearn.cluster import KMeans
total_inertias = [KMeans(n_clusters=i, n_init=10).fit(dataset).inertia_ for i in range(1, 10)]
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Elbow Graph')
plt.plot(range(1, 10), total_inertias, marker='o', markersize=12)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de zambak veri kümesi için dirsek yöntemiyle küme sayısını yine görsel olarak belirleyelim. Aşağıdaki örnekte küme sayısı
3 (4 de olabilir) olarak belirlenmiştir. Dirsek noktası eğrinin kuvvetli düşüşü bıraktı noktadır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('iris.csv')
dataset = df.iloc[:, 1:-1].to_numpy()
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
scaled_dataset = mms.fit_transform(dataset)
from sklearn.cluster import KMeans
total_inertias = [KMeans(n_clusters=i, n_init=10).fit(scaled_dataset).inertia_ for i in range(1, 15)]
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Elbow Graph')
plt.plot(range(1, 15), total_inertias, marker='o', markersize=12)
plt.xticks(range(1, 15))
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Silhouette yönteminde yine 2'den başlanarak belli sayıda küme için çözüm elde edilir. Sonra her çözüm için "silhoutte skor"
denilen bir değer elde edilmektedir. Bu değerin en yüksek olduğu kme sayısından bir fazla küme sayısı en iyi küme sayısı olarak belirlenmktedir.
Silhouette skor sklearn.metrics modülündeki silhouette_score isimli fonksiyonla elde edilebilmektedir. Bu fonksiyona parametre olarak
veri kümesi ve kümelenmiş sonuçlar (yani labels_ değeri) verilmektedir. Silhouette skor işlemi tek kümeyle yapılamamaktadır.
Aşağıdaki örnekte dana önceden kullandığımız "points.csv" verileir için Silhouette skor değeri elde edilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('points.csv', delimiter=',')
from sklearn.cluster import KMeans
km_labels = [KMeans(n_clusters=i, n_init=10).fit(dataset).labels_ for i in range(2, 10)]
from sklearn.metrics import silhouette_score
sc_list = []
for index, labels in enumerate(km_labels, 2):
sc = silhouette_score(dataset, labels)
print(f'{index} ---> {sc}')
sc_list.append(sc)
optimal_cluster = np.array(sc).argmax() + 2 + 1
print(f'optimal cluster: {optimal_cluster}')
km = KMeans(n_clusters=optimal_cluster, n_init=10).fit(dataset)
print(km.labels_)
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte yine zambak verileri için en uygun küme sayısı Silhouette skor yöntemi ile elde edilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('iris.csv')
dataset = df.iloc[:, 1:-1].to_numpy()
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
scaled_dataset = mms.fit_transform(dataset)
from sklearn.cluster import KMeans
km_labels = [KMeans(n_clusters=i, n_init=10).fit(dataset).labels_ for i in range(2, 15)]
import numpy as np
from sklearn.metrics import silhouette_score
sc_list = []
for index, labels in enumerate(km_labels, 2):
sc = silhouette_score(dataset, labels)
print(f'{index} ---> {sc}')
sc_list.append(sc)
optimal_cluster = np.array(sc).argmax() + 2 + 1
print(f'optimal cluster: {optimal_cluster}')
km = KMeans(n_clusters=optimal_cluster, n_init=10).fit(dataset)
print(km.labels_)
#----------------------------------------------------------------------------------------------------------------------------
Biz yukarıdaki örneklerde bütün sonları nümerik olan veri kümesi üzerinde kümeleme işlemi yaptık. Pekiyi kategorik sütunlar da
içeren (mixed biçimde olan) veri kümelerinde kümeleme işlemi nasıl yapılmaktadır? Bunun birka. yöntem kullanılabilmektdir.
Bazı uzaklık hesaplama yöntemleri kategorik verilerde de kullanılabilmektedir. Dolayısıyla bazı uygulamacılar bu biçimde
kategorik sütun da içeren veri kümeleri için uzaklık yöntemini "Öklit" uzaklığı yerine bunun için kullanılan uzaklık yöntemiyle
değiştirebilmektedir. Bunun "Gower" uzaklığı denilen bir uzaklık yöntemi kullanılabilmektedir. Ancak maalesef scikit-learn
henüz bu "gower" uzaklığını desteklememekktedir. Bu uzaklık bazı işlemlerin manuel yapılması gerekebilmektedir. Diğer bir yöntem de
sinir ağlarında yaptığımız gibi one-hot encoding işlemi uygulayabiliriz. Diğer bir yöntem kategorik sütunları tamamen veri
kümesinden atmak olabilir.
Aşağıdaki örnekte "müşterilere" ilişkin kategorik sütunlar içeren "mixed" bir veri kümesi üzerinde K-Means yöntemi uygulanmıştır.
Bu veri kümesini aşağıdaki bağlantıdan indirebilirsiniz:
https://www.kaggle.com/datasets/dev0914sharma/customer-clustering?resource=download
Bu veri kümesinde şu sütunlar bulunmaktadır:'Sex', 'Marital status', 'Age', 'Education', 'Income', 'Occupation', 'Settlement size'.
Nurada "Sex" iki kategorili, "Marital status" iki kategorili, "Education" 4 kategorili, "Occupation" 3 kategorili ve "Settlement size" 3
kategorili sütunlardır. Biz bu örnekte ikiden fazla olan kategorileri "one hot encoding" dünüştürmesine soktuk. Sonra da tüm tabloya min-max ölçeklemesi
uyguladık. Tablonun ilk sütunu indeks numarası belirtmektedri. Bunu veri kğmesinden attık.
Bu örnekte optimal küme sayısını Silhouette yöntemi ile belirleyip verileri PCA işlemi ile iki sütuna indirgedikten sonra saçılma diyagramını çizdik.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('segmentation-data.csv')
df.drop(labels=['ID'], axis=1, inplace=True)
ohe_df = pd.get_dummies(df, columns=['Education', 'Occupation', 'Settlement size'])
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
scaled_dataset = mms.fit_transform(ohe_df.to_numpy())
from sklearn.cluster import KMeans
total_inertias = [KMeans(n_clusters=i, n_init=10).fit(scaled_dataset).inertia_ for i in range(1, 100)]
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 8))
plt.title('Elbow Graph')
plt.plot(range(1, 100), total_inertias, marker='o')
plt.xticks(range(1, 100, 5))
plt.show()
km_labels = [KMeans(n_clusters=i, n_init=10).fit(scaled_dataset).labels_ for i in range(2, 100)]
import numpy as np
from sklearn.metrics import silhouette_score
sc = np.array([silhouette_score(scaled_dataset, labels) for labels in km_labels])
optimal_cluster = np.array(sc).argmax() + 2 + 1
print(f'optimal cluster: {optimal_cluster}')
km = KMeans(n_clusters=optimal_cluster, n_init=100)
km.fit(scaled_dataset)
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
transformed_dataset = pca.fit_transform(scaled_dataset)
plt.figure(figsize=(20, 16))
plt.title('Clustered Points')
for i in range(1, optimal_cluster + 1):
plt.scatter(transformed_dataset[km.labels_ == i, 0], transformed_dataset[km.labels_ == i, 1])
plt.show()
plt.figure(figsize=(20, 16))
plt.title('Clustered Points')
for i in range(1, optimal_cluster + 1):
plt.scatter(transformed_dataset[km.labels_ == i, 0], transformed_dataset[km.labels_ == i, 1])
xmean = np.mean(transformed_dataset[km.labels_ == i, 0])
ymean = np.mean(transformed_dataset[km.labels_ == i, 1])
plt.text(xmean, ymean, f'{i}')
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de MNIST veri kümesine K-Means yöntemini uygulayalım. Buradaki problemimiz şöyle olabilir: Birisi bize 28x28'lik
binlerce resim vermiş olsun. Bu resimleri veren kişi bu resimlerin 10 farklı olguya ilişkin olduğunu bize söylesin. Ancak hangi resmin
hangi olguya ilişkin olduğunu resmi veren kişi bilmesin. Bu durumda kişi bizden bu resimleri 10 farklı kümeye ayırmamızı istemektedir.
Biz de K-Means yöntemiyle bunu yapmaya çalışalım.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
K-Means yöntemi uç değerlerden oldukça etkilenmektedir. Bu nedenle uygulamacının verilerdeki uç değerleri (outliers) temizlemesi
uygun olur. İleride göreceğimiz başka yöntemler bu uç değerlerden fazlaca etkilenmemektedir. K-Means algoritma karmaşıklığı bakımından hızlı
bir yöntemdir. K-Means yönteminde en önemli noktalardan biri başlanıçta kğmelerin ağırlık merkezlerinin nasıl seçileceğidir. İşte K-Means
yönteminin başlangıçtaki ağırlık merkezlerinin nasıl seçileceğine yönelik birkaç varyasyonu vardır. Bunlardan en fazla tercih edileni KMeans++
denilen algoritmadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Çok kullanılan diğer bir kümeleme yöntem grubu "bağlantı temelli (connecivity based)" ya da "hiyerarşik kümeleme (hierarchical clustering)"
denilen yöntem grubudur. Bu yöntem grubu kendi içerisinde "agglomerative" ve "divisive" olmak üzere ikiye ayrılmaktadır. Agglomarative
yöntemler "aşağıdan yukarı (bottom-up)", divise yöntem ise "yukarıdan aşağıya (top-down)" yöntemlerdir. Uygulamada hemen her zaman
agglomerative yöntemler tercih edilmektedir. Bu yöntemlere de "agglomerative hiyerarşik kümeleme" denilmektedir.
Agglomerative kümeleme algoritması tipik olarak şöyle yürütülmektedir. Toplam n tane nokta olduğunu varsaylım:
1) Önce her nokta ayrı bir küme gibi ele alınır.
2) Tüm noktalarla tüm noktalar arasındaki uzaklık hesaplanır. Bu bir çeşit simetrik matris oluşturacaktır.
3) En yakın iki nokta tespit edilip bir küme olarak birleştirilir. Artık bu küme tek bir nokta gibi ele alınacaktır.
Dolayısıyla artık elimizde n - 1 tane nokta bulunmaktadır. Burada 2. adıma dönülerek yine tüm noktalarla tüm noktalar A
arasındaki uzaklıklar hesaplanır. Ancak iki elemanlı küme sanki tek bir nokta gibi değerlendirilecektir. Bu aşamadan sonra yenidne bir
birleştirme yapılır. Ve böylece n - 2 tane nokta elde edilir. İşlemler istenen k tane küme elde edilene kadar devam ettirilir.
Algoritmadaki önemli noktalar şunlardır:
- Yine noktalar arasındaki uzaklıklar değişik yöntemlerle ölçülebilmektedir. En çok kullanılan uzaklık ölçütü yine Öklit uzaklığıdır.
- Birden fazla noktadan oluşan küme tek nokta olarak nasıl davranacaktır? Bu durumda bu kümeye uzaklık nasıl hesaplanacaktır? İşte
burada birkaç hesaplama yöntemi kullanılabilmektedir:
Min Yöntemi: Kümelerin en yakın elemanları tespit edilip uzaklık bu en yakın elemanlara göre hesaplanır.
Max Yöntemi: Kümelerin en uzak elemanları tespit edilip uzaklık bu en uzak elemanlara göre hesaplanır.
Grup Ortalaması Yöntemi: Noktalarla kümenin tüm noktalarının uzaklıkları hesaplanıp ortalama uzaklık elde edilir ve bu ortalama uzaklık
dikkate alınır.
Ward Yöntemi: Noktalarla kümenin tüm noktalarının uzaklıklarının karesi elde edilir ve bu kareli ortalama uzaklık
olarak dikkate alınır.
Uygulamada en fazla "ward yöntemi" denilen yöntem kullanılmaktadır.
Agglomerative hiyerarşik kümelemede hangi noktaların ve kümelerin hangi nokta ve kümelerle birleştirildiğine yönelik bir ağaç grafiği
çizilebilmektedir. Buna "dendrogram" denilmektedir.
Agglomaerative hiyerarşik kümelemede her kümeleme işleminde aynı kümeler elde edilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Agglomerative hiyerarşik kümeleme işlemleri için scikit-learn kütüphanesinde AgglomerativeClustering isimli bir sınıf bulundurulmuştur.
Sınıfın __init__ metodunun parametrik yapısı şöyledir:
sklearn.cluster.AgglomerativeClustering(n_clusters=2, *, affinity='deprecated', metric=None, memory=None,
connectivity=None, compute_full_tree='auto', linkage='ward', distance_threshold=None, compute_distances=False)
Metodun n_clusters parametresi oluşturulacak nihai küme sayısını belirtmektedir. affinity parametresi "deprecated" yapılmış ve bunun
yerine "metric" kullanılmaya başlanmıştır. (Yani bu anlamda terminolojide bir değişiklik yapılmıştır.) affinity parametresi yerine 1.4 ve yukarısında
metric ismi kullanılacaktır. Eğer sklearn veriyonunuz 1.4'ten geri ise metirc yerine affinity parametresini kullanınız. metrik parametresi uzaklık hesaplama
yöntemini belirtmektedir. Buradaki None default değeri "Öklit uzaklığı" olarak ele alınmaktadır. Yani bu parametreye bir şey girmesek
sanki "euclidean" girmiş gibi oluruz. linkage parametresi kümeye ilişkin noktaların temsil edildiği noktayı belirlemek için kullanılmaktadır.
Bu parametreye şu değerler girilebilir: "ward", "average", "complete ya da maximum", "single". Bu parametrenin default değeri "ward"
biçimdedir. Bu durum kümenin tüm noktalarına uzaklıkların karelerinin ortalaması yönteminin kullanılacağı anlamına gelir. "average"
grup ortalaması anlamına, "complete ya da maximum" maksimum uzaklık anlamına "single" ise minimum uzaklık anlamına gelir.
Metodun diğer parametreleri çok önemli değildir.
AgglomerativeClustering nesnesi yaratıldıktan sonra yine sınıfın fit metoduyla işlemler yapılır. Yani kümeleme işlemini asıl yapan metot fit metodudur.
fit işleminden sonra sonuçlar nesnenin özniteliklerinden alınabilir. Nesnenin özniteliklerleri şunlardır:
nclusters_: Elde edilen müme sayısını belirtmektedir. Tabii küme sayısını aslında biz vermekteyiz. Ancak __init__ metodunun distance_threshold isimli
parametresi için bir değer girilirse bu durumda bu eşik uzaklığın ötesinde kümeleme yapılmamaktadır. Eğer distance_threshold parametresi girilirse
bu durumda __init__ metodunun birinci parametresi None girilmelidir. Çünkü küme sayısı artık bu şik uzaklığa bağlı olarak hesaplanacktır. Eğer distance_threshold
için bir değer girilirse aynı zamanda __init__ metodunun compute_full_tree parametresi True girilmek zorundadır.
labels_: Tıpkı KMenas sınıfında olduğu gibi noktaların sırasıyla hangi kümeler içerisinde yer aldığını blirten bir NumPy dizisidir.
n_features_in_: fit işlemine sokulan veri kğmesindeki sütun sayısını belirtmektedir.
distances_: Eğer nesne yaratılırken compute_distances parametresi True geçilmişse bu örnek özniteliği oluşturulur. Bu durumda bu elemanda
uzaklık değerleri bulunur.
Aşağıdaki örnekte daha önce üzerinde çalıştığımız "points.csv" noktaları bu kez AgglomerativeClustering sınıfıyla kümelenmiştir. Buradaki
"points.csv" dosyasının içeriği şöyledir:
7,8
2,4
6,4
3,2
6,5
5,7
3,3
1,4
5,4
7,7
7,6
2,1
#----------------------------------------------------------------------------------------------------------------------------
NCLUSTERS = 3
import numpy as np
dataset = np.loadtxt('points.csv', delimiter=',')
from sklearn.cluster import AgglomerativeClustering
ac = AgglomerativeClustering(n_clusters=NCLUSTERS, affinity='euclidean', linkage='ward', compute_distances=True)
ac.fit(dataset)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(NCLUSTERS):
plt.scatter(dataset[ac.labels_ == i, 0], dataset[ac.labels_ == i, 1])
legends = [f'Cluster-{i}' for i in range(1, NCLUSTERS + 1)]
plt.legend(legends)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Şimdide Agglomerative kümeleme yöntemini "zambak veri kümesine (iris.csv)" uygulayalım. Buradan elde edilen sonuçları K-Means kümelemesinden elde
edilen sonuçlarla kaşılaştırdığımızda birbiren benzediğini ancak birkaç noktanın farklı kümelendiğini görmekteyiz.
#----------------------------------------------------------------------------------------------------------------------------
NCLUSTERS = 3
import pandas as pd
df = pd.read_csv('iris.csv')
dataset = df.iloc[:, 1:-1].to_numpy()
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
scaled_dataset = mms.fit_transform(dataset)
from sklearn.cluster import KMeans
km = KMeans(n_clusters=NCLUSTERS, n_init=20)
km.fit(scaled_dataset)
print(km.labels_)
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
decomposed_dataset = pca.fit_transform(dataset)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(NCLUSTERS):
plt.scatter(decomposed_dataset[km.labels_ == i, 0], decomposed_dataset[km.labels_ == i, 1])
# plt.scatter(centroids[:, 0], centroids[:, 1], 60, color='red', marker='s')
legends = [f'Cluster-{i}' for i in range(1, NCLUSTERS + 1)]
plt.show()
import numpy as np
predict_data = np.array([[5.0,3.5,1.6,0.6], [4.8,3.0,1.4,0.3], [4.6,3.2,1.4,0.2]], dtype='float64')
transformed_predict_data = mms.transform(predict_data)
predict_result = km.predict(transformed_predict_data)
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
KMeans sınıfında bir predict metodu vardı. Bu metot mevcut ağırlık merkezlerini dikkate alarak noktanın hangi ağırlık merkezine yakın
olduğunu hesaplayıp noktanın sınıfını ona göre belirliyordu. Ancak AgglomerativeClustering sınıfında bir predict metodu yoktur. Çünkü yöntemde
bir ağırlık mekezi olmadığı için kestirimi yapılacak noktanın neye göre kestiriminin yapılacağı belli değildir. Kümeleme işlemi bütün noktalar
temelinde yapılmaktadır. Gerçi sınıfın fit_predict isimli bir metodu vardır. Ancak bu metot önce fit işlemi yapıp sonra labeles_ örnek
özniteliğini vermektedir. Başka bir deyişle:
result = ac.fit_predict(dataset)
işlemi ile aşağıdaki işlem eşdeğerdir:
ac.fit(dataset)
result = ac.labels_
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Daha önceden de belirtildiği gibi elimizde zaten hangi noktaların hangi sınıflara ilişkin olduğuna yönelik bir bilgi varsa
burada kestirim amacıyla "denetimli yöntemlerin" kullanılması önerilmektedir. Fakat yine de biz elimizde noktaların hangi sınıflardan
olduğu bilindiği halde kümeleme yöntemlerini kullanabiliriz. Örneğin mademki elimizde zambak verilerinin hangi sınıflara ilişkin olduğu bilgisi var o zaman biz
bu yöntemlerin başarısını bir anlamda ölçebiliriz. Ancak burada dikkat edilmesi gereken nokta kümeleme ile elde edilen küme numaralarının
orijinal sınıf numaralarına sayısal olarak karşı gelmeyebileceğidir. Kaldı ki değişik kümeleme yöntemlerinde (K-Means gibi) programın
her çalıştırılmasında küme numaraları da farklılaşabilmektedir. Örneğin problem KMeans sınıfı ile çözüldüğünde 1 numaralı küme
AgglomerativeClustering sınıfıyla çözüldüğünde 0 numaralı küme biçiminde elde edilebilir ve bu küme de aslında bizim veri kümemizdeki 2
numaralı kümeyi belirtiyor olabilir.
Aşağıdaki örnekte zambak veri kümesi KMeans ve AgglomerativeClustering sınıflarıyla çözülmüş, sütunlar ikiye indirgenerek
iki boyutlu grafik çizilmiştir. Burada farklı elemanlar kırmızı ile boyanmıştır. Ancak bu programı birakç kere çalıştırıp
küme numalarının tesadüfen uyuştuğu grafiği dikkate alınız. Sınıfta yapılan denemede iki kümeleme yöntemi arasında 6 noktanın
kümeleri uyuşmamaktadır.
#----------------------------------------------------------------------------------------------------------------------------
NCLUSTERS = 3
import pandas as pd
df = pd.read_csv('iris.csv')
dataset = df.iloc[:, 1:-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
scaled_dataset = mms.fit_transform(dataset)
from sklearn.cluster import KMeans
km = KMeans(n_clusters=NCLUSTERS, n_init=20)
km.fit(scaled_dataset)
print(km.labels_)
from sklearn.cluster import AgglomerativeClustering
ac = AgglomerativeClustering(n_clusters=NCLUSTERS, affinity='euclidean', linkage='ward')
ac.fit(scaled_dataset)
print(ac.labels_)
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
decomposed_dataset = pca.fit_transform(dataset)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(NCLUSTERS):
plt.scatter(decomposed_dataset[ac.labels_ == i, 0], decomposed_dataset[ac.labels_ == i, 1])
different_rows = km.labels_ != ac.labels_
plt.scatter(decomposed_dataset[different_rows,0], decomposed_dataset[different_rows,1], color='red')
# plt.scatter(centroids[:, 0], centroids[:, 1], 60, color='red', marker='s')
legends = [f'Cluster-{i}' for i in range(1, NCLUSTERS + 1)]
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
scikit-learn içerisinde kümeleme ve sınıflandırma işlemleri için rastgele veri üreten bazı fonksiyonlar oluşturulmuştur. Bunlar sklearn.datasets
modülü içerisindedir. make_blobs fonksiyonu belli bir merkezden hareketle onun çevresinde rastgele noktalar üretmektedir. Fonksiyonun parametrik yapısı
şöyledir:
sklearn.datasets.make_blobs(n_samples=100, n_features=2, *, centers=None, cluster_std=1.0,
center_box=(-10.0, 10.0), shuffle=True, random_state=None, return_centers=False)
Buradaki n_samples parametresi üretlecek noktaların sayısını belirtmektedir. n_features parametresi üretilecek rastgele verilerin kaç sütundan
oluşacağını belirtmektedir. centers parametresi label sayısını belirtir. Yani toplam kaç merkezden hareketle rastgele noktalar üretilecektir?
cluster_std parametresi rastgele noktaların küme içerisinde birbirinden uzaklığını ayarlamakta kullanılmaktadır. Bu değer küçültülürse noktalar daha toplaşık,
büyütülürse noktalar daha merkezden uzak üretilmektedir. center_box parametresi ikili bir demet almaktadır. Rastgeele üretilecek değerlerin
aralığını belirtir. Default değerler -10 ile +10 arasındadır. random_state parametresi rassal sayı üreticisi için tohum değeri belirtmektedir.
Bu parametreye spesifik bir değer girilirse hep aynı noktalar elde edilir. Bu parametreye değer girilmezse programın her çalışmasında farklı
noktalar elde edilmektedir.
Fonksiyon bize normal olarak iki elemanlı bir demet vermektedir. Bu demetin birinci elemanı rastgele noktaları belirtmektedir. İkinci elemanı ise
onların sınıflarını belirtir. Eğer fonksiyonda return_centers parametresi True girilirse bu durumda fonksiyon üçlü bir demete geri dönmektedir.
Demetin üçüncü elemanı kümelere ilişkin merkez noktalarını belirtir.
Aşağıdaki make_blobs fonksiyonu ile rastgele noktalar elde edilmiş daha sonra bu noktalar KMeans ve AgglomerativeClustering sınıflarıyla
kümelenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
NCLUSTERS = 3
from sklearn.datasets import make_blobs
dataset, labels = make_blobs(n_samples=1000, n_features=2, centers=3, cluster_std=1)
print(dataset)
print(labels)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Random Points')
for i in range(NCLUSTERS):
plt.scatter(dataset[labels == i, 0], dataset[labels == i, 1])
plt.show()
from sklearn.cluster import KMeans
km = KMeans(n_clusters=NCLUSTERS, n_init=20)
km.fit(dataset)
plt.figure(figsize=(10, 8))
plt.title('K-Means Clustered Points')
for i in range(NCLUSTERS):
plt.scatter(dataset[km.labels_ == i, 0], dataset[km.labels_ == i, 1])
plt.show()
from sklearn.cluster import AgglomerativeClustering
ac = AgglomerativeClustering(n_clusters=NCLUSTERS, affinity='euclidean', linkage='ward')
ac.fit(dataset)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(NCLUSTERS):
plt.scatter(dataset[ac.labels_ == i, 0], dataset[ac.labels_ == i, 1])
# plt.scatter(centroids[:, 0], centroids[:, 1], 60, color='red', marker='s')
legends = [f'Cluster-{i}' for i in range(1, NCLUSTERS + 1)]
plt.show()
dataset, labels, centers = make_blobs(n_samples=100, n_features=2, centers=3, cluster_std=1, return_centers=True)
plt.figure(figsize=(10, 8))
plt.title('Random Points')
for i in range(NCLUSTERS):
plt.scatter(dataset[labels == i, 0], dataset[labels == i, 1])
plt.scatter(centers[:, 0], centers[:, 1], 60, color='red', marker='s')
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
sklearn.datasets modülünde make_classification isimli benzer bir fonksiyon da bulunmaktadır. Bu fonksiyon özellikle sınıflandırma
problemleri için rastgele noktalar üretmektedir. Fonksiyonun parametrik yapısı şöyledir:
sklearn.datasets.make_classification(n_samples=100, n_features=20, *, n_informative=2, n_redundant=2, n_repeated=0,
n_classes=2, n_clusters_per_class=2, weights=None, flip_y=0.01, class_sep=1.0, hypercube=True,
shift=0.0, scale=1.0, shuffle=True, random_state=None)
Fonksiyonun birinci parametresi üretilecek nokta sayısını ikinci parametresi sütun sayısını belirtmektedir. Fonksiyonun n_classes
parametresi ise üretilecek noktaların ilişkin olduğu sınıfların sayısını belirtmektedir. Bu parametrenin default değeri 2'dir.
Fonksiyon yine bize ikili bir demet verir. Demetin birinci elemanı rastgele üretilen noktalardan ikinci elemanı ise bunların ilişkin olduğu sınıflardan oluşmaktadır.
make_classification fonksiyonu standart normal dağılma uygun rastgele noktalar üretmektedir.
#----------------------------------------------------------------------------------------------------------------------------
from sklearn.datasets import make_classification
dataset, labels = make_classification(100, 10, n_classes=3)
print(dataset)
print(labels)
#----------------------------------------------------------------------------------------------------------------------------
sklearn.datasets modülü içerisindeki make_circles isimli fonksiyon eliptik tarzda veri üretmek için kullanılmaktadır.
Eliptik tarzda veriler birbirlerini çevreleyen tarzda verilerdir. Bunlar özellikle bazı kümeleme algoritmalarını test etmek için kullanılmaktadır.
Fonksiyonun parametrik yapısı şöyledir:
sklearn.datasets.make_circles(n_samples=100, *, shuffle=True, noise=None, random_state=None, factor=0.8)
Fonksiyon her zaman iki sütuna ilişkin nokta üretmektedir. Fonksiyonun birinci parametresi üretileceknoktaların sayısını belirtir.
Default durumda fonksiyon iki sınıf üretmektedir. Default durumda her iki sınıftan da eşit sayıda nokta üretilmektedir. Eğer birinci parametre
iki elemanlı bir demet olarak girilirse bu durumda 0 ve 1 sınıflarından kaçar tane değer üretileceği de gizlice belirtilmiş olur. Örneğin:
dataset, labels = make_circles((100, 200))
Burada 100 tane 0, 200 tane 1 sınıfına ilişkin rastgele nokta üretilecektir. Fonksiyonun factor parametresi iç içe çemberlerin birbirine yakınlığını
ayarlamak için kullanılmaktadır. Bu parametres (0, 1) arasında değer alır. 1'ye yaklaşıldıkça çemberler birbirine yaklaşır, 0'a yaklaşıldıkça çemberler
biribirinden uazaklaşır. Bu parametrenin default değeri 0.8 biçimindedir. Fonksiyonun noise parametresi çemberlerin düzgünlüğü konusunda etkili olmaktadır.
Bu parametre 0 ile 1 arasında değer alır. Yğkseltildikçe gürültü artar yani çemberler çember görünümünden çıkar. Testlerde 0.05 gibi değerler
kullanılabilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
from sklearn.datasets import make_circles
dataset, labels = make_circles(100, factor=0.8, noise=0.05)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(2):
plt.scatter(dataset[labels == i, 0], dataset[labels == i, 1])
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Agglomerative hiyerarşik kümelemede sırasıyla hangi noktaların hangi noktalarla birleştirildiğine ilişkin ağaç grafiğine
"dendrogram" denilmektedir. scikit-learn kütüphanesinde doğrudan dendrogram çizmek için fonksiyonlar yoktur. Ancak SciPy
kütüphanesinde scipy.cluster.hierarchy modülü içerisinde bunun için linkage ve dendrogram isimli iki fonksiyon bulunmaktadır.
Burada asıl hiyeararşik kümeleme işlemini yapan linkage isimli fonksiyondur. Bu fonksiyon bize dendrogram çizmek için Nx4 boyutunda bir
matris vermektedir. Fonksiyonun parametrik yapısı şöyledir:
scipy.cluster.hierarchy.linkage(y, method='single', metric='euclidean', optimal_ordering=False)
Fonksiyon zorunlu olarak yalnızca bizden dataset verilerini almaktadır. Aşağıdaki "points.csv" verilerini linkage fonksiyonuna sokmuş olalım:
7,8
2,4
6,4
3,2
6,5
5,7
3,3
1,4
5,4
7,7
7,6
2,1
Burada toplam 12 tane veri vardır. Şimdi linkage fonksiyonu kullanalım:
import numpy as np
dataset = np.loadtxt('points.csv', delimiter=',')
from scipy.cluster.hierarchy import linkage
linkage_data = linkage(dataset)
print(linkage_data)
Şöyel bir matris elde edilmiştir:
[[ 0. 9. 1. 2. ]
[10. 12. 1. 3. ]
[ 2. 4. 1. 2. ]
[ 8. 14. 1. 3. ]
[ 3. 6. 1. 2. ]
[ 1. 7. 1. 2. ]
[13. 15. 1.41421356 6. ]
[16. 17. 1.41421356 4. ]
[11. 19. 1.41421356 5. ]
[ 5. 18. 2. 7. ]
[20. 21. 2.23606798 12. ]]
Matris her zaman toplam nokta sayısından bir eksik satıra ve 4 sütüna sahiptir. Bu matristeki ilk iki sütun birleştirme bilgilerini
belirtmektedir. Nokta sayısı N olmak üzere bu iki stundaki [0, N - 1] arasındaki sayılar asıl noktaları (yaprak düğüm (leaf nodes) da denilmektedir)
belirtir. Her birleştirmeye N'den başlanarak sırasıyla numara verilmektedir. Bu örnekte 0 ile 11 arasındaki tüm sayılar asıl noktalara ilişkindir.
Her birleştirmede sırasıyla 12, 13, 14, ... biçiminde numaralar verilecektir. İlk satırdaki 0 ile 9, "0 numaralı satırdaki noktayla 9 numaralı
satırdaki noktanın birleştirildiği anlamına gelmektedir. Bu birleşmeden elde edilen kümeye 12 numarası verillir. İkinci satırdaki 10 ve 12 değerleri
ise 10 numaralı nokta ile 12 numaralı noktanın bşrleştirileceğini belirtmektedir. 12 numaralı nokta zaten 0 ile 9'un birleşmesinden elde edilen kümedir.
O halde burada önce 0 ile 9 birleştirilmiş bir küme elde edilmiş sonra da bu küme ile 10 birleştirilmiştir. Tabii burada el edilen yeni küme 12
numaralı küme olacaktır. İşlemler bu biçimde devam ettirilmektedir. Matrisin üçüncü sütunu o satırdaki birleştirmenin alınan ölçüe göre uzaklığını belirtmektedir.
Son sütun ise oluşturulan o kümedeki nokta sayısını belirtmektedir.
Linkage bilgisi elde edildikten sonra dendrogram fonksiyonu asıl dendrogram grafiğini çizmektedir. dendrogram fonksiyonu oldukça fazla sayıda
parametreye sahiptir:
scipy.cluster.hierarchy.dendrogram(Z, p=30, truncate_mode=None, color_threshold=None, get_leaves=True,
orientation='top', labels=None, count_sort=False, distance_sort=False, show_leaf_counts=True, no_plot=False,
no_labels=False, leaf_font_size=None, leaf_rotation=None, leaf_label_func=None, show_contracted=False,
link_color_func=None, ax=None, above_threshold_color='C0')
Burada zorunlu parametre ilk parametre olan linkage bilgileridir. Biz bu ilk parametreye linkage fonksiyonundan elde ettiğimiz
matrisi geçeririz. Fonksiyonun parametreleri için SciPy dokümanlarına başvurulabilir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('points.csv', delimiter=',')
from scipy.cluster.hierarchy import linkage, dendrogram
linkage_data = linkage(dataset)
print(linkage_data)
dendrogram(linkage_data, orientation='top')
#----------------------------------------------------------------------------------------------------------------------------
K-Means yöntemiyle Agglomerative Hiyerarşik kümeleme yöntemlerini şöyle karşılaştırabiliriz:
- K-Means algoritması oldukça etkindir. Algoritmik karmaşıklığı O(n * k) biçimindedir. (Burada n nokta sayısını k ise
sınıf sayısını belirtmektedir.) Halbuki Agllomerative hiyerarşik kğmelemede karmaşıklık O(n ** 3) biçimindedir. Her ne kadar Agglomerative
yöntemin SLINK, CLink gibi özelleştirilmiş gerçekleştirimlerinde karmaşıklık O(n ** 2)'ye düşürülüyor olsa da K-Means çoğu zaman çok daha
hızlı bir algoritmadır.
- K-Means algoritmasında ilk ağırlık merkezlerinin seçimine göre algoritmanın her çalıştırılmasında farklı kümeler elde edilebilmektedir.
Halbuki Agglomerative kğmelemede her zaman aynı kümeler elde edilir.
- K-Means yönteminde her kümenin bir ağırlık merkezi olduğu için atalet (inertia) hesabı yapılabilmektedir. Halbuki Agglomerative
yöntemde atalet kavramı kullanılmamaktadır.
- K-Means yöntemin dendrogram çilemez. Halbuki Agglomerative yöntemde hangi kümenin hangi kümeyle birleştirileceğini belirten bir
dendrogram çizilebilmektedir.
- K-Means yönteminde ağırlık merkezlerine uzaklıklar minimize edilmeye çalışıldığı için kestirim yapılabilmektedir. Örneğin KMeans
sınıfının bir predict metodu vardır. Ancak Agglomerative yöntemde bu anlamda bir kestirim yapılamamaktadır. AgglomerativeClustering
sınıfının bir predict metodu yoktur.
- K-Means yönteminde küme sayısı işin başında kesinlikle sabit bir biçimde belirlenmiş olmak zorundadır. Halbuli Agglomerative yöntemde
aslında birleştirme tek küme oluşana kadar devam ettirilebilir. Örneğin bu yöntemde her birleştirmedeki durum kaydedilerek farklı miktarda kümeler
için kümeleme tek hamlede yapılabilmektedir. Oysa K-Means yönteminde her küme sayısı için algoritmayı tamamen baştan başlatmak gerekir.
- K-Means ve Agglomerative yöntemin her ikisi de küresel (spherical) olmayan veri kümelerinde başarısız olmaktadır. Küresel veri demekle
bir merkez etrafında serpişmiş veri anlaşılmaktadır. Eliptik tarzda veriler bu anlamda küresle değildir. Dolayısıyla örneğin make_circles
gibi fonksiyonlar elde ettiğimiz birbirini kapsayan çembersel verilerde bu iki yöntem de başarız olmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte birbirine kapsayan eliptik verilerde K-Means ve Agglomerative yöntemlerin başarıları grafiksel olarak ve
sayısal olarak gösterilmeye çalışılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
from sklearn.datasets import make_circles
dataset, labels = make_circles(100, factor=0.9, noise=0.08)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Random Points')
for i in range(2):
plt.scatter(dataset[labels == i, 0], dataset[labels == i, 1])
plt.show()
from sklearn.cluster import KMeans
km = KMeans(n_clusters=2, n_init=20)
km.fit(dataset)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('K-Means Clustered Points')
for i in range(2):
plt.scatter(dataset[km.labels_ == i, 0], dataset[km.labels_ == i, 1])
plt.show()
from sklearn.cluster import AgglomerativeClustering
ac = AgglomerativeClustering(n_clusters=2, affinity='euclidean', linkage='ward')
ac.fit(dataset)
plt.figure(figsize=(10, 8))
plt.title('Agglomerative Clustered Points')
for i in range(2):
plt.scatter(dataset[ac.labels_ == i, 0], dataset[ac.labels_ == i, 1])
plt.show()
import numpy as np
kmeans_accuracy = np.sum(km.labels_ == labels) / len(labels)
agglomerative_accuracy = np.sum(ac.labels_ == labels) / len(labels)
print(f'K-Means accuracy: {kmeans_accuracy}')
print(f'Agglomerative accuracy: {agglomerative_accuracy}')
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Kümelede diğer çok kullanılan yöntem grubundan biri de "yoğunluk tabanlı (density based)" kümele yöntemleridir. Yoğunluk temelli
kümeleme yöntemlerinde "yoğunluk (density)" en önemli unsurdur. Bir bölge yoğunsa onun bir küme belirtmesi olasıdır. Pekiyi yoğunluk
nasıl ölçülmektedir? Yoğunluk belli bir dairesel alan içerisinde kalan nokta sayısına göre ölçülmektedir. Yöntemde iki parametre başlangıçta tespt edilir.
Bu parametrelere "eps (epsilon)" ve "min_samples" denilmektedir. Eps dairesel bölgenin yarıçapını belirtmektedir. min_samples ise o dairesel
bölgenin yoğun kabul edilebilmesi için gerekli olan minimum nokta sayısıdır. Örneğin eps = 1, min_samples = 10 demek, "eğer 1 yarıçaplı
daire içerisinde en az 10 nokta varsa o daire alanı yoğun" demektir. Tabii daire iki boyutlu kartezyen koordinat sisteminde kullanılan
bir geometrik şekildir. Eğer boyut sayısı (yani sütun sayısı) üç olursa buradaki alan daire değil küre olacaktır. N boyutlu uzayn da bir küresi
vardır. Yöntem iki boyutlu kartezyen koordinat sistemi üzerinde açıklansa da aslında yapılan işlemler N boyutlu uzay için de
benzerdir.
İki boyutlu kartezyen koordinat sisteminde boyutlar x ve y olmak üzere merkezi (a, b) noktasında ve yarıçapı r olan daire denklemi şöyledir:
(x - a) ** 2 + (y - b) ** 2 = r ** 2
Üç boyutlu uzay için merkez koordinatı (a, b, c) olan ve boyutları x, y, ve z olan daire denklemi ise şöyledir:
(x - a) ** 2 + (y - b) ** 2 + (z - c) ** 2= r ** 2
N boyutlu uzayın küresi de benzer biçimde elde edilebilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yoğunluk tabanlı algoritmaların en çok kullanılanı DBSCAN (Density Based Spatial Clustering of Applications with Noise) isimli algoritmadır.
Algoritmanın anlaşılması için birkaç terimden faydalanılmaktadır. Bu terimler ve anlamları şöyledir:
Ana Noktalar (Core Points): Eğer bir nokta merkez kabul edildiğinde onun eps yarıçaplı küresinde en az min_pts kadar nokta varsa o nokta bir ana noktadır.
Bu durumda bir nokta belirlenen eps ve min_samples değerlerine göre ya ana noktadır ya da değildir.
Bir Ana Noktadan Doğrudan Erişilebilen Noktalar (Direct Reachable Points): Bir ana noktanın kğresi içerisinde kalan noktalar o ana noktanın
doğrudan erişilen noktalarıdır.
Ana Bir Noktanın Yoğunluk Yoluyla Erişilebilir Noktaları (Density Reachable Points): Bir noktanın doğrudan erişilebilen noktalarından biri
bir ana noktas ise o ana noktanın da doğrudan erişilebilen noktaları ilk ana noktanın yoğunluk yoluyla erişileben noktaları olur.
Yani yoğunluk yoluyla erişilebilen noktalar "dostumun dostu dostumdur" gibi geçili olarak devam etmektedir. Bu geçişlilik yoğunluk
yoluyla erişilebilen noktaların uzayabilmesi anlamına gelir.
Bir Ana Noktanın Sınır Noktaları(Border Points): Bir ana noktanın yoğunluk yoluyla erişilebilen ana nokta olmayan noktaları o ana noktanın sınır noktalarıdır.
Sınır noktalar ana nokta olmadığı için alanı genişletememektedir. Yani yoğun luk geçişli olarak o noktalardan öteye geçememektedir.
Gürültü Noktaları (Noise Points): Bir nokta hiçbir ana noktanın yoğunluk yoluyla erişilebilen noktası durumunda değilse o noktaya "gürültü noktası"
denilmektedir. Gürültü noktaları aslında yoğun bölgelerden kopuk olarak genellikle izole biçimde bulunan noktalardır.
Bu durumda algoritma şöyle işletilir:
1) Önce "Kalan Noktalar Kümesi", "Gürültü Noktaları Kümesi" biçiminde iki küme oluşturulur. İşin başında tüm noktalar "Kalan Noktalar Kümesine"
yerleştirilir. Gürültü Noktaları Kümesi Boştur.
2) Kalan Noktalar Kümesinden rastgele bir nokta alınır. Eğer o nokta bir ana nokta değilse o nokta Kalan Noktalar Kümesinden çıkartılıp
Gürültü Noktaları Kümesine yerleştirlir. Eğer alınan nokta bir ana nokta ise o noktanın yoğunluk yoluyla erişilebilen tüm noktaları
elde edilir. Bu noktalar Kalan Noktalar Kümesinden çıkartılır ve bir küme yaratlırak o kümeye dahil edilir. Tabii başta Gürültü Noktaları Kümesine
girmiş olan bir nokta sonra bir kümeye dahil edilebilmektedir.
3) Yeniden 2. Adıma dönülür. Algoritma Kalan Noktalar Kalan Noktalar Kümesinde nokta kalmayana kadar devam ettirilir. Bu işlemlerin snucunda
K tane küme ve bir de Gürültü Noktaları Kümesi elde edilmiş olur.
Algoritmadaki önemli noktalar şunlardır:
- Bu algoritmada yoğun bölgeler bir küme olarak elde edilmektedir. Kğmeler arasında boş bölgeler vardır. Yani tıpkı kıtalar gibi
bir durum söz konusudur. Örneğin kıtalarda insan yoğunluğu vardır. Ancak denizlerde yoktur. Böylece 5 farklı kıta küme olarak belirlenecektir.
- Bu algoritmada biz algoritmaya yalnızca eps (yarıçap) ve min_samples değerlerini veririz. Küme sayısını biz vermeyiz. Küme sayısı bu değerlerden hareketle
algoritma tarafından belirlenecektir.
- Bu algoritmada iki hyper parametre vardır: eps ve min_samples. Bu değerlerin farklı seçimleri farklı kümelerin oluşturulmasına yol açacaktır.
- Algortimada yine bir uzaklık hesaplama yöntemi (yani metrik) söz konsudur. Yine tipik olarak Öklit uzaklığı kullanılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
DBSCAN algoritması için sklearn.cluster modülündeki DBSCAN isimli sınıf bulundurulmuştur. Sınıfın __init__ metodunun parametrik
yapısı şöyledir:
class sklearn.cluster.DBSCAN(eps=0.5, *, min_samples=5, metric='euclidean', metric_params=None, algorithm='auto', leaf_size=30, p=None, n_jobs=None)
Metodun ilk parametresi yarıçap belirten eps parametresidir. İkinci parametre olan min_samples bir noktanın ana nokta olması için gereken
minimum nokta sayısını belirtmektedir. metric parametresi uzaklık ölçütü için kullanılacak yöntemi belirtmektedir. Diğer parametreler için dokümanlara bakabilirsiniz.
DBSCAN sınıfı türünden nesne yaratıldıktan sonra yine fit işlemi yapılır. fit işlemi sonrasında sınıfın örnek özniteliklerinden
oluşan bilgiler alınır. Sınıfın örnek öznitelikleri şunlardır:
labels_: Bu öznitelik hangi noktaların hangi kümeler içerisinde kümelendiğini belirtmektedir. Buradaki -1 değeri gürültü noktası anlamına gelir.
core_sample_indices_: Ana noktaların indeks numalaralarını içerir.
components_: Ana noktaların hepsinin bulunduğu NumPy dizisi
n_features_in_: Veri kümesindeki sütun sayısı
DBSCAN algoritmasında uygulamacının epsion ve min_samples değerlerini belirlemiş olması gerekmektedir. Eğer bu değerler geniş belirlenirse
küme sayısı azalır, dar belirlenirse kğme sayısı artar. Pekiyi uygulamacı bu değerleri nasıl belirlemelidir? Aslında bu konuda pratik şeyle söylemek
o kadar değildir. Ancak genel olarak min_samples 1 olmaması gerekir. 2 de uygun değildir. Buradaki değerin en az özellik sayısından bir fazla olması
en normal durumdur. Örneğin iki özellikli (yani kartezyen koordinat sisteminde gösterebileceğimiz) noktalar söz konusu olduğunda bu değerin en az üç
olması uygun olur. Epsilon değeri de noktalar arasındaki uzaklıklar dikkate belirlenebilir. Ya da deneme yanılma yoluna gidilebilir. O halde uygulamacı önce
min_samples parametresini bwlirleyip daha sonra epsilon parametresiyle oynayarak nihai ayarlamayı yapabilir.
Aşağıda örnekte daha önce kullanmış olduğumuz "points.csv" verileri üzerinde DBSCAN algoritmasını uyguluyoruz. Noktalar şunlardı:
7,8
2,4
6,4
3,2
6,5
5,7
3,3
1,4
5,4
7,7
7,6
2,1
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('points.csv', delimiter=',')
from sklearn.cluster import DBSCAN
dbs = DBSCAN(eps=2, min_samples=3)
dbs.fit(dataset)
nclusters = np.max(dbs.labels_) + 1
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(nclusters):
plt.scatter(dataset[dbs.labels_ == i, 0], dataset[dbs.labels_ == i, 1])
plt.scatter(dataset[dbs.labels_ == -1, 0], dataset[dbs.labels_ == -1, 1], marker='x', color='black')
legends = [f'Cluster-{i}' for i in range(1, nclusters + 1)]
legends.append('Noise Points')
plt.legend(legends)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de zambak veri kümesini DBSCAN algoritmasıyla kümelere ayıralım. Zambak veri kümesinde toplam 4 özellik vardır.
Bu durumda biz örneğin min_samples parametresini 5'te utarak epsilon değeriyle oynayabiliriz. Aşağıdaki örnekte min_samples = 5,
epsilon=0.42 değerleriyle kümeleme yapıldığında üç küme oluşturulmuştur. Ancak bazı noktalar kümelerin uzağında kaldığı için gürültü noktaları biçiminde
işaretlenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('iris.csv')
dataset = df.iloc[:, 1:-1].to_numpy()
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
scaled_dataset = mms.fit_transform(dataset)
import numpy as np
from sklearn.cluster import DBSCAN
dbs = DBSCAN(eps=0.42, min_samples=5)
dbs.fit(dataset)
nclusters = np.max(dbs.labels_) + 1
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
decomposed_dataset = pca.fit_transform(dataset)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(nclusters):
plt.scatter(decomposed_dataset[dbs.labels_ == i, 0], decomposed_dataset[dbs.labels_ == i, 1])
plt.scatter(decomposed_dataset[dbs.labels_ == -1, 0], decomposed_dataset[dbs.labels_ == -1, 1], marker='x', color='black')
legends = [f'Cluster-{i}' for i in range(1, nclusters + 1)]
legends.append('Noise Points')
plt.legend(legends, loc='lower right')
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Bir merkez etrafında yayılmayan veri kümelerinde (örneğin iç içe geçmiş daireler gibi) daha önce K-Means ve Agglomerative
hiyerarşik kümeleme yöntemlerinin iyi çalışmdaığını görmüştük. İşte bu tarzdaki veri kümelerinde yoğunluk temelli yöntemler
iç ve dış dairesel verileri iyi bir biçimde kümeleyebilmektedir.
Aşağıdaki örnekte iç içe iki dairesel veri kümesi oluşturulup DBSCAN yöntemiyle bunlar uygun epsilon ve min_samples değerleri
ile kümelendirilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
from sklearn.datasets import make_circles
dataset, labels = make_circles(100, factor=0.5, noise=0.02)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Random Points')
for i in range(2):
plt.scatter(dataset[labels == i, 0], dataset[labels == i, 1])
plt.show()
import numpy as np
from sklearn.cluster import DBSCAN
dbs = DBSCAN(eps=0.3, min_samples=3)
dbs.fit(dataset)
nclusters = np.max(dbs.labels_) + 1
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(nclusters):
plt.scatter(dataset[dbs.labels_ == i, 0], dataset[dbs.labels_ == i, 1])
plt.scatter(dataset[dbs.labels_ == -1, 0], dataset[dbs.labels_ == -1, 1], marker='x', color='black')
legends = [f'Cluster-{i}' for i in range(1, nclusters + 1)]
legends.append('Noise Points')
plt.legend(legends)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Diğer bir yoğunluk tabanlı kümeleme algoritması da OPTICS denilen algoritmadır. Aslında OPTICS algoritması DBSCAN algoritmasının
bir uzantısı gibidir. OPTICS algortimasında bir yoğunluk grafiği elde edilir. Bu yoğunluk grafiğinden hareketle kümeleme yapılır.
Dolayısıyla algoritma yalnızca kümeleme işlemi sırasında değişik amaçlarla da kullanılabilmektedir. OPTICS algoritmasında uygulamacı yalnızca
min_samples değerini belirler. Epsilon değerini belirlemez. Algoritma değişik epsilon değerleri çin bir yoğun grafiği oluşturmaktadır.
Algoritmada iki önemli kavram vardır:
Ana Uzaklık (Core Distance): Bir noktanın ana nokta olması için (yani dairel alanında en az min_samples kadar nokta olması için) gerekli olan
minimum epsilon değerini belirtir. Bir noktayı merkeze alıp artan yarıçaplı daireler çizersek bu dairenin içerisinde en az min_samples
kadar noktanın kaldığı minimum uzaklığı gözle görebiliriz. Tabii her noktanın bu ana uzaklık değeri farklı olacaktır.
Erişilebilir Uzaklık (Reachability Distance): Erişilebilir uzaklık p ve q noktaları arasındaki uzaklıktır. Burada p noktası bir ana uzaklığa sahiptir.
İşte erişilebilir uzaklık p'nin ana uzaklı ile p ile q arasındaki uzaklığın hangisi fazlaysa o uzaklıktır. Yani max(ana_uzaklık, pr_arasındak,i_uzaklık)
biçimindedir.
Algoritma her noktanın her noktaya erişim uzaklığını hesaplayıp bir yoğunluk grafiği oluşturmaktadır. Örneğin epsilon=3 ile epislon=5 arasında
çok nokta olabildiği halde epsilon=5 ile epsilon=8 arasında az nokta olabilmektedir. Sonra algoritma bu yoğunluk grafiğinden hareketle
yoğunlukların değiştiği noktalardan kümeleme yapmaktadır.
OPTICS algoritması scikit-learn kütüphanesindeki sklearn.cluster modülünde bulunan OPTICS isimli sınıflar geçekleştirilmiştir.
Sınıfın __init__ metodunun parametrik yapısı şöyledir:
class sklearn.cluster.OPTICS(*, min_samples=5, max_eps=inf, metric='minkowski', p=2, metric_params=None,
cluster_method='xi', eps=None, xi=0.05, predecessor_correction=True, min_cluster_size=None,
algorithm='auto', leaf_size=30, memory=None, n_jobs=None)
Metotta bir epsilon parametre yoktur. Yalnızca min_samples isimli bir parametre vardır. Ancak max_eps biçiminde bir parametre bulunmaktadır.
Bu parametre ana uzaklık için maksimum sınır oluşturmaktadır. Eğer bir nokta bir ana noktadan burada belirtilen değerden daha uzaksa onun için
erişim uzaklığı hesaplanmaz. Dolayısıyla bu değer yanı zamanda bir noktanın ana nokta olması için gereken maksimum uzakl
Aşağıdaki örnekte daha önce üzerinde çalışmış olduğumuz "points.csv" verileri OPTICS algoritmasıyla kümelendirilmiştir. Burada biz yalnızca min_samples
değerini vermekteyiz. Algoritma bu örnekte iki küme oluşturmuştur. Hiçbir noktayı gürültü noktası olarak işaretlememiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('points.csv', delimiter=',')
from sklearn.cluster import OPTICS
optics = OPTICS(min_samples=3)
optics.fit(dataset)
nclusters = np.max(optics.labels_) + 1
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(nclusters):
plt.scatter(dataset[optics.labels_ == i, 0], dataset[optics.labels_ == i, 1])
plt.scatter(dataset[optics.labels_ == -1, 0], dataset[optics.labels_ == -1, 1], marker='x', color='black')
legends = [f'Cluster-{i}' for i in range(1, nclusters + 1)]
legends.append('Noise Points')
plt.legend(legends)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte min_samples=5 için zambak veri kümesi OPTICS algoritmasına göre kümelendirilmiştir. Burada 6 kğme oluşturulmuştur.
Bu 6 kümenin dışında bazı noktalar da gürültü noktası olarak işaretlenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('iris.csv')
dataset = df.iloc[:, 1:-1].to_numpy()
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
scaled_dataset = mms.fit_transform(dataset)
import numpy as np
from sklearn.cluster import OPTICS
optics = OPTICS(min_samples=5)
optics.fit(dataset)
nclusters = np.max(optics.labels_) + 1
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
decomposed_dataset = pca.fit_transform(dataset)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(nclusters):
plt.scatter(decomposed_dataset[optics.labels_ == i, 0], decomposed_dataset[optics.labels_ == i, 1])
plt.scatter(decomposed_dataset[optics.labels_ == -1, 0], decomposed_dataset[optics.labels_ == -1, 1], marker='x', color='black')
legends = [f'Cluster-{i}' for i in range(1, nclusters + 1)]
legends.append('Noise Points')
plt.legend(legends, loc='lower right')
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda iç içe dairesel veri kümesine OPTICS algoritması uygulanmıştır. Burada min_samples parametresi uygun bir biçimde ayaralanarak
iç ve dış daire birbirinden ayrılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
from sklearn.datasets import make_circles
dataset, labels = make_circles(1000, factor=0.5, noise=0.02)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Random Points')
for i in range(2):
plt.scatter(dataset[labels == i, 0], dataset[labels == i, 1])
plt.show()
import numpy as np
from sklearn.cluster import OPTICS
optics = OPTICS(min_samples=40)
optics.fit(dataset)
nclusters = np.max(optics.labels_) + 1
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(nclusters):
plt.scatter(dataset[optics.labels_ == i, 0], dataset[optics.labels_ == i, 1])
plt.scatter(dataset[optics.labels_ == -1, 0], dataset[optics.labels_ == -1, 1], marker='x', color='black')
legends = [f'Cluster-{i}' for i in range(1, nclusters + 1)]
legends.append('Noise Points')
plt.legend(legends)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Yoğunluk tabanlı DBSCAN algoritmasıyla OPTICS algoritmasını şöyle karşılaştırabiliriz:
- OPTICS algoritması daha fazla bellek kullanmaktadır. Çünkü algoritmanın işleyişinde bir "öncelik kuyruğundan (priority queue)
faydalanılmaktadır.
- OPTICS algoritması DBSCAN algoritmasına göre daha tavaş çalışma eğilimindedir. Çünkü farklı epsilon değerleri için yoğunluk bilgisi
oluşturulmaktadır.
- OPTICS algoritmasında biz yalnızca min_samples parametresini belirleriz. Halbuki DBSCAN algoritmasında biz epsilon değerini de belirlemek zorundayız.
Bu nedenle OPTICS algortiması daha kolay kullanılabilmektedir.
- DBSCAN algortiması daha esnektir. DBSACN'de epsilon değeri uygulamacı tarafından istenildiği gibi alınıp kümeleme üzerinde daha fazla kontrol
sağlanabilmektedir.
- Hem DBSCAN hem de OPTICS algoritmaları dairesel verilerde K-Means ve Agglomerative hiyerarşik yönteme göre daha iyi sonuç vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Biz giriş derslerinde "varyans" kavramını görmüştük. Varyans standart sapmanın karesine denilmektedir. İstatistikte bazı konularda
varyans terimi çok kullanılırken bazı konularda standart sapma terimi çok kullanılmaktadır. Dolayısıyla bu iki kavram birbirleriyle ilişkili
olduğu halde bunlar için iki farklı terim uydurulmuştur. Varyans işlemi NumPy ktüphanesindeki axis temelinde yapılabilmektedir.
Standart sapma ve varyans değerlerin ortalama etrafındaki kümelenmesi konusunda bir fikir verebilmektedir. Biribirine yakın değerlerin
standart sapması ve varyansı düşüktür. Birbirindne uzak değerlerin standart sapması ve varyansı yüksektir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Kovaryans (covariance) iki olgunun birlikte değişimi ya da doğrusallığı konusunda bilgi veren istatistiksel bir ölçüttür.
Örneğin bu olgular x ve yolsun. Eğer x artarken tutarlı biçimde y de artıyorsa aralarında doğrusal bir ilişkiye benzer bir ilişki vardır. Bu durumda iki
değişkenin kovaryasnları yüksektir. Tabii ilişki doğrusal gibi olduğu halde ters yönde de olabilir. Yani örneğin x artarken y de tutarlı bir biçimde
azalıyor olabilir. Burada da kovaryans ters yönde yüksektir. Ancak bir değişken artarken diğeri tutarlı bir biçimde artıp azalmıyorsa
bu iki değişken arasında düşük bir kovaryans vardır.
Kovaryans için değişken arasında şöyle hesaplanmaktadır:
import numpy as np
def cov(x, y):
return np.sum((x - np.mean(x)) * (y - np.mean(y))) / len(x)
NumPy kütüphanesindeki cov fonksiyonu iki boyutlu NumPy dizileriyle ya da tek boyutlu Numpy dizileriyle çalışabilmektedir.
cov fonksiyonu bize bir kovaryans matrisi verir. Yani her değişkenin her değişkenle kovaryasları matris halinde verilmektedir.
Tabii bu matris simetrik bir matrisir. Default durumda cov fonksiyonu n - 1'değerine bölme yapmaktadır. ddof=0 parametresiyle n değerine bölme
yaptırabiliriz. x ile x'in kovaryasnı ile varyasnı aynı anlamdadır. Kovaryans matrisinde kçşegenler değişkenlerin varyanslarını belirtir.
Çünkü cov(x, x) zaten var(x) anlamındadır.
Aşağıdaki örnekte tek boyutlu iki dizinin kovaryansları manuel olarak ve numpy.cov fonksiyonuyal hesaplanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
def cov(x, y):
return np.sum((x - np.mean(x)) * (y - np.mean(y))) / len(x)
x = np.array([1, 2, 3, 4, 5])
y = np.array([3, 6, 8, 10, 12])
result = cov(x, y)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte iki değişkenin arasındaki ilişkiyi değiştirerek doğrusallık temelinde kovaryans değerlerini incelyiniz.
Değişkenlerin arasındaki ilişki doğrusallığa benzedikçe kovaryans yükselmektedir. Tabii ters yönde doğrusal ilişki de yüksek
negatif bir kovaryans oluşturmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
def cov(x, y):
return np.sum((x - np.mean(x)) * (y - np.mean(y))) / len(x)
x = np.array([1, 2, 3, 4, 5])
y = np.array([3, 5, 8, 9, 12])
result = cov(x, y)
print(result)
result = np.cov(x, y, ddof=0)
print(result)
import matplotlib.pyplot as plt
plt.title('Covariance')
plt.plot(x, y, marker='o')
plt.show
#----------------------------------------------------------------------------------------------------------------------------
n tane değişkenin birbirlerine göre kovaryanslarını hesaplarken cov fonksiyonuna iki boyutlu tek bir dizi girilir. (Tabii
aslında bu parametre bir liste listesi, bir NumPy dizi dizisi ya da Numpy dizilerinden oluşan bir liste olarak da girilebilir).
Default durumda cov fonksiyonu satır temelinde çalışmaktadır. Yani her satırı ayrı bir değişken gibi kabul etmektedir. Örneğin üç ayrı
değişkenimiz olsun:
a = np.array([1, 2, 3, 4, 5])
b = np.array([2, 6, 1, 7, 4])
c = np.array([30, 23, 45, 16, 12])
Biz bu üç değişkenin birbirlerine göre kovaryanslarını şöyle hesaplayabiliriz:
>>> a = np.array([1, 2, 3, 4, 5])
>>> b = np.array([2, 6, 1, 7, 4])
>>> c = np.array([30, 23, 45, 16, 12])
>>> np.cov([a, b, c], ddof=0)
array([[ 2. , 1. , -8.6 ],
[ 1. , 5.2 , -20.2 ],
[ -8.6 , -20.2 , 135.76]])
Aynı şey şöyle de yapılabilirdi:
>>> a = np.array([[1, 2, 3, 4, 5], [2, 6, 1, 7, 4], [30, 23, 45, 16, 12]])
>>> np.cov(a, ddof=0)
array([[ 2. , 1. , -8.6 ],
[ 1. , 5.2 , -20.2 ],
[ -8.6 , -20.2 , 135.76]])
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Kovaryans iki değişkenin birlikte değişimi hakkında bize bilgi vermektedir. Ancak kovaryans değerlerini karşılaştırmak
zordur. Yani başka bir deyişle x ile y arasındaki kovaryansı m ile z arasındaki kovaryansla karşılaştıramayız. İşte kovaryansların
standardize edilmiş haline "korelasyon katsayısı" denilmektedir. Korelasyon katsayıları için değişik hesaplama yöntemleri önerilmiştir.
Ancak en çok kullanılan korelasyon katsayı hesaplama yöntemi "Pearson Korelasyon Katsayısı" denilen yöntemdir. Bu yöntemde iki
değişkenin kovaryansları onların standart sapmalarının çarpımına bölünmektedir. Yani değişkenler x ve y olmak üzere
Pearson korelasyon katsayısı cov(x, y) / (std(x) * std(y)) biçiminde hesaplanmaktadır. Bu işlem kovaryans değerini [-1, 1]
aralığına hapsetmektedir. Dolayısıyla karşılaştırmalar bu sayede yapılabilmektedir. Değişkenler ne kadar doğrusal ilişki içerisindeyse
korelasyo katsayısı +1 ya da -1'e yaklaşır. Bir değişken artarken diğeri de artıyorsa pozitif bir korelasyon söz konusudur. Bir değişken artarken
diğeri azalıyorsa negatif bir korelasyon söz konusu olabilir. Pozitif de olsa negatif de olsa korelasyon katsayısı yükseldikçe
ilişki doğrusal olmaya yaklaşmaktadır. Eğer iki değişken arasındaki ilişki tutarsız ve doğrusal olmaktan uzak ise bu durumda
korelasyon katsayısı 0'a yaklaşmaktadır.
Özellikle sosyal bilimlerde ve sağlık bilimlerinde araştırma yapanlar ölçükleri değişkenlerin arasında korelasyonlara
bakmaktadır. Bu sayede yüksek korelasyonlu değişkenler arasında bir ilişkinin olabileceği göz önüne alınmaktadır.
İki değişken arasında korelasyon için tipik olarak şunlar sşöylenebilmektedir:
0-0.2 ise çok zayıf korelasyon ya da korelasyon yok
0.2-0.4 arasında ise zayıf korelasyon
0.4-0.6 arasında ise orta şiddette korelasyon
0.6-0.8 arasında ise yüksek korelasyon
0.81 > ise çok yüksek korelasyon
İki olgu arasında yüksek bir koerlasyon olamsı bunlar arasında bir neden-sonuç ilişkisinin olacağı anlamına gelmemektedir.
İki olgu arasında dolaylı bir ilişki olabilir ancak bu neden-sonuç ilişkisi olmayabilir. Örneğin dondurma satışlarıyla boğulma
vakaları arasında yüksek bir korelasyon olabilir. Ancak biz buaradan dondurma yemenin boğulmaya yol açtığı gibi bir sonuç
çıkartamayız.
Pearson korelasyon katsayısı NumPy'da corrcoef isimli fonksiyonla hesaplanabilmektedir. corrcoef fonksiyonu tamamen cov fonksiyonu
gibi kullanılmaktadır. Yine bu fonksiyon da bir korelasyon matrisi vermektedir. Örneğin:
>>> a = np.array([1, 2, 3, 4, 5])
>>> b = np.array([2, 6, 1, 7, 4])
>>> c = np.array([30, 23, 45, 16, 12])
>>> np.corrcoef([a, b, c])
array([[ 1. , 0.31008684, -0.52191231],
[ 0.31008684, 1. , -0.76026287],
[-0.52191231, -0.76026287, 1. ]])
Buradaki matrisin yine simetrik olduğuna dikkat ediniz. Köşegen elemanlarının 1 olmasının nedeni bir şeyin kendisiyle korelasyonunun 1 olmasından
kaynaklanmaktadır. Yani corr(x, x) her zaman 1'dir. corrcoef fonksiyonunda ddof parametresi gereksizdir ve kaldırılmıştır. Çünkü aslında
bölme işlemi dikkatle incelenirse n-1 ya da n'e bölümün aslında kesirde aynı değeri vereceği anlaşılır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Veri biliminde ve makine öğrenmesinde veri kümesinde çok fazla sütun (yani özellik) bulunmasının bazı olumsuzlukları vardır.
Çok fazla sütun çok fazla işlem anlamına gelir. Dolayısıyla hesaplama zamanları göreli olarak artar. Çok fazla sütun aynı zamanda
bellek kullanımı üzerinde de olumsuz etkiye yol açmaktadır. Fazlaca sütun bilgisinin işlenmesi için daha fazla alana gereksinim duyulmaktadır.
Ancak veri kümesinde çok fazla sütunun bulumasının en önemli dezavantajı "modeli karmaşık hale getirmesi ve overfitting" eğilimini
artırmasıdır. Denetimli öğrenmede karmaşık modeller öğrenmein düşmesine ve yanşlış öğrenmeler yol açabilmektedir.
O halde çok fazla sütunun daha az sütuna indirgenmesi önemli önişlem faaliyetlerinden biridir. Buna "boyutsal özellik indirgemesi (dimentionality
feature reduction)" denilmektedir. Boyutsal özellik indirgemesi çeşitli Auto ML araçları tarafından otomatik yapılabilmektedir.
Tabii bunun için verilerin iti bir biçimde analiz edilmesi gerekir.
Boyutsal özellik indirgemesi n tane sütundan k < n koşulunu sağlayan k tane sütunun elde edilmesi sürecidir. Bu süreç iki alt gruba
ayrılmaktadır:
1) n tane sütundan bazılarını atarak ancak diğerlerini değiştirmeden k tane sütun elde etmeye çalışan yöntemler
2) n tane sütundan onu temsil eden (ancak bu n tane sütunun hiçbirini içermeyen) k tane sütun elde etmeye çalışan yöntemler.
Biz de burada belli başlı yöntemler üzerinde duracağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Eksik Değerli Sütunların Atılması Yöntemi (Missing Value Ratio): Bu yöntemde eğer bir sütunda eksik veriler varsa o sütun
veri kümesinden çıkartlır. Tabii burada sütundaki eksik verilerin oranı da önemlidir. Genellikle belli oranı aşan sütunların
atılması yoluna gidilmektedir. Örneğin sütunlarda %20'nin yukarısında eksik veri varsa bu sütunları atabiliriz. Çünkü zaten
bu sütunların temsil yeteneği azalmıştır.
Düşük Varyans Filtrelemesi (Low Variance Filtering): Bir sütunun varyansı o sütundaki değişkenliği bize anlatmaktadır.
Örneğin veri kümesinde hep aynı değerlerden oluşan bir sütun bulunuyor olsun. Bu sütun bize bir bilgi verebilir mi?
Tabii ki hayır. Bu sütunun varyansı 0'dır. O halde biz n tane sütundan bazılarını atarak k tane sütun elde etmek istediğimizde seçeneklerden
biri de az bilgiye sahip olan sütunları atmaktır. O da değişkenliği az olan yani düşük varyansa sahip sütunlardır. O halde bu yöntemde
sütunların varyanslarına bakılır. k + m = n ise en düşük varyansa sahip m tane sütun atılarak k tane sütun elde edilebilir. Diğer bir yöntem de
m tane sütunu atmak yerine belli bir eşik değeri belirlenip o eşik değerinin aşağısında kalan sütunları atmak olabilir. Ancak
sütunlardaki skala farklılıkları varyansların karşılaştırılmasını engellemektedir. O halde bu yöntem uygulanmadan önce sütunların
aynı skalaya dönüştürülmesi uygun olur. Bunun için Min-Max ölçeklemesi kullanılabilir.
Aşağıdaki örnekte "Boston Housing Price (housing.csv)" veri kümesindeki 13 sütundan en düşük varyansa sahip olan 5 tanesi atılarak
bu veri kümesi 8 sütuna indirgenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(dataset_x)
scaled_dataset_x = mms.transform(dataset_x)
import numpy as np
feature_vars = np.var(scaled_dataset_x, axis=0)
sorted_arg_vars = np.argsort(feature_vars)
reduced_dataset_x = np.delete(dataset_x, sorted_arg_vars[:5], axis=1)
print(reduced_dataset_x)
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte ise aynı yöntem kullanılarak 4 sütuna sahip zambak verileri en düşük varyansa ilişkin sütun atılarak üç
sütuna indirgenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('iris.csv')
dataset_x = df.iloc[:, 1:-1].to_numpy(dtype='float32')
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(dataset_x)
scaled_dataset_x = mms.transform(dataset_x)
import numpy as np
feature_vars = np.var(scaled_dataset_x, axis=0)
sorted_arg_vars = np.argsort(feature_vars)
reduced_dataset_x = np.delete(dataset_x, sorted_arg_vars[:1], axis=1)
print(reduced_dataset_x)
#----------------------------------------------------------------------------------------------------------------------------
scikit-learn içerisinde sklearn.feature_selection modülünde VarianceThreshold isimli bir sınıf bulunmaktadır. Bu sınıf belli bir
eşik değerinden küçük olan sütunların atılmasında kullanılmaktadır. Sınıfın kullanımı diğer scikit-learn sınıflarındaki gibidir.
Nesne yaratılırken eşik değeri verilmektedir. Sonra fit_transform işlemiyle indirgeme yapılabilmektedir. fit işleminden sonra
sınıfın variances_ örnek özniteliğinde sütun varyansları bulunur.
Aşağıdaki örnekte Boston Housing Price veri kümesi üzerinde VarianceThreshold sınıfı kullanılarak belli bir eşik değeri
ile düşük varyans filtrelemesi yapılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(dataset_x)
scaled_dataset_x = mms.transform(dataset_x)
from sklearn.feature_selection import VarianceThreshold
vt = VarianceThreshold(0.04)
reduced_dataset_x = vt.fit_transform(scaled_dataset_x)
print(reduced_dataset_x.shape)
#----------------------------------------------------------------------------------------------------------------------------
Yüksek Korelasyon Filtrelemesi Yöntemi (High Correlation Filtering): İki sütun söz konusu olsun. Biri diğerinin iki katı değerlere
sahip olsun. Bu iki sütunun bir arada bulunmasının hiçbir yöntemde hiçbir faydası yoktur. Bu iki sütunun Pearson korelasyon katsayısı 1'dir.
İşte birden fazla sütun birbirleriyle yüksek derecede korelasyon içeriyorsa bu sütunların yalnızca bir tanesi muhafaza edilip diğerleri
atılabilir.
Yüksek korelasyon filtrelemesi manuel bir biçimde yapılabilir. Anımsanacağı gibi korelasyon iki değişken arasında hesaplanmaktadır.
Dolayısıyla bir korelasyon matrisi elde edilmektedir. Burada programcı matirisn en büyük elemanlarını bulmaya çalışabilir. Onun satır ve sütun değerleri
yüksek korelasyonu olan sütunları verecektir. Korelasyon için özellik ölçeklemesi yapmaya gerek yoktur. Çünkü zaten Pearson korelasyon katsayısı bize
standardize edilmiş bir değer vermektedir. Tabii yüksek korelasyon filtrelemesi yapılırken yüksek korelasyonun negatif ya da pozitif olmasının bir önemi yoktur.
Pozitif yüksek korelasyon da negatif yüksek korelasyon da neticede aynı durumlara yol açmaktadır.
Yüksek korelasyon filtrelemesini yapmak biraz daha zahmetlidir. Çünkü korelasyon matrisi büyük olabilir. Bizim de bu büyük matrisi
incelememiz gerekebilir. Ayrıca yüksek korelasyona sahip olan sütunlardan hangilerinin atılacağı da bazen önemli olabilmektedir. Örneğin
bizim iki sütunuzmuzun korelasyonları 0.95 olsun. Bunlardan birini atmak isteriz. Ama hangisini atmak daha uygun olur? İşte burada uygulamacı
başka ölçütleri de göz önüne alabilir. Örneğin düşük varyansa sahip olanı atmak isteyebilir. Nispeten daha önemsiz kabul ettiği bir sütunu
da atmak isteyebilir.
Yüksek korelasyonlu sütunların görsel bir biçimde tespit edilebilmesi için "heatmap" denilen grafiklerden de faydalanılabilmektedir.
Heapmap grafikleri matplotlib içerisinde yoktur Ancak seaborn kütüphanesinde bulunmaktadır. Bu heatmap grafiğinde (grafik çeşitli biçimlerde
konfigüre edilebilmektedir) yüksek değerler açık renklerle düşük değerler koyu renklerle gösterilirler. Böylece uygulamacı gözle bunları kontrol edebilir.
Aşağıdaki örnekte Boston veir kümesinde korelasyon uygulanıp korelasyon matrisi heatmap fonksiyona verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
import numpy as np
feature_corrs = np.abs(np.corrcoef(dataset_x, rowvar=False))
import seaborn as sns
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 9))
sns.heatmap(data=feature_corrs, annot=True)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Korelasyon matrisinde yüksek olan satır sütun numaralarını programalama yoluyle elde etmek istebiliriz. Burada dikkat edilmesi gereken
nokta korelasyon matrisinin simetrik olması ve köşegen elemanlarının 1 olmasıdır. Aşağıdaki örnekte korelasyon matrisi dolaşılmış
yüksek korelasyonlu sütunların bir tanesi atılmak üzere işaretlenmiştir. Bu örnekte biz
#----------------------------------------------------------------------------------------------------------------------------
CORR_THRESHOLD = 0.75
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
import numpy as np
feature_corrs = np.abs(np.corrcoef(dataset_x, rowvar=False))
eliminated_features = []
for i in range(feature_corrs.shape[0]):
for k in range(feature_corrs.shape[1]):
if i != k and i not in eliminated_features and feature_corrs[i, k] > CORR_THRESHOLD:
eliminated_features.append(i)
print(eliminated_features)
reduced_dataset_x = np.delete(dataset_x, eliminated_features, axis=1)
print(reduced_dataset_x.shape)
#----------------------------------------------------------------------------------------------------------------------------
En çok kullanılan boyutsal özellikj indirgemesi yöntemi "temel bileşenler analizi (principle component analysis)" denilen
yöntemdir. Bu yötemde orijinal n tane sütuna sahip olan veri kümesi k < n olmak üzere k tane sütuna indirgenmektedir. Ancak
bu yöntemde elde edilen k sütunlu veri kümesinin sütunları orijinal n sütun ile aynı olmamaktadır. Yani bu yöntem n tane sütunlu veri
kümesini temsil eden tamamen farklı k tane sütun oluşturmaktadır. Yöntemin matematiksel temeli biraz karmaşıktır. Bu yöntemde
n boyutlu uzaydaki noktalar dönüştürülerek en yüksek varyans sağlanacak biçimde k < n boyutlu uzaydaki noktalara dönüştürülmektedir.
Şüphesiz n tane özelliğe sahip olan veri kümesini k tane özelliğe indirgediğimiz zaman orijinal veri kümesinin temsili zayıflamış olur.
Ancak bu yöntem bu veri kümesindeki zayıflamayı en aza indirmeye çalışmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Temel bileşenler analizi sklearn.decomposition modülü içerisindeki PCA isimli sınıfla temsil edilmiştir. Sınıfın kullanımı diğer
scikit-learn sınıflarında olduğu gibidir. Yani PCA sınıfı türünden bir nesne yaratılır. Sonra sınıfın fit ve transform (ya da fit_transform)
metotları çağrılır. PCA sınıfın __init__ metodunun parametik yapısı şöyledir:
class sklearn.decomposition.PCA(n_components=None, *, copy=True, whiten=False,
svd_solver='auto', tol=0.0, iterated_power='auto', n_oversamples=10, power_iteration_normalizer='auto', random_state=None)
Burada zorunlu olan ilk parametre indirgenme sonucunda elde edilecek sütun sayısını velirtmektedir. PCA nesnesi yaratıldıktan sonra
önce fit işlemi yapılır. Bu işlem sırasında indirgeme için bilgiler oluşturulmuş olur. Ondan sonra gerçek indirgeme transform metoduyla
yapılmaktadır. Tabii fit ve transform işlemleri bir arada da fit_transform metoduyla yapılabilmektedir.
Eğer veri kümesinin sütunları arasında skala farklılıkları varsa PCA işleminden önce özellik ölçeklemesi uygulamak gerekir.
PCA işlemi için en uygun özellik ölçeklemesi yöntemi "standart ölçekleme" yani StandardScaler sınıfı ile gerçekleştirilen ölçeklemedir.
Ancak MinMAx ölçeklemesi de kullanılabilir.
Aşağıdaki örnekte "Boston Housing Price" veri kümesi üzerinde PCA işlemi uygulanmıştır. Bu veri kümesi 13 sütun oluşmaktadır. Biz aşağıdaki örnekte
bu 13 sütunu 10'a indirdik.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(dataset_x)
scaled_dataset_x = ss.transform(dataset_x)
from sklearn.decomposition import PCA
pca = PCA(10)
pca.fit(scaled_dataset_x)
reduced_dataset_x = pca.transform(scaled_dataset_x)
print(dataset_x.shape)
print(reduced_dataset_x.shape)
#----------------------------------------------------------------------------------------------------------------------------
Şüphesiz PCA işleminde bir biçimde "pedict" yapılırken predict için kullanılacak veri kümesini eğitim sırasındaki veri kümesi biçimine
getirmek gerekir. Yukarıdaki örnekte biz önce StandardScaler ve sonra da PCA işlemlerini uyguladık. Muhtemelen bunun sonucunda elde
edilen veri kümesini kullanacağız. O halde "predict" işleminde predict edilecek verileri de önce StnadardScaler ve PCA işlemlerine sokmamız gerekir.
Tabii predict veri kümsi eitimde kullanılan StandardScler ve PCA bilgilertiyle transform edilmelidir.
Aşağıdaki örnekte 13 sütundan oluşan "Boston Housing Price" veri kümesi önce StandardScaler sınıfı ile normalize edilmiştir.
Daha sonra normalize edilmiş veri kğmesi PCA işlemine sokularak 10 sütuna indirgenmiştir. Daha sonra da yapay sinir ağı yoluyla
eğitim, test ve kestirim işlemleri yapılmıştır. Burada 13 sütunun 10 sütuna indirilmesinin önemli bir avantajı muhtemelen olmayacaktır.
Bu örnek yalnızca PCA sınıfının bu tür durumlarda nasıl kullanılacağınııklamak verilmiştir.
Veri kümesinin birtakım peşi sıra işlemlere sokulması (örneğin önce normalizasyon sonra PCA gibi) bazen sıkıcı olabimektedir. Bu tür
peşi sıra yapılacak işlemleri daha basit ele almak için sckit-learn kütüphanesinde Pipeline isimli bir sınıf da bulundurulmuştur.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(training_dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
scaled_test_dataset_x = ss.transform(test_dataset_x)
from sklearn.decomposition import PCA
pca = PCA(10)
pca.fit(scaled_training_dataset_x)
reduced_training_dataset_x = pca.transform(scaled_training_dataset_x)
reduced_test_dataset_x = pca.transform(scaled_test_dataset_x)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Boston-Housing-Price')
model.add(Dense(64, activation='relu', input_dim=reduced_training_dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
hist = model.fit(reduced_training_dataset_x, training_dataset_y, batch_size=32, epochs=200, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Mean Absolute Error Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['mae'])
plt.plot(hist.epoch, hist.history['val_mae'])
plt.legend(['Mean Absolute Error', 'Validation Mean Absolute Error'])
plt.show()
eval_result = model.evaluate(reduced_test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
predict_data = np.array([[0.11747, 12.50, 7.870, 0, 0.5240, 6.0090, 82.90, 6.2267, 5, 311.0, 15.20, 396.90, 13.27]])
scaled_predict_data = ss.transform(predict_data)
reduced_predict_data = pca.transform(scaled_predict_data)
predict_result = model.predict(reduced_predict_data)
for val in predict_result[:, 0]:
print(val)
model.save('boston.h5')
import pickle
with open('boston.pickle', 'wb') as f:
pickle.dump(ss, f)
pickle.dump(pca, f)
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi biz n tane sütuna sahip olan bir veri kümesini kaç sütuna indirgemeliyiz? Bu indirgenecek sütun sayısını nasıl belirleyebiliriz?
Öncelikle PCA işleminin matematiksel temelinde "açıklanan varyans (explained variance)" denilen bir kavram vardır. PCA sonucunda elde
edilen her sütun için "açıklanan varyans" ve "açıklanan varyans oranı (yüzdesi)" elde edilmektedir. Bu açıklanan varyans oranı (yüzdesi) ilgili sütunun
asıl veri kümesini tek başına temsil yeteneği ile ilgilidir. Örneğin bir sütunun açıklanan varyans oranı 0.4 ise bu sütun tek başına
tüm veri kümesindeki bilgilerin yüzde 40'ını temsil etmektedir. O halde programcı tüm sütunların açıklanan varyans oranlarını toplayarak
indirgenmiş olan veri kümesinin asıl veri kümesinin yüzde kaçını temsil edebildiğini görebilir. Örneğin Boston veri kümesi için bu işlemi tek tek yapalım.
Programın çalıştırışması sonucunda şöyle bir çıktı elde edilmiştir:
1 ---> 0.471296101808548
2 ---> 0.5815482139587402
3 ---> 0.6771341562271118
4 ---> 0.7431014180183411
5 ---> 0.8073177337646484
6 ---> 0.8578882217407227
7 ---> 0.89906907081604
8 ---> 0.929538369178772
9 ---> 0.9508417248725891
10 ---> 0.9677831530570984
11 ---> 0.9820914268493652
12 ---> 0.9951147437095642
13 ---> 1.0000001192092896
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(dataset_x)
scaled_dataset_x = ss.transform(dataset_x)
import numpy as np
from sklearn.decomposition import PCA
for i in range(1, dataset_x.shape[1] + 1):
pca = PCA(i)
pca.fit(scaled_dataset_x)
total_ratio = np.sum(pca.explained_variance_ratio_)
print(f'{i} ---> {total_ratio}')
#----------------------------------------------------------------------------------------------------------------------------
O halde PCA işleminde indirgenecek sütun sayısı nasıl belirlenmelidir? Bunun temelde iki yöntem sık kullanılmaktadır. Birinci
yöntemde uygulamacı belli bir temsil oranını belirler. Toplam açıklanan varyans yüzdesinin o oranı kartşıladığı sütun sayısını elde eder.
İkinci yöntemde uygulamacı toplam açıklanan varyans yüzdelerinin grafiğini çizerek grafikten hareketle kararını verir.
Aşağıda birinci yöntemin nasıl uygulanması gerektiğine ilişkin bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
TARGET_RATIO = 0.7
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(dataset_x)
scaled_dataset_x = ss.transform(dataset_x)
import numpy as np
from sklearn.decomposition import PCA
for i in range(1, dataset_x.shape[1] + 1):
pca = PCA(i)
pca.fit(scaled_dataset_x)
total_ratio = np.sum(pca.explained_variance_ratio_)
if total_ratio >= TARGET_RATIO:
break
print(f'Number of features: {i}')
#----------------------------------------------------------------------------------------------------------------------------
İkinci yöntemde uygulamacı toplam açıklanan varyansın ya da bunun oranının grafiğini çizer. Bu grafikte yatay eksende özellik sayıları
düşey eksende açıklanan varyans oranları bulunur. Bu grafik önce sert bir biçimde yükselmekte ve sonra yavaş yavaş yatay bir seyire
doğru hareket etmektedir. İşte eğrinin yatay seyire geçtiği bokta gözle tesipt edilir.
Aşağıdaki örnekte "Boston Housing Price" için böyle bir grafik çizilmiştir. Bu veri kğmesinde çok az sayıda sütun vardır.
Dolayısıyla eğrinin yataya geçmesi açık bir biçimde görülmemektedir. Ancak yine bu grafik yorumlandığında 9 gibi bir değerin
uygun olduğu görülmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(dataset_x)
scaled_dataset_x = ss.transform(dataset_x)
import numpy as np
from sklearn.decomposition import PCA
total_ratios = []
for i in range(1, dataset_x.shape[1] + 1):
pca = PCA(i)
pca.fit(scaled_dataset_x)
total_ratio = np.sum(pca.explained_variance_ratio_)
total_ratios.append(total_ratio)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 8))
plt.title('Optimal number of Featured')
plt.plot(range(1, dataset_x.shape[1] + 1), total_ratios, color='red')
plt.plot(range(1, dataset_x.shape[1] + 1), total_ratios, 'bo', color='blue')
plt.legend(['Total explained variance ratio'])
plt.xlabel('Nuber of Features')
plt.ylabel('Ratio')
plt.xticks(range(1, dataset_x.shape[1] + 1))
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de MNIST örneğinde (784 özellik) toplam açıklanan varyans oranlarının grafiğini çizdirelim.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.datasets import mnist
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = mnist.load_data()
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 10))
for i in range(9):
plt.subplot(3, 3, i + 1)
plt.title(str(training_dataset_y[i]), fontsize=14)
plt.imshow(training_dataset_x[i], cmap='gray')
plt.show()
from tensorflow.keras.utils import to_categorical
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
scaled_training_dataset_x = training_dataset_x.reshape(-1, 784) / 255
scaled_test_dataset_x = test_dataset_x.reshape(-1, 784) / 255
import numpy as np
from sklearn.decomposition import PCA
total_ratios = []
for i in range(1, 300):
pca = PCA(i)
pca.fit(scaled_training_dataset_x)
total_ratio = np.sum(pca.explained_variance_ratio_)
total_ratios.append(total_ratio)
print(i, end=' ')
import pickle
with open('mnist.pickle', 'wb') as f:
pickle.dump(total_ratios, f)
import matplotlib.pyplot as plt
plt.figure(figsize=(30, 8))
plt.title('Optimal number of Featured')
plt.plot(range(1, 300), total_ratios, color='red')
plt.plot(range(1, 300), total_ratios, 'bo', color='blue')
plt.legend(['Total explained variance ratio'])
plt.xlabel('Nuber of Features')
plt.ylabel('Ratio')
plt.xticks(range(1, 300, 10))
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
PCA işleminin diğer bir kullanımı da çok sütunlu olan veri kğmelerinin sütun sayısını ikiye indirgeyerek grafiğini çizmek içindir.
Biz de zaten daha önceki örneklerimizde zambak veri kümesini kümeledikten sonra bu yöntemle grafiğini çizmiştik.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Anomalilerin tespit edilmesi (anomaly detection) makine öğrenmesinin popüler konularından biridir. Elimizde bir veri kümesi
olabilir. Burada bazı satırlar diğerlerinden şüphe oluşturacak biçimde farklı olabilir. Biz de bu farklı olan satırlatın belirlenmesini
isteyebiliriz. İngilizce "anomalarin tespit edilmesi (anomaly detection)" terimi yerine "outliers", "novelties", "noise", "deviations", "exceptions"
gibi terimler de kullanılabilmektedir.
Anomalilerin tespit edilmesi pek çok alanda kullanılabilecek bir uygulama konusudur. Örneğin bankalardaki şüpheli işlemlerin
tespit edilmeye çalışılması, bilgisayarlardaki zararlı unsurların tespit edilmesi (malware detection), biyomedikal görüntülerdeki
anomalilerin otomatik tespiti gibi pek çok faydalı amaçlar sıralanabilir. Anomalilerin tespit edilmesi "denetimli (supervied)"
öğrenme yöntemleriyle yapılabilirse de ana olarak bu konu "denetimsiz (unsupervied)" öğrenme konularının kapsamı içerisine girmektedir.
Elimizde anamolai içeren ve içermeyen bilgiler varsa biz eğitimli yöntemlerle kestirim yapabiliriz. Ancak genellikle bu tür durumlarda
elimizde yeteri kadar anomali içeren veri bulunmaz. Bu nedenle bu konuda daha çok "denetimsiz öğrenme yöntemleri" kullanılmaktadır.
Anomalalierin tespit edilmesi için pek çok yöntemden faydalanılabilmektedir. Örneğin:
- Yoğunluk Tabanlı Yöntemler (Isolation Forest, K-Nearest Neighbor, vs.)
- Destek Vektör Makineleri (Support Vector Machines)
- Bayes Ağları (Bayesian Networks)
- Saklı Markov Modelleri (Hidden Markov Models)
- Kümeleme Esasına Dayanan Yöntemler (Culestering Based Methods)
- Bulanık Mantık Kullanılan Yöntemler (Fuzzy Logic Methods)
- Boyutsal Özellik İndirgemesi ve Yükseltmesi Esasına Dayanan Yöntemler
Biz burada "kümeleme esasına dayanan yöntemler" ile "boyutsal özellik indirgemesi ve yükseltmesi esasına dayanan yöntemler"
üzerinde duracağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte zambak veri kümesi üzerinde DBSCAN kümeleme algoritması uygulanmıştır ve eps ve min_samples değeri ayarlanarak
anomali içeren noktalar tespit edilmiştir. Aşağıdaki örnekte siz de min_samples değerini sabit bırakarak epsilon değerini değiştirip
anomalileri tespit ediniz.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('iris.csv')
dataset = df.iloc[:, 1:-1].to_numpy()
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
scaled_dataset = mms.fit_transform(dataset)
import numpy as np
from sklearn.cluster import DBSCAN
dbs = DBSCAN(eps=0.65, min_samples=5)
dbs.fit(dataset)
nclusters = np.max(dbs.labels_) + 1
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
decomposed_dataset = pca.fit_transform(dataset)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(nclusters):
plt.scatter(decomposed_dataset[dbs.labels_ == i, 0], decomposed_dataset[dbs.labels_ == i, 1])
plt.scatter(decomposed_dataset[dbs.labels_ == -1, 0], decomposed_dataset[dbs.labels_ == -1, 1], marker='x', color='black')
legends = [f'Cluster-{i}' for i in range(1, nclusters + 1)]
legends.append('Noise Points')
plt.legend(legends, loc='lower right')
plt.show()
anomaly_data = dataset[dbs.labels_ == -1]
print(f'Number of points with anomly: {len(anomaly_data)}')
#----------------------------------------------------------------------------------------------------------------------------
OPTICS kümeleme algoritmasıyla anomalilerin tespit edilmesi de mümkündür. Anımsanacağı gibi biz OPTICS algoritmasında yalnızca min_samples
değerini veriyorduk. Bu durumda min_samples değeri değiştirilerek gürültü noktalarının (anomalilerin) sayısı artırılıp eksiltilebilir.
Aşağıda min_Sample=5 için zambak kümesi üzerinde bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('iris.csv')
dataset = df.iloc[:, 1:-1].to_numpy()
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
scaled_dataset = mms.fit_transform(dataset)
import numpy as np
from sklearn.cluster import OPTICS
optics = OPTICS(min_samples=5 )
optics.fit(dataset)
nclusters = np.max(optics.labels_) + 1
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
decomposed_dataset = pca.fit_transform(dataset)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(nclusters):
plt.scatter(decomposed_dataset[optics.labels_ == i, 0], decomposed_dataset[optics.labels_ == i, 1])
plt.scatter(decomposed_dataset[optics.labels_ == -1, 0], decomposed_dataset[optics.labels_ == -1, 1], marker='x', color='black')
legends = [f'Cluster-{i}' for i in range(1, nclusters + 1)]
legends.append('Noise Points')
plt.legend(legends, loc='lower right')
plt.show()
anomaly_data = dataset[optics.labels_ == -1]
print(f'Number of points with anomly: {len(anomaly_data)}')
#----------------------------------------------------------------------------------------------------------------------------
Anomalilerin tespit edilmesi için KMeans kümeleme yöntemi de kullanılabilir. Bu durumda biz K-Means algoritmasını tek küme
oluşturacak biçimde belirleriz. Böylece noktaların bir ağırlık metkezini elde ederiz. Sonra da bu ağırlık merkezine en uzak noktaları belirlemeye
çalışabiliriz. Tabii aslında burada K-Means algoritması yalnızca ağırlık merkezi bulmak için kullanılmaktadır. Biz bu ağırlık merkezini manuel biçimde de
bulabiliriz. Aşağıda zambak verileri için bu yöntem kullanılmıştır.
K-Means yöntemi ile anomali tespiti zayıf bir yöntemdir. DBSCAN ve OPTICS küeleme yöntemleri anomali tespiti için daha iyi
sonuç vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
NANOMALY_POINTS = 5
import pandas as pd
df = pd.read_csv('iris.csv')
dataset = df.iloc[:, 1:-1].to_numpy()
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
scaled_dataset = mms.fit_transform(dataset)
import numpy as np
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=1)
distances = kmeans.fit_transform(scaled_dataset)
arg_sorted_distances = np.argsort(distances[:, 0])
anomaly_points = dataset[arg_sorted_distances[-NANOMALY_POINTS:]]
print(anomaly_points)
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
decomposed_dataset = pca.fit_transform(dataset)
decomposed_normal_points = decomposed_dataset[arg_sorted_distances[:-NANOMALY_POINTS]]
decomposed_anomaly_points = decomposed_dataset[arg_sorted_distances[-NANOMALY_POINTS:]]
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('K-Means Anomaly Detection')
plt.scatter(decomposed_normal_points[:, 0], decomposed_normal_points[:, 1], color='blue')
plt.scatter(decomposed_anomaly_points[:, 0], decomposed_anomaly_points[:, 1], color='red')
plt.legend(['Normal Points', 'Anomaly Points'])
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de biraz daha gerçekçi bir örnek üzerinde çalışalım. Bu örnekte kredi kartı işlemlerine yönelik çeşitli bilgiler
toplanmıştır. Bu bilgiler PCA işlemine sokularak 29 sütuna indirilmiştir. İlk sütun işlemin göreli zamanını belirtmektedir.
Bu sütun veri kümesinden atılabilir. Son sütun ise işlemin anomali içerip içermediğini belirtmektedir. Bu sütun "0" ise
işlem anomali içermemektedir, "1" ise içermektedir. Veri kümesindeki toplam satır sayısı 284807 tanedir. Bunların yalnızca 492 tanesi
anomali içermektedir. Veri kümesi aşağıdaki bağlantıdan "creditcard.csv" ismiyle indirilebilir:
https://www.kaggle.com/datasets/mlg-ulb/creditcardfraud?resource=download
Veri kümesi üzerinde PCA işlemi uygulanmış durumdadır. Dolayısıyla PCA işleminden önce zaten özellik ölçeklemesi yapılmıştır. Bu nedenle biz bu
örnekte özellik ölçeklemesi yapmayacağız.
Aşağıdaki örnekte min_samples=5, eps=10 alınarak 100000 tane kredi kart verisi DBSCAN işlemine sokulmuş bu parametrelerle 707 tane
nokta anomali olarak tespit edilmiştir. DBSCAN parametreleriyle oynayarak anomali miktarını ayarlayabilirsiniz.
Buradaki kredi kart verilerini sayısı çok yüksektir. İşlemler yerel makinede fazlaca zaman alabilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('creditcard.csv', dtype='float32')
dataset = df.iloc[:100000, 1:-1].to_numpy()
dataset_y = df.iloc[:100000, -1].to_numpy()
import numpy as np
from sklearn.cluster import DBSCAN
dbs = DBSCAN(eps=10, min_samples=5)
dbs.fit(dataset)
nclusters = np.max(dbs.labels_) + 1
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
decomposed_dataset = pca.fit_transform(dataset)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Clustered Points')
for i in range(nclusters):
plt.scatter(decomposed_dataset[dbs.labels_ == i, 0], decomposed_dataset[dbs.labels_ == i, 1])
plt.scatter(decomposed_dataset[dbs.labels_ == -1, 0], decomposed_dataset[dbs.labels_ == -1, 1], marker='x', color='black')
legends = [f'Cluster-{i}' for i in range(1, nclusters + 1)]
legends.append('Noise Points')
plt.legend(legends, loc='lower right')
plt.show()
anomaly_data = dataset[dbs.labels_ == -1]
print(f'Number of points with anomly: {len(anomaly_data)}')
original_anomaly_indices = np.where(dataset_y == 1)
dbscan_anomaly_indices = np.where(dbs.labels_ == -1)
intersect_anomalies = np.intersect1d(original_anomaly_indices, dbscan_anomaly_indices)
success_ratio = len(intersect_anomalies) / (dataset_y == 1).sum()
print(f'Success ratio: {success_ratio}') # %26
#----------------------------------------------------------------------------------------------------------------------------
K-Means yöntemi ile kredi kart verileri üzerinde anomali tespiti de aşağıdaki gibi yapılabilir.
#----------------------------------------------------------------------------------------------------------------------------
NANOMALY_POINTS = 2000
import pandas as pd
df = pd.read_csv('creditcard.csv', dtype='float32')
dataset = df.iloc[:, 1:-1].to_numpy()
dataset_y = df['Class'].to_numpy()
import numpy as np
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=1)
distances = kmeans.fit_transform(dataset)
arg_sorted_distances = np.argsort(distances[:, 0])
anomaly_points = dataset[arg_sorted_distances[-NANOMALY_POINTS:]]
print(anomaly_points)
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
decomposed_dataset = pca.fit_transform(dataset)
decomposed_normal_points = decomposed_dataset[arg_sorted_distances[:-NANOMALY_POINTS]]
decomposed_anomaly_points = decomposed_dataset[arg_sorted_distances[-NANOMALY_POINTS:]]
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('K-Means Anomaly Detection')
plt.scatter(decomposed_normal_points[:, 0], decomposed_normal_points[:, 1], color='blue')
plt.scatter(decomposed_anomaly_points[:, 0], decomposed_anomaly_points[:, 1], color='red')
plt.legend(['Normal Points', 'Anomaly Points'])
plt.show()
original_anomaly_indices = np.where(dataset_y == 1)
kmeans_anomaly_indices = arg_sorted_distances[-NANOMALY_POINTS:]
intersect_anomalies = np.intersect1d(original_anomaly_indices, kmeans_anomaly_indices)
print(len(intersect_anomalies))
#----------------------------------------------------------------------------------------------------------------------------
Özellik indirgemesi ve yükseltmesi yoluyla anomali tespiti yapılabilmektedir. Bunun için n tane sütuna sahip olan veri kümesi
PCA işlemi ile k < n olmak üzere k tane sütuna indirgenir. Sonra yeniden bu noktalar n tane sütuna yükseltilir. Bu işlemler sonucunda
orijinal noktalarla indirgenip yükseltilmiş noktalar arasındaki uzaklaklar ne kadar fazla ise bu noktaların anomali olasılığı o kadar yükselmektedir.
Pekiyi bu yöntemde özellikleri kaça kadar düşürmeliyiz? Aslında bu noktada deneme yanılma yöntemine gidilerbilir. Ancak açıklanan varyans oranının %70'lerin
aşağısına düşürülemsi uygun olabilmektedir.
Aşağıdaki örnekte zambak veri kğmesindeki 4 sütun 1'e indirgenip yeniden 4'e yükseltilmiştir. Sonra da orijinal noktalarla indirgenip
yükselitlmiş noktalar arasındaki uzaklar hesaplmış ve en uzak belli sayıda nokta anomaly olarka tespit edilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
NANOMALY_POINTS = 20
import pandas as pd
df = pd.read_csv('iris.csv')
dataset = df.iloc[:, 1:-1].to_numpy()
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
scaled_dataset = mms.fit_transform(dataset)
from sklearn.decomposition import PCA
pca = PCA(n_components=1)
decomposed_dataset = pca.fit_transform(scaled_dataset)
inverse_transformed_dataset = pca.inverse_transform(decomposed_dataset)
import numpy as np
distances = np.sqrt(np.sum((scaled_dataset - inverse_transformed_dataset) ** 2, axis=1))
arg_sorted_distances = np.argsort(distances)
anomaly_indices = arg_sorted_distances[-NANOMALY_POINTS:]
anomaly_points = dataset[arg_sorted_distances[-NANOMALY_POINTS:]]
print(anomaly_points)
from sklearn.decomposition import PCA
pca_graph = PCA(n_components=2)
decomposed_dataset = pca_graph.fit_transform(dataset)
decomposed_normal_points = decomposed_dataset[arg_sorted_distances[:-NANOMALY_POINTS]]
decomposed_anomaly_points = decomposed_dataset[arg_sorted_distances[-NANOMALY_POINTS:]]
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('K-Means Anomaly Detection')
plt.scatter(decomposed_normal_points[:, 0], decomposed_normal_points[:, 1], color='blue')
plt.scatter(decomposed_anomaly_points[:, 0], decomposed_anomaly_points[:, 1], color='red')
plt.legend(['Normal Points', 'Anomaly Points'])
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte kredi kartı işlemleri üzerinde özellik indgirmesi ve yükseltmesi yöntemi ile anomali tespiti yapılmıştır.
1000 tane anomali noktasının içerisinde gerçek anomali noktaları toplam anomali noktalarının yaklaşık %50'si olarak bulunmuştur.
#----------------------------------------------------------------------------------------------------------------------------
NANOMALY_POINTS = 1000
import pandas as pd
df = pd.read_csv('creditcard.csv', dtype='float32')
dataset = df.iloc[:, 1:-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.decomposition import PCA
pca = PCA(n_components=10)
decomposed_dataset = pca.fit_transform(dataset)
inverse_transformed_dataset = pca.inverse_transform(decomposed_dataset)
import numpy as np
distances = np.sqrt(np.sum((dataset - inverse_transformed_dataset) ** 2, axis=1))
arg_sorted_distances = np.argsort(distances)
anomaly_indices = arg_sorted_distances[-NANOMALY_POINTS:]
anomaly_points = dataset[arg_sorted_distances[-NANOMALY_POINTS:]]
print(anomaly_points)
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
decomposed_dataset = pca.fit_transform(dataset)
decomposed_normal_points = decomposed_dataset[arg_sorted_distances[:-NANOMALY_POINTS]]
decomposed_anomaly_points = decomposed_dataset[arg_sorted_distances[-NANOMALY_POINTS:]]
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('K-Means Anomaly Detection')
plt.scatter(decomposed_normal_points[:, 0], decomposed_normal_points[:, 1], color='blue')
plt.scatter(decomposed_anomaly_points[:, 0], decomposed_anomaly_points[:, 1], color='red')
plt.legend(['Normal Points', 'Anomaly Points'])
plt.show()
original_anomaly_indices = np.where(dataset_y == 1)
pca_anomaly_indices = arg_sorted_distances[-NANOMALY_POINTS:]
intersect_anomalies = np.intersect1d(original_anomaly_indices, pca_anomaly_indices)
success_ratio = len(intersect_anomalies) / (dataset_y == 1).sum()
print(f'Success ratio: {success_ratio}')
#----------------------------------------------------------------------------------------------------------------------------
İstatistiksel sınıflandırma yöntemlerinin en basiti "Naive Bayes" denilen yöntemdir. Burada "naive" sıfatı yöntemde kullanılan
bazı varsayımlardan hareketle uydurulmuştur. Naive Bayes yöntemi tamamen olasılık kurallarına göre sınıflandırma yapmaktadır.
Naive Bayes yömntemi grup olarak "denetimli (supervised)" bir yöntemdir. Yöntemin temeli ünlü olasılık kuramcısı Thomas Bayes'in
"Bayes kuralı" olarak bilinen teoremine dayanmaktadır. İstatistikte Bayes Kuralına "koşulu olasılık (conditinal probablity)" kuralı da denilmektedir.
İstatistikte koşiullu olasılık bir olayın olduğu kabul edilerek başka bir olayın olasılığının hesaplanması anlamına gelmektedir.
Koşullu olasılık P(A|B) biçiminde gösterilmektedir. Burada P(A|B) ifadesi "B olayı olmuşken A'nın olma olasılığı" anlamına gelmektedir.
Yani buradaki olasılıkta zaten B'nin gerçekleştiği ön koşul olarak kabul edilmektedir.
P(A|B) olasılığı "B olmuşken A'nın olasılığı" anlamına geldiğine göre aşağıdaki gibi hesaplanır:
P(A|B) = P(A, B) / P(B)
Burada P(A, B) P(A kesişim B) ile aynı anlamdadır. Buradan P(A, B) olasılığını elde edelim:
P(A, B) = P(A|B) / P(B)
Şimdi de P(B|A) olasılığını hesaplayalım:
p(B|A) = P(A, B) / P(A)
Buradan da P(A, B) olasılığını elde edilim:
P(A, B) = p(B|A) / P(A)
İki eşitliğin sol tarafı eşit olduğuna göre sağ tarafları da birbirlerine eşittir:
P(A|B) * P(B) = P(B|A) * P(B)
O halde aşağıdaki iki eşitlik elde edilir:
P(A|B) = P(B|A) * P(A) / P(B)
P(B|A) = P(A|B) * P(B) / P(A)
Bu eşitliklere "Bayes Kuralı" denilmektedir.
Olasılık konusunda koşullu olasılıklara ilişkin tipik sorular "bir koşul altında bir olasılığın verilmesi ve bunun tersinin sorulması"
biçimindedir. Örneğin:
"Bir şirket çalışanları arasında üniversite mezunu olan 46 personelin 6'sının, üniversite mezunu olmayan 54 personelin 22'sinin
sigara içtiği biliniyor. Buna göre şirketteki sigara odasında sigara içtiği görülen bir çalışanın üniversite mezunu olma olasılığı nedir?"
Bu soruda bize verilenler şunlardır:
P(sigara içiyor|üniversite mezunu) = 6 / 46
P(sigara içiyor|üniversite mezunu değil) = 22 / 54
Bizden istenen de şudur:
P(üniversite mezunu|sigara içiyor)
Bayes formülünde bunları yerlerine koyalım:
P(üniversite mezunu|sigara içiyor)= P(sigare içiyor|üniversite mezunu) * P(üniversite mezunu) / P(sigara içiyor)
P(üniversite mezunu|sigara içiyor) = (6 / 46) * (46 / 100)) / (28 / 100)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bir olayın gerçekleşme olasılığının diğer bir olayın gerçekleşmesi olasılığı ile hiçbir ilişkisi yoksa bu iki olaya
"istatistiksel bağımsız olaylar" denilmektedir. Örneğin bir oyun kartından bir kart çekilmesi ve sonra bir zar atılması
olaylarını düşünelim. Bu iki olay istatistiksel bakımdan bağımsızdır. Çünkü çekilen kart atılan zarı etkilememktedir.
Bir olay oluşsa da olmamışsa da diğer olayın olasılığını etkilemiyorsa bu iki olay istatistiksel bakımdan bağımıszıdır.
P(A|B) koşuluu olasılığında B'nin olması ile A'nın hiçbir alakası yoksa bu olasılık P(A) ile gösterilebilir. Yani B olsa da olmasa da
A'nın olasılığı değişmiyorsa P(A|B) = P(A)'dır. Böylece
P(A, B) = P(A|B) * P(B) olduğuna göre A ve B istatistiksel olarak bağımsızsa bu formül şu hale gelmektedir:
P(A, B) = P(A) * P(B)
Buna olasılıkta "bağımsız olasılıkların çarpım kuralı" denilmektedir. Örneğin bir oyun destesinden çekilen kartın kupa ası olma
olasılığı ve sora atılan zarın 6 gelme olasılığı 1/52 * 1/6'dır.
Ancak doğada pek çok olay istatistiksel olarak bağımsız değildir. Çünkü biribirine etki etmektedir. Konunun felsefi açılımları da vardır.
Örneğin bir kişinin kalp hastası olması ile sigara içmesi istatistiksel bakımdan bağımsız değildir. Sigara içmek kalp hastalıklarına
yatkınlığı artırmaktadır.
İkiden fazla sitatistiksel olarak bağımsız olayın birikte gerçekleşmesi de yine çarpım kuralı ile ifade edilebilir. Örneğin:
P(A, B, C, D, E) = P(A) * P(B) * P(C) * P(D) * P(E)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Elimizde kategorik değerlern oluşan x1, x2, x3, ..., xn sütunlarına ilişkin bir veri kümesi olsun. Veri kümesindeki her satır m tane
kategoriden oluşan bir Y sınıfı ile eşleştirilmiş olsun. Bu durumda biz x1, x2, x3, ..., xn değerlerinin hangi sınıfa ilkişkin
olduğunu koşullu olasılığa dayalı olarak belirleyebiliriz. Zira m tane Y için tek tek P(Yk|Ax1, x2, x3, ..., xn) olasılıkları hesaplanıp
bunların hangisi yüksekse o kategori kestirimn için seçilebilir. P(Yk|Ax1, x2, x3, ..., xn) koşullu olasılığı şöyledir:
P(Yk|x1, x2, x3, ..., xn) = P(x1, x2, x3, ..., xn|Yk) * P(Yk) / P(x1, x2, x3, ..., xn)
Burada naive bir varsayımla x1, x2, x3, ..., xn olaylarının istatistiksel bakımdan bağımsız olduğunu varsayarsak aşağıdaki durumu
elde ederiz. (Bu varsayımda bulunmazsak buradan bir sonuç çıkmamaktadır):
P(Yk|x1, x2, x3, ..., xn) = (P(x1|Yk) * (x2|Yk) * P(x3|Yk) * ... * P(xn|Yk) * P(Yk)) / (P(x1) * P(x2) * P(x3) * ... * P(xn))
Burada Yk demekle Y'nin ayrı ayrı m tane kategorisini belirtiyoruz. Bu olasılıkların en büyüğü elde edileceğine göre ve bu
olasılıkların hespinde payda ve pay kısmındaki P(Yk)'lar aynı olduğuna göre bu karşılaştırmada onları boşuna hesaplamayız:
argmax (k = 1, ..., m) = P(x1|Yk) * (x2|Yk) * P(x3|Yk) * ... * P(xn|Yk)
Buerada eşitliğin sağ tarafı tablodaki sıklık değerleriyle elde edilebilmektedir. Buradaki koşulun sütunların istatistiksel olarak birbirinden
bağımsızlığı olduğuna dikkat ediniz. Yöntemi "naive" hale getiren bu koşuldur. Aslında bu koşul genellikle karşılanmaz. Ancak karşılandığı varsayılır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yukarıdaki açıklamalar veri kümesindeki tüm sütunların "kategorik" olduğu varsayımıyla yapılmıştır. Tabii Naive Bayes yöntemi
bir sınıflandırma yöntemi olduğuna göre Y verileri de her zaman kategoriktir. Ancak tüm sütunların kategorik olduğu durumlar çok seyrektir.
Genellikle sütunların bazıları "sürekli (continuous)" değerlerden oluşmaktadır. Pekiyi bu durumda ne yapılacaktır? Burada iki yönteme
başvurulabimektedir:
1) Değerleri ayrık hale getirmek (discretazation)
2) Değerleri normal dağılıma uydurmak.
Genellikle değerlerin normal dağılıma uydurulması yöntemi izlenmektedir. Bu yönteme "Gaussian Naive Bayes" denilmektedir. Bu yöntemde
sütunların her Y kategorisi için ortalamaları ve standart sapmaları hesaplanır. Bu değerlerden hareketle Gauss foksiyonu oluşturulur ve olasılıklar bu Gauss
fonksiyonundan hareketle elde edilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Naive Bayes yönteminde eğer sütunlar yalnızca 0 ve 1'lerden oluşuyorsa bu durumda olasılıkların Bernolli dağılımından hareketle hesaplanması
daha uygun olmaktadır. Eğer sütunlar sıklık sayılarından oluyorsa olasılıkların Binom dağılımından hareketle hesaplanması daha iyi olmaktadır.
Bu iki durum yazısal sınıflamalarda tipik karşılaşılan durumlardır. Anımsanacağı gibi vektörizasyon yapıldığında sütunlar binary ya da
sözcüklerin sıklık sayılarına ilişkin değerlerden oluşmaktaydı.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Naive Bayes yöntemi ile sınıflandırma için sklearn.naive_bayes modülünde çeşitli hazır sınıflar bulundurulmuştur. Bu sınıfların
kullanımları benzerdir. Hangi sınıfların hangi durumlarda kullanılacağı şöyle özetlenebilir:
CategoricalNB: Bütün sütunların zaten kategorik olduğu durumlarda kullanılır. Böylesi veri kümeleri seyrek karşımıza çıkmaktadır.
GaussianNB: Sütunların kategorik olmadığı durumlarda kullanılmaktadır. Tipik sınıflandırma problemleri böyledir.
BernoullyNB: Sütunların yalnızca 1 ve 0'laran oluştuğu durumlarda kullanılmaktadır. Tipik olarak CountVectorizer sınıfı ile
yazıların vektörize edilmesi sonucunda elde edilen veri kümelerinde kullanılır.
MultinomialNB: Sütunların sıklık sayılarından oluştuğu durumlarda kullanılmaktadır. Tipik olarak yazısal bilginin CountVectorizer
sınıfı ile vektörel hale getirilmesi sonucunda elde edilen veri kümelerinde kullanılmaktadır.
Gaussian Naive Bayes yönteminde programcının ayrıca "özellik ölçeklemesi" yapmasına gerek yoktur. Zaten yöntem kendi içerisinde sütunları
normal dağılıma uydurmaktadır. Ve bunu her kategorik olmayan sütun için bağımsız yapmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte "Winconsin Breast Cancer" veri kümesi üzerinde Gaussian Naive Bayes yöntemiyle kestirim yapılmıştır.
Burada scikit-learn içerisindeki GaussianNB sınıfından faydalanılmıştır. "Winconsin Breast Cancer" veri kümesi 30 biyomedikal
veriden hareketle kitlenin "iyi huylu (Benign)" mu yoksa "kötü huylu mu (Malign)" olduğunu anlamak için kullanılmaktadır. Veri kğmesi CSV
dosyası olarak aşağıdaki adresten indirilebilir:
https://www.kaggle.com/datasets/uciml/breast-cancer-wisconsin-data
Aslında bu veri kümesi zaten sklearn.datasets modülünde load_breast_cancer fonksiyonu ile yüklenebilmektedir. Bu fonksiyon bize bir
sınıf nesnesi verir. Sınıfın data ve target örnek öznitelikleri veri kümesine ilişkin x ve Y değerlerini vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
"""
import numpy as np
import pandas as pd
df = pd.read_csv("data.csv")
dataset_x = df.iloc[:, 2:-1].to_numpy()
dataset_y = np.zeros(len(df))
dataset_y[df['diagnosis'] == 'M'] = 1
"""
from sklearn.datasets import load_breast_cancer
bc = load_breast_cancer()
dataset_x = bc.data
dataset_y = bc.target
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
gnb.fit(training_dataset_x, training_dataset_y)
predict_result = gnb.predict(test_dataset_x)
from sklearn.metrics import accuracy_score
accuracy = accuracy_score(predict_result, test_dataset_y)
print(accuracy)
#----------------------------------------------------------------------------------------------------------------------------
Naive Bayes yönteminde artırımlı eğitim işlemleri sıfırdan yapmadan sağlanabilmektedir. Yani biz belli miktarda satırla eğitimi
yapmış olabiliriz. Daha sonra yeni satırlar geldikçe eğitimi devam ettirebiliriz. Bunun için scikit-learn sınıflarında partial_fit isimli
metotlar bulunmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Naive Bayes yönteminin şimdiye kadar gördüğümüz ve henüz görmedğimiz yöntemlere göre avantajları ve dezavantajları şöyle ifade
edilebilir:
- Kategorik sütunların bulunduğu veri kğmelerinde Naive Bayes yöntemi diğer yöntemlere göre daha iyi sonuç verme eğilimindedir.
Ancak karışık sütunlu ve kategorik olmayan sütunlu veri kümelerinde yöntemin etkinliği düşmektedir.
- Naive Bayes yöntemi az sayıda veriden (yani satırdan) oluşan veri kümelerinde oldukça etkilidir. Örneğin az sayıda veriler için
yapay sinir ağlarının iyi bir biçimde eğitilmesi mümkün olamamaktadır.
- Naive Bayes yöntemi basit aritmetik hesaplara dayalı olduğu için az miktarda belleğin kullanılmasına yol açmaktadır. Yine
hesaplama zamanı diğer yöntemlere göre çok daha hızlı olabilmektedir. Ancak sütun sayısı arttıkça algoirtmanın performansı düşme
eğilimindedir.
- Naive Bayes yöneteminin gerçekleştirimi nispeeten diğer yöntemlere göre daha kolaydır.
- Yöntem metinsel sınıflamalarda (örneğin spam filtrelemelerinde vs) basitliğinden ve hızından dolayı tercih edilebilmektedir.
- Naive Bayes yöntemi artırımlı eğitimlere uygun bir yöntemdir.
- Naive Bayes yöntemi sütunların istatistiksel bakımdan bağımısiz olduğu fikrine dayanmaktadır. Uygulamada bu koşulun sağlanması
çoğu kez mümkün olamamaktadır. Bu durumda modelin başarısı düşmektedir. Örneğin bir sütunda hava durumu (normal, güneşli, yağışlı gibi),
diğer sütunda havadaki nem oranı bulunuyor olsun. Bu ikis sütun aslında birbirinden bağımsız değildir. Yöntem bunların bağımsız olduğu fikriyle
uygulanmaktadır. Sütunlar birbirlerine ne kadar bağımlıysa yöntemin etkinliği o oranda düşmektedir.
- Veri kümesinde hiç bulunmayan x verileri tahmin edilmeye çalışılırsa 0 elde edilmektedir. Gerçi bunun için bazı yöntemler (smoothing methods)
uygulanmaktadır. Örneğin scikit-learn sınıfları bu yöntemleri uygulamaktadır. Ancak bu tür verilerin tahmini düşük bir performansla yapılabilmektedir.
- Naive Bayes yöntemi kategorik olmayan sütunlarda alternatif yöntemlere göre daha düşük performans gösterme eğilimindedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Lojistik olmayan regresyon problemlerinin istatistiksel çözümü için en çok kullanılan yöntemlerden biri "doğrusal regresyon (linear regression)"
denilen yöntemdir. Doğrusal regresyon noktaları temsil eden bir doğru denkleminin elde edilmesi işlemidir. Eğer böyle bir doğru denklemi
elde edilebilirse bu durumda kestirim yapılabilir. Kestirim için kestirilecek noktalar doğru denkleminde yerlerine konur ve sonuç elde edilir.
Pekiyi noktaları temsil eden bir doğrunun denklemi nasıl elde edilecektir? Çünkü noktalar bir doğru üzerinde olmayabilirler. O halde elde edilecek doğru denklemi
bu noktaları en iyi ortalayan bir doğrunun denklemi olmalıdır. Pekiyi noktaların en iyi biçimde ortalanmasının matematiksel ifadesi nedir?
Eğer sütun sayısı (yani özelliklerin sayısı) bir tane ise bu duruma istatistikte "basit doğrusal regresyon (simple linear regression)"
denilmektedir. Yani basit doğrusal regresyon aşağıdaki gibi veri kümeleri için söz konusudur:
x Y
10 12.3
13.2 18.2
14.3 19.8
... ...
Basit doğrusal regresyonda elde edilmek istenen doğru denklemi şöyle ifade edilebilir:
y = B0 + B1x
Genellikle istatistikte katsayılar için Beta sembolü kullanılmaktadır. Buradaki B'ler beta sembolü anlamında kullanılmıştır.
Eğer veri kümesinde iki sütun olsaydı bu durumda üç boyutlu uzayda bir düzlem söz konusu olurdu:
y = B0 + B1x1 + B2x2
N boyutlu uzayında bir düzelemi vardır. Buna "hyperplane" denilmektedir. Lineer cebirde doğrusallık iki boyutlu kartezyen koordinat sisteminde
nasıl yürütülüyorsa N boyutlu uzayda da benzer biçimde yürütülmektedir. Bu nedenle örnekler iki boyutlu düzlemde veriliyor olsa da
kolaylıkla genelleştirilebilmektedir. N boyutlu uzayda hyperplane denklemi ise şöyle oluşturulabilir:
y = B0 + B1x1 + B2x2 + B3... + Bnxn
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Basit doğrusal regresyonda noktaları en iyi ortalayan doğru denklemine ilişkin B0 ve B1 değerleri şöyle elde edilmektedir:
- Önce minimize edilecek fonksiyon belirlenmektedir. Minime edilecek fonksyon noktaların gerçek y değerlerinden doğru denkleminden
elde edilen y değerlerinin farklarının toplamları biçiminde oluşturulabilir. Tabii farklar pozitif ve negatif olabileceği için farkların kareleri
alınarak farkların her zaman pozitif olması sağlanmaktadır. O halde minimize ediecek fonksiyon şöyle ifade edilebilir:
(Y - (B0 + B1 x)) ** 2
- Bu değerleeri minimize eden B0 ve B1 değerleri birkaç biçimde elde edilebilmektedir. En basit yol buradaki fonksiyonun B0 ve B1 için
parçalı türevlerini alarak onları sıfıra eşitleyip oluşan denklemi çözmektir. Bu denlemin çözümünden elde edilen B0 ve B1 değerlererini
veren formüle "en küçük karerler (least square)" formülü de denilmektedir. Burada text ekrandan dolayı bu formülü veremiyoruz.
Ancak Internet'te Bo ve B1 değerlerini veren en küçük kareler formülünü kolaylıkla elde edebilirsiniz.
Aşağıdaki örnekte en küçük kareler formülü kullanılarak basit doğrusal regresyon için B0 ve B1 değerlerini elde eden bir fonksiyon
yazılmıştır. Bu fonksiyon aşağıda verilmiş olan "points.csv" dosaysı ile test edilmiştir:
2,4
3,5
5,7
7,10
7,8
8,12
9.5,10.5
9,15
10,17
13,18
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
def linear_regression(x, y):
a = np.sum((x - np.mean(x)) * (y - np.mean(y)))
b = np.sum(((x - np.mean(x)) ** 2))
b1 = a / b
b0 = (np.sum(y) - b1 * np.sum(x)) / len(x)
return b0, b1
dataset = np.loadtxt('points.csv', delimiter=',')
dataset_x = dataset[:, 0]
dataset_y = dataset[:, 1]
b0, b1 = linear_regression(dataset_x, dataset_y)
x = np.linspace(0, 15, 100)
y = b0 + b1 * x
import matplotlib.pyplot as plt
plt.title('Simple Linear Regression')
plt.scatter(dataset_x, dataset_y, color='blue')
plt.plot(x, y, color='red')
plt.xticks(range(1, 15))
plt.show()
predict_data = 12.9
predict_result = b0 + predict_data * b1
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
Aslında doğrusal regresyondaki beta katsayıları en türev alarak elde edilen en küçük kareler formülü dışında iteratif bir biçimde
nümerik analiz yöntemleriyle "gradient descent" denilen algortmik yolla da elde edilebilmektedir. Nümerik optimizasyon hakkında izleyen
bölümlerde bilgiler verilecektir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda da belirtildiği gibi basit (tekli) doğrusal regresyon aslında veri biliminde ve makine öğrenmesinde gerçek bir kullanıma
sahip değildir. Bu alanlarda kullanılan veri tablolarının çok fazla sütunu vardır. Dolayısıyla bu alanlarda "çoklu doğrusal regresyon
(multiplelinear regression)" ile karşılaşılmaktadır. x değerlerinin birden fazla olduğu (yani sütun sayısının birden fazla olduğu)
doğrusal regresyon modellerine istatistikte "çoklu doğrusal regresyon (multiple linear regression)" denilmektedir. Çoklu doğrusal
regresyon aslında N boyutlu uzayda noktaları en iyi oralayan bir hyperplane denkleminin elde edilmesi sürecidir. Çoklu doğrusal regresyonda
elde edilecek beta değerleri boyut sayısı N olmak üzere N + 1 tanedir. Örneğin sütun sayısının 5 olduğunu varsayalım. Bu durumda
hyperplane denklemi şöyle olacaktır:
y = B0 + B1x1 + B2x2 + B3x3 + B4x4 + B5x5
Çoklu doğrusal regresyon için Beta katsayılarının parçalı türevlerle nasıl elde edildiği biraz lineer cebir ve türev bilgisi gerektirmektedir.
Aşağıdaki dokümandan bu bilgileri edinebilirsiniz:
https://eli.thegreenplace.net/2014/derivation-of-the-normal-equation-for-linear-regression
Aşağıdaki örnekte çoklu doğrusal regresyon işlemini yapan multiple_linear_regression isimli bir fonksiyon da verilmiştir.
Bu fonksiyonda yapılan işlemler yukarıdaki makeleden alınmıtır. Her ne kadar aşağıdaki örnekte çoklu doğrusal regesyon için
en küçük kareler yöntemi uygulanmışsa da biz test işlemini yine basit doğrusal regresyon verileri üzerinde gerçekleştirdik.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
def multiple_linear_regression(x, y):
ones = np.ones((len(x), 1))
x = np.append(ones, x, axis=1)
beta = np.linalg.inv(x.T @ x) @ x.T @ y
return beta;
dataset = np.loadtxt('dataset.csv', delimiter=',')
dataset_x = dataset[:, 0]
dataset_y = dataset[:, 1]
beta = multiple_linear_regression(dataset_x.reshape(-1, 1), dataset_y)
x = np.linspace(0, 15, 100)
y = beta[0] + beta[1] * x
import matplotlib.pyplot as plt
plt.title('Simple Linear Regression')
plt.scatter(dataset_x, dataset_y, color='blue')
plt.plot(x, y, color='red')
plt.xticks(range(1, 15))
plt.show()
predict_data = 12.9
predict_result = beta[0] + predict_data * beta[1]
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
Doğrusal regresyon için özellik ölçeklemesi yapmaya gerek yoktur. Çünkü yöntemin matematiksel temelinde ölçekleme gerekmemektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aslında scikit-learn içerisinde zaten en küçük kareler yöntemini kullanarak doğrusal regresyon işlemini yapan sklearn.linear_model modülünde
LinearRegression isimli bir sınıf bulunmaktadır. Yani bizim yukarıdaki gibi fonksiyonları yazmamıza gerek kalmamaktadır. Sınıfın genel kullanımı diğer
scikit-learn sınıflarında olduğu gibidir. Sınıfın __init__ metodunun parametrik yapısı şöyledir:
sklearn.linear_model.LinearRegression(*, fit_intercept=True, copy_x=True, n_jobs=None, positive=False)
Sınıf nesnesi herhangi bir argüman verilmeden default argümanlarla yaratılabilir. Örneğin:
lr = LinearRegression()
Nesne yaratıldıktan sonra beta katsayılarının elde edilmesi için yine fit işlemi yapılır. Örneğin:
lr.fit(dataset_x, dataset_y)
fit işleminden sonra nesnenin coef_ örnek özniteliği beta katsayılarını (B1, B2, B3, ..., Bn) intercept_ örnek özniteliği ise B0 değerini
kestirim işlemi manuel bir biçimde intercept_ ve coef_ örnek özniteikleri kullanılarak yapılabilir. Ancak zaten sınıfta
bu işlemi yapan predict metodu vardır.
Aşağıdaki örnekte daha önce vermiş olduğumuz "points.csv" dosyası üzerinde LinearRegression sınıfıyla tahminleme yapılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('points.csv', delimiter=',')
dataset_x = dataset[:, 0]
dataset_y = dataset[:, 1]
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(dataset_x.reshape(-1, 1), dataset_y)
x = np.linspace(0, 15, 100)
y = lr.intercept_ + lr.coef_[0] * x
import matplotlib.pyplot as plt
plt.title('Simple Linear Regression')
plt.scatter(dataset_x, dataset_y, color='blue')
plt.plot(x, y, color='red')
plt.xticks(range(1, 15))
plt.show()
predict_data = 12.9
predict_result = lr.intercept_ + predict_data * lr.coef_[0]
print(predict_result)
predict_data = np.array([[12.9], [4.7], [6.9]])
predict_result = lr.predict(predict_data)
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi bir doğrusal regresyon uygulandığı zaman bu regresyonun başarısı hakkında bir şey söylenebilir mi? İşte bunun için bazı
ölçütler kullanılabilmektedir. En yaygın kullanılan ölçüt R^2 (R kare) denilen ölçüttür. R^2 değeri ne kadar büyük olursa
elde edilen doğrunun noktaları temsil güüc o kadar iyi olur. R^2 değerine İngilizce "coefficient of determination" da denilmektedir.
R^2 değeri "açıklanan (explained) varyans değerinin toplam varyansa oranıdır." Matematiksel olarak "kestirilen değerlerle
gerçek değerlerin arasındaki farkların karelerinin toplamının, gerçek değerlerle gerçek değerlerin ortalamasının farklarının karelerinin
toplamına oranı ile hesaplanmaktadır. Bu oran 1'den çıkartılır. R^2 değeri [0, 1] aralığında bir değerdir. Bu değer ne kadar büyük olursa
doğruların noktaları temsil etme özelliği o kadar fazla olmaktadır. Ancak R^2 değeri kestirimin gücü konusunda mutlak bir
belirlemeye sahip değildir. Yani R^2 değeri yüksek olduğu halde kestirim beklenen kadar güçlü olmayabilir. Ya da R^2 değeri
yüksek olmadığı halde kestirim iyi olabilir. Bunun çeşitli nedenleri vardır.
R^2 değeri LinearRegression sınıfının score isimli metodu ile elde edilebilmektedir. score metodunu çağırmadan önce bizim
fit işlemini yapmış olmamız gerekmektedir. score metodu bizden gerçek x ve Y değerlerini almaktadır. x değerleriyle predict işlemi
yapıp R^değerini hesaplayarak bize vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte "Boston Hausing Price" veri kümesi üzerinde çoklu doğrusal regresyon ile yapay sinir modeli uygulnamıştır.
Genel olarak bu haliyle yapay sinir ağı modelinin test verileri ile yapılan ölçümde (mean absolute error) her zaman daha iyi
sonuç verdiği görülmektedir. Ancak çoklu doğrusal regresyon da bu veri kümesi için kötü bir sonuç vermemektedir.
Örnekte çoklu doğrusal regresyonun R^2 değeri de yzdırılmıştır. Bu R^2 değeri ile kestirimin sonuçları her zaman örtüşmemektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(training_dataset_x, training_dataset_y)
predict_result = lr.predict(test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(predict_result, test_dataset_y)
print(f'Mean Absolute Error: {mae}')
r2 = lr.score(test_dataset_x, test_dataset_y)
print(f'R^2 = {r2}')
print()
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Boston-Housing-Price')
model.add(Dense(64, activation='relu', input_dim=training_dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=200, validation_split=0.2, verbose=0)
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 5))
plt.title('Epoch-Loss Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
plt.figure(figsize=(15, 5))
plt.title('Epoch-Mean Absolute Error Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
plt.plot(hist.epoch, hist.history['mae'])
plt.plot(hist.epoch, hist.history['val_mae'])
plt.legend(['Mean Absolute Error', 'Validation Mean Absolute Error'])
plt.show()
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi her türlü veri kğmesine doğrusal regresyon uygulayabilir miyiz? Bunun yanıtı hayır olacaktır. Doğrusal regresyon
veri kümesinin sütunları ile (bağımsız değişkenlerle) kestirilecek nicelik (bağımlı değişken) arasında doğrusal bir ilişkiye
benzer bir ilişki varsa iyi bir yöntem olabilmektedir. Eğer veri tablosunun sütunları ile kestirilecek nicelik arasında doğrusal
bir ilişki yoksa buı yöntemşn başarısı düşmektedir. Siz de R^2 değerinden hareketle bunu anlayabilirsiniz.
Noktaların doğrusallığının korelasyon katsayısı ile ölçüldüğünü anımsayınız. Dolayısıyla biz önce x ve Y değerleri arasındaki korelasyona
bakıp ondan sonra doğrusal regresyon uygulayabiliriz. Eğer bu korelasyon çok zayıf çıkarsa doğrusal regresyon uygulamamalıyız.
Aşağıdaki örnekte make_blobs fonksiyonu ile küresel noktalar üretilip bu noktalar üzerinde doğrusal regresyon uygulanmıştır.
R^2 çok düşük çıkmıştır. x ve Y verileri arasında doğursal bir ilişkiye benzeyen bir ilişiki yoksa doğrusal regresyon uygulamaya çalışmayınız.
Bu tür durumlarda yapay sinir ağları ya da "destek vektör makineleri" gibi yöntemler tercih edilmelidir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from sklearn.datasets import make_blobs
dataset, _ = make_blobs(n_samples=100, centers=1, center_box=(0, 0))
dataset_x = dataset[:, 0].reshape(-1, 1)
dataset_y = dataset[:, 1]
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(dataset_x, dataset_y)
x = np.linspace(-5, 5, 100)
y = lr.predict(x.reshape(-1, 1))
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Lşnear Regression with Circuler Data')
plt.scatter(dataset_x, dataset_y, color='blue')
plt.plot(x, y, color='red')
plt.show()
r2 = lr.score(dataset_x, dataset_y)
print(f'R^2 = {r2}')
cor = np.corrcoef(dataset_x.flatten(), dataset_y)
print(cor)
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi dataset_x veri kümesi çok sütun içeriyorsa bu durumda bu veri kümesi için doğrusal regresyonun uygun olup olmadığına
nasıl karar verebiliriz? Örneğin elimizde x1, x2, x3 ve x4 sütunlarına sahip bir veri kümesi olsun bunlar da Y değerleriyle
eşleşsinler. İşte bunun için iki yöntem uygulanabilir:
1) Biz önce doğrusal regresyonu uygulayıp R^2 değerine bakabiliriz. Eğer R^2 değeri düşük çıkarsa doğrusal regresyon uygulamanın
iyi bir fikir olmadığına karar veririz.
2) Veri kümesinin sütunlarıyla Y sütunu arasında tek tek ikili korelasyonlara bakarız. Eğer bu korelasyonlar yüksek değilse doğrusal regresyonun
iyi sonuç vermeyeceğini düşünürüz.
Pekiyi ikinci yöntemde bazı x sütunlarıyla Y arasında yüksek korelasyon bazılarıyla düşük korelasyon varsa ne yapabiliriz? Örneğin
x1, x2, x3, x4 sütunlarından x1 ve x3'ün Y ile korelasyonları yüsek ancak x2 ile x4'ün Y ile korelasyonları düşük olsun. Bu durumda
ne yapmalıyız? İlşte bu tür durumlarda uygulamacı düşük korelasyonu olan sütunları atarak "özellik seçimi (feature selection)"
uygulayabilir. Yani uygulamacı burada x1 ve x3 sütunlarını alıp x2 ve x4 sütunlarını atarak doğrusal regresyon uygulayabilir. Doğrusal
regresyonda tüm sütunların kullanılması zorunlu değildir. Bu tekniği uygulamak için Y verileri x verilerine katılıp np.corrcoef
fonksiyonu uygulanabilir. Bu fonksiyon uygulanarak heatmap grafiğin çizilmesi de görsel olarak tespitin yapılmasını kolaylaştırmaktadır.
Aşağıdaki örnekte "Boston Housing Price" veri kümesinde x sütunlarıyla Y arasındaki korelasyonlar incelenmiş ve 0.45'ten büyü olan
sütunlar alınarak onlarla doğrusal regresyon uygulanmıştır. Bu sonuç tüm sütunların elınmasıyla elde edilen sonuçtan biraz daha iyi olmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
CORR_THREASHOLD = 0.45
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=1234)
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(training_dataset_x, training_dataset_y)
predict_result = lr.predict(test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(predict_result, test_dataset_y)
print(f'Mean Absolute Error: {mae}')
r2 = lr.score(test_dataset_x, test_dataset_y)
print(f'R^2 = {r2}')
print()
import numpy as np
concat_dataset = np.concatenate((dataset_x, dataset_y.reshape(-1, 1)), axis=1)
corr = np.abs(np.corrcoef(concat_dataset, rowvar=False))
import seaborn as sns
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 8))
sns.heatmap(data=corr, annot=True)
plt.show()
selected_cols, = np.where(corr[:-1, -1] > CORR_THREASHOLD)
print(selected_cols)
selected_training_dataset_x = training_dataset_x[:, selected_cols]
selected_test_dataset_x = test_dataset_x[:, selected_cols]
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(selected_training_dataset_x, training_dataset_y)
predict_result = lr.predict(selected_test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(predict_result, test_dataset_y)
print(f'Mean Absolute Error: {mae}')
r2 = lr.score(selected_test_dataset_x, test_dataset_y)
print(f'R^2 = {r2}')
#----------------------------------------------------------------------------------------------------------------------------
Doğrusal regresyonun diğer önemli bir problemi de "multi-collinearity" denilen problemdir. Eğer doğrusal regresyon için kullanılan
x veri kümesindeki bazı sütunların kendi aralarında yüksek bir korelasypnu varsa yüksek korelasyonu olan sütunların birlikte
regresyona sokulması regresyonu kötüleştirmektedir. Bu durumda uygulamacı kendi aralarında yüksek korelasyona sahip olan sütunların yalnızca
bir tanesini alıp regresyon uygulamalıdır. İşte x sütunlarıyla Y arasında yüksek korelasyonu olanları seçtikten sonra ayrıca
seçilenlerin arasındaki korelasyonlara da bakmalıyız. Eğer seçilen arasında yüksek korelsyona sahip olanlr varsa onların da
bir tanesini muhafaza ederek diğerlerini atabiliriz. Pekiyi bu işlem nasıl yapılabilir? Birinci yöntem yine heatmap grafiğinden
de faydalanarak gözle tespitin yapılmasıdır.
Aralarında yüksek korelasyonun bulunduğu sütunları tespit etmek için nümerik yöntemler de geliştirilmiştir. Bunlardan en çok
kullanılanı "Variance Inflation Factor" denilen yöntemdir. Bu yöntemde veri kümesinin sütunları tek tek bu bakımdan değerlendirilir.
Yüksek skor alan sütunların diğer sütunlarla korelasyonu olduğu sonucuna varılır. Bu yöntemin matematiksel açıklamasını burada yapmayacağız.
Bu yöntemi uygulayan scikit-learn ya da scipy içerisinde hazır fonksiyon yoktur. Ancak "statsmodels" kütüphanesinde statsmodels.stats.outliers_influence
modülünde variance-inflation_factor isimli bir fonksiyon bulunmaktadır. Kütüphane Anaconda dağıtımıunda default olarak bulunmamaktadır.
Aşağıdaki biçimde kurulabilir:
pip install statsmodels
variance_infaltion_factor fonksiyonun iki parametresi vardır. Birinci parametre x verilerinin bulunduğu matrisi almaktadır.
İkinci parametre sütun numarasını alır. Uygulamacının bir döngü içerisinde tüm sütunlar için bu fonksiyonu çağırması gerekir.
Pekiyi fonksiyonun geri döndürdüğü VIF değeri nasıl yorumlanacaktır? Yorumu tipik olarak şöyle yapılmaktadır:
- Eğer 1 ise sütunun diğerleri ile korelasyonu yoktur.
- Eğer 1 ile 5 arasında ise sütunun diğer sütunlarla orta derecede bir korelasyonu vardır.
- 5'ten büyük ise sütun diğer sütunlarla yüksek bir korelasyon içerisindedir.
O halde bizim iki yöntemi karma etmemiz gerekmektedir. Yani biz hem Y ile yüksek korelasyonu olan x sütunlarını almalıyız. Hem de
kendi aralarında yüksek korelasyonu olan x sütunlarını atmalıyız.
Aşağıdaki örnekte "Boston Housing Price" veri kümesinde sütunların VIF değerleri elde edilip 5'ten küçük olanlar
yazdırılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
CORR_THREASHOLD = 0.45
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=1234)
import numpy as np
concat_dataset = np.concatenate((dataset_x, dataset_y.reshape(-1, 1)), axis=1)
corr = np.abs(np.corrcoef(concat_dataset, rowvar=False))
import seaborn as sns
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 8))
sns.heatmap(data=corr, annot=True)
plt.show()
selected_cols, = np.where(corr[:-1, -1] > CORR_THREASHOLD)
print(selected_cols)
from statsmodels.stats.outliers_influence import variance_inflation_factor
vifs = np.array([variance_inflation_factor(training_dataset_x, i) for i in range(training_dataset_x.shape[1])])
for i, vif in enumerate(vifs):
print(f'{i} ---> {vif}')
selected_cols, = np.where(vifs < 5)
print(selected_cols)
#----------------------------------------------------------------------------------------------------------------------------
Matematiksel optimizasyonlar temelde iki yöntemle yapılabilmektedir:
1) Sembolik (ya da analitik) çözümlerden
2) Nümerik çözümler
Sembolik ya da analitik çözüm demek bilinen matematik kurallarla çözümü farmüllere indirgeyip değişkenleri bu formüllerde yerlerine
koyarak yapılan öçözüm demektir. Örneğin tekl,i ve çoklu doğrusal regresyon için analitik çözüm kolay bir biçimde yapılabilmektedir.
Ancak bazı durumlarda sembolik ya da analitik çözüm mümkün olmayabilir ya da etkin olmayabilir. İşte bu tür durumlarda iteratif biçimde
yavaş yavaş hedefe yaklaşma yoluna gidilir. Bunun için çeşitli yöntemler geliştirilmiştir. Hdefe bu biçimde varan çözümlere genel olarak
"nümerik çözümler" denilmektedir. Makine öğrenmesi uygulamarında karşılaşılan optimizasyon problemleri genel olarak etkin bir biçimde sembolik
ya da analitik yolla çözülememektedir. Bu nedenle bu problemlerin çoğu nümerik yolla çözülürler.
Sembolik ya da analitik çözüm daha kesin bir sonuca varılmasını sağlayabilmektedir. Bu çözüm çoğu durumda daha hızlı olmay eğilimindedir.
Ancak bazı durumlarda nümerik yöntemlere göredaha yavaş kalmaktadır. Nümerik çözümler iteratif olduğu için hedeflenen sonuca yavaş yavaş yaklaşmayı
sağlarlar. Dolayısıyla gerçek optimal nokta ile elde edilen arasında farklar oluşabilmektedir. Ancak asıl önemli nokta pek çok problemin
sembolik ya da analitik çözümünün mümkün olmaması ya da işlem yükünün aşırı yüksek olmasıdır. Maalesef makine öğrenmesindeki pek çok optimizasyon
işlemleri bu biçimdedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
scikit-learn içerisindeki LinearRegression sınıfı yukarıda bizim benzerini yazdığımız "en küçük kareler" yöntemini kullanmaktadır.
Bu sınıf ile çoklu doğrusal regresyon yaparken uygun sütunların seçilmesi uygulamacının sorumluluğundadır. İşte bu konuda uygulamacıya
yardımcı olan üç önemli regresyon modeli bulunmaktadır: Lasso Regresyonu, Ridge Regresyonu ve EleasticNet regresyonu. Bu regresyon
modelleri aslında birbirine benzemektedir. Birbirlerinin çeşitli versiyonları gibidir. Bu regresyon modelleri sembolik biçimde çözülmemekte,
iteratif nümerik analiz yöntemleriyle "gradient descent" optimizasyon tekniği ile çözülmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Lasso regresyonu en küçüğk kareler yöntemine mutlak değer içeren bir ceza puanının eklenmesiyle oluşturulmuştur. Dolayısıyla
regresyon modelinin nümerik analiz yöntemiyle iteratif çözülmesinde kullanılan en küçüklenecek amaç fonksiyonu (maliyet fonksiyonu)
bu ceza puanı nedeniyle aralarında yüksek korelasyon olan sütunları ve Y değerleriyle düşük korelasyon olan sütunları elimine
etmektedir. Yani Lasso regresyonu kendi içerisinde zaten yukarıda manuel yapmaya çalıştığımız "özellik seçimlerini" yapmaktadır.
Lasso regresyonunda en küçük kareler yöntemine eklenen bu ceza puanına "L1 Düznlemesi (L1 regulation)" denilmektedir.
Lasso regresyonunda ceza puanını oluşturan L1 düzenlemesinde Lambda ile ifade edilen bir parametre vardır. Lasso regresyonu uygularken
bu parametrenin uygulamacı tarafından seçilmesi gerekmektedir. Bu parametre yüksek seçilirse ceza puanı artar. Dolayısıylşa daha fazla
sütun elimine edilir. Bu parametre düşürülürse daha az sütun elimine edilmektedir. Bu bağlamda lamda parametresi bir "hyperparametre"dir.
Lasso regresyonunun sembolik ya da analitik çözümü yapılabilirse de asıl olarak nümerik çözüm uygulanmaktadır. Alpha değeri 0 alınırsa bu durumda
Lasso regresyonunun en küçük kareler yönteminden bir farkı kalmamaktadır.
Lasso regresyonunda "özellik ölçeklemesi" gerekmektedir. Gnel olarak "standart ölçekleme (standard scaling)" tercih edilmektedir.
Ancak "min-max ölçeklemesi" de benzer sonuçları vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Lasso regeresyonu sklearn.linear_model modülünün içerisindeki LAsso sınıfı ile uygulanabilmektedir. Sınıfın kullanımı diğer
scikit-learn sınıflarına olduğu gibidir. Lasso sınıfının __init__ metodunun parametrik yapısı şöyledir:
class sklearn.linear_model.Lasso(alpha=1.0, *, fit_intercept=True, precompute=False, copy_x=True,
max_iter=1000, tol=0.0001, warm_start=False, positive=False, random_state=None, selection='cyclic')
Metodun en önemli parametresi L1 düzenlemesinde söz konusu olan bizim lamda dediğimiz parametredir. Bizim lamda dediğimiz
bu parametreye scikit-learn içerisinde alpha denilmektedir. Bu parametrenin default değerinin 1 olduğuna dikkat ediniz.
Daha sonra yaratılan nesne ile sınıfın fit metodu x ve Y verilerini vererek çağrılır. Yine tahminleme için predict metodu
kullanılmaktadır. Oluşturulan hyperplane'in katysayı değerleri intercept_ ve coef_ örnek özniteliklerindne elde edilebilmektedir.
Lasso regresyonu kendi içerisinde özellik seçimi yapacağından dolayı sınıfın coef_ ile elde edilen katsayılarının bazılarının sıfır
olabilmektedir. Bu katsayılırn sıfır olması aslında o sütunun tamamen atıldığı anlamına gelmektedir.
Aşağıdaki örnekte "Boston Housing Price" veri kğmesi üzerinde Lasso regresyonu uygulanmıştır. Burada alpha parametresi 0.1
seçilmiştir. Bu seçim soncunda iki sütun atılmıştır. Bu örnekte veriler üzerinde standart ölçekleme de uygulanmıştır.
Tabii kestirim bulunulurken kestirilmek istenen x değerlerinin de yine aynı standart ölçeklemeye sokulması gerekmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=1234)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(training_dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
scaled_test_dataset_x = ss.transform(test_dataset_x)
from sklearn.linear_model import Lasso
lasso = Lasso(alpha=0.1)
lasso.fit(scaled_training_dataset_x, training_dataset_y)
print(f'Intercept: {lasso.intercept_}')
print(f'Coefficients: {lasso.coef_}')
test_predict_result = lasso.predict(scaled_test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(test_predict_result, test_dataset_y)
print(f'Mean Absolute Error: {mae}')
r2 = lasso.score(scaled_training_dataset_x, training_dataset_y)
print(f'R^2: {r2}')
import numpy as np
predict_data = np.array([[0.98843, 0.00, 8.140, 0, 0.5380, 5.813, 100.00, 4.0952, 4, 307.0, 21.0, 394.54, 19.88], [0.75026, 0.00, 8.140, 0, 0.5380, 5.9240, 94.10, 4.3996, 4, 307.0, 21.00, 394.33, 16.30]])
scaled_predict_data = ss.transform(predict_data)
predict_result = lasso.predict(scaled_predict_data)
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
Lasso regresyonundaki alpha parametresini otomatik ayarlamaının bir yolu yoktur. Uygulamacı değişik alpha değerlerini deneyip
uygun bir değeri seçebilir.
Aşağıdaki örnekte "Boston Housing Price" veri kümesi üzerinde alpha 0'dan başlatılarak 0.05 artırımla Lasso regresyonu çok defalar uygulanıp
"mean absolute error" değerinin en düşük olduğu alpha değeri tespit edilmeye çalışılmıştır. Burada R^2 değerinin yükselmesini
önemsemeyiniz. Daha önceden de belirttiğimiz gibi R^2 değeir doğrusal regresyonun iyiiğine ilişkin bir ölçüt olmasına karşın
her zaman kestirim gücünü yordayamamktadır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=1234)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(training_dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
scaled_test_dataset_x = ss.transform(test_dataset_x)
import numpy as np
from sklearn.linear_model import Lasso
for alpha in np.arange(0, 2, 0.05):
print(f'Alpha: {np.round(alpha, 2):.2F}')
lasso = Lasso(alpha=alpha)
lasso.fit(scaled_training_dataset_x, training_dataset_y)
test_predict_result = lasso.predict(scaled_test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(test_predict_result, test_dataset_y)
print(f'Mean Absolute Error: {mae}')
r2 = lasso.score(scaled_training_dataset_x, training_dataset_y)
print(f'R^2: {r2}')
print('------------------------------------------------')
#----------------------------------------------------------------------------------------------------------------------------
Lasso regresyonunun bir benzerine "Ridge Regresyonu" denilmektedir. Ridge regresyonu da tıpkı Lasso regresyonunda olduğu gibi özellik
seçimi yapmaktadır. Ancak Ridge regresyonu sütunları tam olarak atmaz. Onların etkisini zayıflatır. Dolayısıyla Ridge regresyonunda
coef_ katsayı örnek özniteliklerinin bazıları sıfır değil sıfıra yakın değerler olacaktır.
Ridge regresyonun temel mantığı Lasso regresyonunda olduğu gibidir. Ancak bu regresyonda ceza terimi mutlak değer değil kare içermektedir.
Bu ceza terimine "L2 Düzenlemesi (L2 Regulation)" da denilmektedir. Ridge regresyonundaki ceza teriminde de bir lamda parametresi vardır.
Yine bu lamda parametresi yükseltilirse daha fazla sütunun etkisi azaltılmaktadır. Yani daha fazla sütun değeri 0'a yaklaştırılmaktadır.
Lamda parametresi düşürülürse daha az sütun 0'a yaklaştırılır.
Ridge regresyonunda da yine özellik ölçeklemesi yapılmalıdır. Genel olarak uygulamacılar standart ölçeklemyi tercih ederler. Ancak min-max
ölçeklemesi de benzer sonuçları vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Ridge regresyonu için scikit-learn kütüphanesinde sklearn.linear_model modülünde Ridge isimli bir sınıf bulundurulmuştur.
Sınıfın __init__ metidonun parametrik yapısı şöyledir:
class sklearn.linear_model.Ridge(alpha=1.0, *, fit_intercept=True, copy_x=True, max_iter=None, tol=0.0001,
solver='auto', positive=False, random_state=None)
Sınıfının genel kullanımı Lasso sınıfı ile aynıdır.
Aşağıdaki örnekte "Boston Hosuing Price" verileri üzerinde Ridge regresyonu uygulanmıştır. Bu örnekte alpha parametresi büyütüldükçe bazı
sütun katsayıları sıfıra yaklaşmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=1234)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(training_dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
scaled_test_dataset_x = ss.transform(test_dataset_x)
from sklearn.linear_model import Ridge
ridge = Ridge(alpha=3)
ridge.fit(scaled_training_dataset_x, training_dataset_y)
print(f'Intercept: {ridge.intercept_}')
print(f'Coefficients: {ridge.coef_}')
test_predict_result = ridge.predict(scaled_test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(test_predict_result, test_dataset_y)
print(f'Mean Absolute Error: {mae}')
r2 = ridge.score(scaled_training_dataset_x, training_dataset_y)
print(f'R^2: {r2}')
import numpy as np
predict_data = np.array([[0.98843, 0.00, 8.140, 0, 0.5380, 5.813, 100.00, 4.0952, 4, 307.0, 21.0, 394.54, 19.88], [0.75026, 0.00, 8.140, 0, 0.5380, 5.9240, 94.10, 4.3996, 4, 307.0, 21.00, 394.33, 16.30]])
scaled_predict_data = ss.transform(predict_data)
predict_result = ridge.predict(scaled_predict_data)
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Elastic Net regresyonu denilen regresyon modeli de aslında Lasso ve Ridge regresyonlarının ceza terimlerinin birleştirilmiş bir
biçimidir. Yani ceza terimi içerisinde hem L1 düzenlemesi hem de L2 düzenlemesi bulunmaktadır. Dolayısıyla modelin lamda1 ve lamda2
biçiminde iki heyper parametresi vardır. Modelin isminin "elastic" olarak isimlendirilmesi daha esnek bir kullanıma izin vermesindendir.
Burada hem bazı sütunlar elimine edilirken bazılarının etkileri de düşürülmektedir. Tabii heyper parametrelerin fazla olması daha fazla ayarlamaya
gereksinim duyulmasına yol açmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Elistic Net regresyon modeli scikit-learn kütüphanesinde linear_model modülündeki ElasticNet isimli sınıfla temsil edilmiştir.
Sınıfın __init__ metodunun parametrik yapısı şöyledir:
class sklearn.linear_model.ElasticNet(alpha=1.0, *, l1_ratio=0.5, fit_intercept=True, precompute=False, max_iter=1000,
copy_x=True, tol=0.0001, warm_start=False, positive=False, random_state=None, selection='cyclic')
Burada alpha parametresi yine temel bir hyper parametredir. Hem L1 düznlemesi üzerinde hem de L2 düzüenlemesi üzerinde etkili olmaktadır.
l1_ratio parametresi ise L1 düzenlemesinin L2 düzenlemesine göre etkisini belirlemektedir. l1_ratio default olarak 0.5 değerindedir.
Yani bu durumda L1 düzenlemesi de L2 düzenlemesi de aynı oranda etkiye sahiptir. Uygulamacı alpha değerini sabit tutarak l1_ratio
değerini değiştirip bunları ayarlayabilir. Ya da tam tersini yapabilir.
ElasticNet sınıfının kullanımı tamamen diğer sınıflarda olduğu gibidir.
Aşağıdaki örnekte "Boston Housing Price" veri kümesinde Elastic Net regresyon modeli uygulanmıştır. Parametreler doğru ayarlanırsa
ElsticNet diğerlerinden daha iyi sonuç verebilmektedir. Ancak parametrelerin ayarlanması daha zahmetlidir. Bunun için pek çok denme
yanılmanın yapılması gerekebilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=1234)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(training_dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
scaled_test_dataset_x = ss.transform(test_dataset_x)
import numpy as np
from sklearn.linear_model import ElasticNet
for ratio in np.arange(0.1, 1, 0.05):
print(f'Alpha: 0.5, L1 Ratio: {ratio}')
elasticnet = ElasticNet(alpha=0.1, l1_ratio=ratio)
elasticnet.fit(scaled_training_dataset_x, training_dataset_y)
test_predict_result = elasticnet.predict(scaled_test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(test_predict_result, test_dataset_y)
print(f'Mean Absolute Error: {mae}')
r2 = elasticnet.score(scaled_training_dataset_x, training_dataset_y)
print(f'R^2: {r2}')
print('------------------------------------------------')
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Biz şimdiye kadar doğrusal regresyon gördük. Doğrusal regresyon noktaları ortalayan bir doğrunun elde edilmesi süreci idi.
Tabii "doğru" sözcüğü iki boyutlu uzaya ilişkin bir terimdir. Çok bouyutlu uzayda doğru yerine o uzayın düzlemi söz konusu
olur kş buna da biz "hyperplane" demiştik. Tek özelliğe sahip veri kümelerinin belirttiği iki boyutlu uzayla çok özelliğe sahip
veri kümelerinin belirttiği çok boyutlu uzaydaki işelmler genel itibari ile benzerdir.
Tek teğişkenli bir polinomun genel biçimi şöyledir:
P(x) = a0 + a1 x + a2 x^2 + a3 x^3 + ... + an x^n
Tabii değişken sayısı (ayni veri kümesindeki sütun sayısı) birden fazla olduğunda polinomun genel biçimi daha çok terim
içeren daha karmaşık bir hale gelmektedir. Örneğin iki değişkenli polinomun genel biçimi şöyledir:
P(x1, x2) = a0 + a1x1 + a2x2 + a3 x1x2 + a4 * x1^2 + a5 x2^2 + a6 x1^2 x2 + a7 x1x2^2 a8 x1^3 + a9 x2^3 + .... +
Örneğin üç değişkenli bir polinomun genel biçimi de şöyle olacaktır:
P(x1, x2, x3) = a0 + a1 x1 + a2 x2 + a3 x3 + a4 x1 x2 + a4 x1 x3 + a5 x2 x3 + a5 x1 x2 x3 + a6 x1^2 x2 + a7 x1 x2^2 + a8 x2 x3^ + ....
Görüldüğü gibi polinomsal regresyonda veri kümesindeki özellikler arttıkça tahmin edilmesi gereken parametrelerin sayısı da
artmaktadır.
Her ne kadar noktaları ortalayan doğru yerine eğri geçirmek daha uygun gibi gözüküyorsa da yukarıdaki polinomların genel biçimlerinde
görüldüğü üzere bu durum tahmin edilmesi gereken parametrelerin sayısını artırmaktadır. Yani modeli daha karmaşık hale getirmektedir.
Biz eğri geçirmenin avantajından faydalanırken o eğrinin parametrelerini daha zor tahmin edebilmekteyiz.
Bir polinomun derecesi onun en yüksek üssüyle belirtilmektedir. Pekiyi biz noktalarımız için kaçıncı derece bir polinom geçirmeliyiz?
Şüphesiz derece arttıkça eğri daha fazla dalgalanacağı için daha uygun bir eğrinin bulunma olasılığı artmaktadır. Ancak yukarıda da
belirtitğimiz gibi polinomun derecesi yüksek tutulursa bu durumda tahmin edilecek parametrelerin sayısı artmaktadır. Bu da uygun bir
polinomun bulunmasını zorlaştırabilmektedir. Polinomsal regresyon yaparken polinomun derecesinin yüksek tutulması genellikle uygun olmaz.
İkinci derece, üçüncü derece polinomlar modeli çok karmaşık hale getirmediği için tercih edilmektedir. Yüksek dereceli polinomlar
overfitting durumuna da yol açabilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi polinomsal regresyonun katsayı değerleri hangi yöntemle elde edilmektedir. Örneğin iki sütunumuzun olduğu bir veri kümesindeki
noktaları biz üçüncü derece bir polinomla temsil etmek iteyelim. Böyle bir polinomun genel biçimi şöyledir:
P(x1, x2) = a0 + a1 x1 + a2 x2 + a3 x1 x2 + a4 x1^2 + a5 x2^2 + a6 x1 x2^2 + a7 x1^2 x2 + a8 x1^3 + a9 x2^3
Burada görükldüğü gibi 9 tane tahmin edilmesi gereken katsayı değeri vardır. Pekiyi bu değerler matematiksel olaraK nasıl
tahmin edilecektir?
İşte aslında polinomsal regresyonda polinomsal regresyon doğrusal regresyona dönüştürülerek çözüm gerçekleştirilir. Polinomsal
regresyonun doğrusal regresyona dönüştürülmesine "polinomsal transformasyon (polynomial transformation)" denilmektedir. Biz burada
bu transformasyonun nasıl yapıldığı üzerinde durmayacağız. Ancak buradaki temel mantık değişkenlerin katsayı yapılması katsayıların
değişken yapılması esasına dayanmaktadır. Yani yukarıdaki polinom adeta aşağıdaki doğru denklemine dönüştürülmektedir:
P(x1, x2) = a0 + x1 a1 + x2 a2 + x1 x2 a3 + x1^2 a4 + x2^2 a5 + x1 x2^2 a6 +x1^2 x2 a7 + x1^3 a8 + x2^3 a9
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Polinomsal regresyon sckit-learn kütüphanesinde iki aşamada çözülür. Birinci aşamada x katsayı matrisi "polinomsal transformasyona"
sokularak doğrusal regresyon biçimine getirilir. İkinci aşamda ise doğrusal regresyon çözümü yapılır.
Polinomsal regresyon için polinomsal transformasyon sklearn.preprocessing modülündeki PolynomialFeatures sınıfıyla yapılmaktadır.
Sınıfın __init__ metodunun parametrik yapısı şöyledir:
class sklearn.preprocessing.PolynomialFeatures(degree=2, *, interaction_only=False, include_bias=True, order='C')
Metodun ilk parametresi noktaların kaçıncı dereceden bir polinom için dönüştürüleceğini belirtmektedir. Daha sonra işlemler
diğer sckit-learn sınıflarına olduğu gibi yapılmaktadır. Yani önce fit işlemi sonra transform işlemi ya da doğrudan fit_transform işlemi
yapılabilir. Bu işlemin sonucunda biz aslında polinomsal regresyon için kullanacağımız orijinal veri kümesini doğrusal regresyon için
kullanılabilecek bir veri kümesine dönüştürmüş oluruz. Bundan sonra elde edilen bu veri kümesine LinearRegresson ya da Lasso, Ridge, ElasticNet
gibi sınıflarla doğrusal regresyon uygulanır.
Orijinal verilerin m satırdan ve n sütun oluştuğunu vasayalım. PolynomialFeatures işleminden elde edilen matris yine m tanesatıra sahip olacaktır.
Ancak sütunları polinomun terim sayısı kadar olacaktır. Örneğin 6 tane satıra ilişkin tek sütunlu bir veri kümesini 2'inci derece polinom için
PolynomialFeatures ile dönüştürmek isteyelim. Bu durumda fit_transform metodu ile elde edilecek dönüştürülmüş matrisin boyutları 6x3 olacaktır.
Çünkü burada bulunmak istenen polinom P(x) = a0 + a1 x + a2 x^2 biçimindedir ve burada 3 terim vardır.
PolynomialFeatures sınıfından elde eedilen dönüştürülmüş matris ile doğrusal regresyon ugulandığında elde edilen coef_ değerleri aslında
polinomsal regresyondaki katsayıları belirtmektedir. Doğrusal regresyonun intercept_ değeri kullanılmamaktadır. Doğrusal regresyon
sonucunda elde edilen R^2 değeri yine bize polinomsal regresyonun uygunluğu konusunda bilgi vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Önce doğrusal regresyonun uygun olmadığı bir örnek verelim. Örneğimizdeki "points.csv" dosyasının içeriği şöyle olsun:
2,4
3,5
5,7
7,10
7,8
8,12
9.5,10.5
9,15
10,17
13,18
Burada ilk sütun x verileri ikinci sütun Y verileridir. Dolayısıyla tek değişkenli bir veri kümesi söz konusudur. Buradan elde edilen
R^2 değeri 0.69 gibi düşük bir değerdir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('points.csv', delimiter=',')
dataset_x = dataset[:, 0]
dataset_y = dataset[:, 1]
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(dataset_x.reshape(-1, 1), dataset_y)
x = np.linspace(0, 100, 1000)
y = lr.predict(x.reshape(-1, 1))
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Polynomial Data Inappropriate Linear Regression')
plt.scatter(dataset_x, dataset_y, color='blue')
plt.plot(x, y, color='red')
plt.show()
r2 = lr.score(dataset_x.reshape(-1, 1), dataset_y)
print(f'R^2: {r2}')
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de yukarıdaki veriler için üçüncü derece bir polinom geçirmeye çalışalım. Üçüncü derece polinomun genel biçimi şöyledir:
P(x) = a0 + a1 x + a2 x^2 + a3 x^3
Burada toplam 4 tane terim sayısı vardır. Bu durumda biz polinomsal transformasyon yaptığımızda dört sütunlu bir veri kümesi elde
ederiz. O halde aslında uygulayacağımız doğrusal regresyon sanki dört sütunlu doğrusal regresyon gibidir. Örneğin:
import numpy as np
dataset = np.loadtxt('points.csv', delimiter=',')
dataset_x = dataset[:, 0]
dataset_y = dataset[:, 1]
from sklearn.preprocessing import PolynomialFeatures
pf = PolynomialFeatures(degree=3)
transformed_dataset_x = pf.fit_transform(dataset_x.reshape(-1, 1))
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(transformed_dataset_x, dataset_y)
Burada doğrusal regresyondan elde ettiğimiz katsayılar (yani coef_ dizisi) dört elemanlı olmalıdır. Bu 4 eleman aslında polinomun
katsayılarını bize vermektedir. Yani polinom aslında şöyle olmaktadır:
P(x) = lr.coef_[0] + lr.coef_[1] * x + lr.coef_[2] * x ** 2 + lr.coef_[3] * x ** 3
Tabii tahminleme yapmak için x değerlerini yukarıdaki gibi bir polinoma sokmak zahmetlidir. Sütun sayısı fazla olduğunda
polinomun genel biçimi çok daha fazla terim içerecektir. Burada tahminleme için yine aynı yol izlenebilir. Yani tahminlenecek değerler
önce PolynomialFeatures sınıfının transform metoduna sokulur, sonra oradan elde edilen değerler doğrusal regresyonun predict metoduna
sokulabilir. Örneğin:
predict_data = np.array([4, 14, 67])
transformed_predict_data = pf.transform(predict_data.reshape(-1, 1))
predict_result = lr.predict(transformed_predict_data)
print(predict_result)
Aşağıdaki örnekte yukarıda kullanmış olduğumuz noktalardan üçüncü derece bir polinom geçirilmeltedir. "points.csv"
noktaları şöyledir:
2,4
3,5
5,7
7,10
7,8
8,12
9.5,10.5
9,15
10,17
13,18
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('points.csv', delimiter=',')
dataset_x = dataset[:, 0]
dataset_y = dataset[:, 1]
from sklearn.preprocessing import PolynomialFeatures
pf = PolynomialFeatures(degree=3)
transformed_dataset_x = pf.fit_transform(dataset_x.reshape(-1, 1))
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(transformed_dataset_x, dataset_y)
x = np.linspace(0, 110, 1000)
transformed_x = pf.transform(x.reshape(-1, 1))
y = lr.predict(transformed_x)
# y = lr.coef_[0] + lr.coef_[1] * x + lr.coef_[2] * x ** 2 + lr.coef_[3] * x ** 3
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Polynomial Data Inappropriate Linear Regression')
plt.scatter(dataset_x, dataset_y, color='blue')
plt.plot(x, y, color='red')
plt.show()
r2 = lr.score(transformed_dataset_x, dataset_y)
print(f'R^2: {r2}')
predict_data = np.array([4, 14, 67])
transformed_predict_data = pf.transform(predict_data.reshape(-1, 1))
predict_result = lr.predict(transformed_predict_data)
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
Yukarıdaki örnekte polinomn derecesi yükseltikçe daha iyi bir sonucun elde edilmektedir. Ancak bu durum yanıltıcıdır. Veri kümesindeki
sütunların sayısı arttıkça polinomların genel biçimlerindeki terim sayısı da artmaktadır. Bu da doğrusal regresyona aslında daha çok
sütunlu veri kümesinin sokulacağı anlamına gelmektedir. Örneğin iki sütuna sahip üçüncü derece bir polinomun genel biçimi şöyledir:
P(x) = a0 + a1 x1 + a2 x2 + a3 x1 x2 + a4 x1^2 + a5 x2^2 + a6 x1^2 x2 + a7 x2^2 x1 + a8 x1^3 + a9 x2^3
Görüldüğü gibi toplam 10 tane terim vardır. Çok sütunlu veri kümelerinde polinomsal regresyon uygulanırken derece yüksek
tutulursa underfitting ya da overfitting kaçınılmaz olmaktadır. Bu nedenle ikinci derecenin ya da üçüncü derecenin yukarısına
çıkılırken dikkat edilmelidir.
Aşağıdaki örnekte veri kümesindeki sütun sayısının artmasıyla 3'üncü derece polinomun genel biçimindeki terim sayısının ne
kadar hızlı arttığına yönelik bir örnek verilmiştir. Şu değerler elde edilmiştir:
Sütun Sayısı ---> Terim Sayısı
2 --> 10
3 --> 20
4 --> 35
5 --> 56
6 --> 84
7 --> 120
8 --> 165
9 --> 220
10 --> 286
11 --> 364
12 --> 455
13 --> 560
14 --> 680
15 --> 816
16 --> 969
17 --> 1140
18 --> 1330
19 --> 1540
20 --> 1771
21 --> 2024
22 --> 2300
23 --> 2600
24 --> 2925
25 --> 3276
26 --> 3654
27 --> 4060
28 --> 4495
29 --> 4960
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from sklearn.preprocessing import PolynomialFeatures
print('Sütun Sayısı ---> Terim Sayısı')
for i in range(2, 30):
dataset_x = np.random.random((100, i))
dataset_y = np.random.random(100)
pf = PolynomialFeatures(degree=3)
transformed_dataset_x = pf.fit_transform(dataset_x)
print(f'{i} --> {transformed_dataset_x.shape[1]}')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte "Boston Housing Price" veri kümesi üzerinde şu üç model denenmiştir:
1) LinearRegression
2) PolynomialFeatures --> LinearRegresson
3) PolynomialFeatures --> Lasso
Elde edilen sonuçlar şöyle olmuştur:
Mean Absolute Error: 3.578927993774414
R^2 = 0.7665389566180016
Mean Absolute Error: 3.879051685333252
R^2 = 0.7380310070920353
Mean Absolute Error: 2.180447816848755
R^2 = 0.8993317696045933
Bu denemelerden en iyi sonuç önce ikinci derece polinomun Lasso regresyonu ile uygulnaması olmuştur. Burada alpha değeri ile
değişik performanslar elde edilmiştir. İyi bir alpha değeri 0.005 biçimindedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=1234)
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(training_dataset_x, training_dataset_y)
predict_result = lr.predict(test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(predict_result, test_dataset_y)
print(f'Mean Absolute Error: {mae}')
r2 = lr.score(test_dataset_x, test_dataset_y)
print(f'R^2 = {r2}')
print()
from sklearn.preprocessing import PolynomialFeatures
pf = PolynomialFeatures(degree=2)
transformed_training_dataset_x = pf.fit_transform(training_dataset_x)
lr = LinearRegression()
lr.fit(transformed_training_dataset_x , training_dataset_y)
transformed_test_dataset_x = pf.transform(test_dataset_x)
predict_result = lr.predict(transformed_test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(predict_result, test_dataset_y)
print(f'Mean Absolute Error: {mae}')
r2 = lr.score(transformed_test_dataset_x, test_dataset_y)
print(f'R^2 = {r2}')
print()
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import Lasso
ss = StandardScaler()
scaled_transformed_training_dataset_x = ss.fit_transform(transformed_training_dataset_x)
scaled_transformed_test_dataset_x = ss.transform(transformed_test_dataset_x)
lasso = Lasso(alpha=0.005, max_iter=100000)
lasso.fit(scaled_transformed_training_dataset_x , training_dataset_y)
predict_result = lasso.predict(scaled_transformed_test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(predict_result, test_dataset_y)
print(f'Mean Absolute Error: {mae}')
r2 = lasso.score(scaled_transformed_test_dataset_x, test_dataset_y)
print(f'R^2 = {r2}')
print()
#----------------------------------------------------------------------------------------------------------------------------
Makine öğrenmesinin pek çok alanında matematiksel optimizasyon problemlerinin çözülmesi gerekmetedir. Bu optimizasyon problemleri
genellikle "minimizasyon" biçiminde karşımıza çıkmaktadır. Bir fonksiyonu minimize eden değerleri elde etmenin iki yöntemi olabilir:
1) Fonksiyonun türevinin sıfıra eşitlenmesiyle oluşan denklemin sembolik (ya da analitik) düzeyde çözülmesi yöntemiyle
2) Adım adım iyileştirmelerle fonksi,yonu minimize eden değere nümerik analiz yöntemleriyle yaklaşılmasıyla
Makine öğrenmesindeki optimizasyon işlemlerinde daha önce de bahsettiğimiz gibi genel olarak sembolik değil nümerik analiz
yöntemleri kullanılmaktadır. Bu tür nümerik analiz yöntemlerine "gradient descent" ya da "gradient ascent" yöntemler de denilmektedir.
Gradient descent fonksiyonu minimum yapan değerlerin bulunmasına, gradient ascent ise fonksiyonu maksimum yapan değerlerin
bulunması için kullanılan terimlerdir.
Optimizasyon işleminde minimize edilecek fonksiyona "kayıp fonksiyonu (loss function) ya da "maliyet fonksiyonu (cost function)" denilmektedir.
Optimizasyonun amacı bu fonksiyonu iteratif bir biçimde minimize eden değerin bulunmasıdır. Loss (ya da cost fonksiyonu) fonksiyonu
probleme dayalı olarak belirlenmektedir. Örneğin doğrusal regresyonda "gerçek değerlerle kestirilen değerler arasındaki farklara" ilişkin
fonksiyon bir loss fonksiyonudur. Biz doğursal regresyonda bu fonksiyonu minimum yapan değerleri elde etmek isteriz.
Bir fonksiyonu minimum yapan değerlerin elde edilmesi fonksiyonun türevinin sıfıra eşitlenmesi ile sembolik düzeyde yapılabilmektedir.
Ancak yukarıda da belirttiğimiz gibi sembolik düzeydeki işlemler her türlü probleme uygulanamamaktadır. Bu durumda işlemler
iteratif biçimde hedefe yavaş yavaş varılması yoluyla yapılmaktadır.
Makine öğrenmesinde genellikle veri kümemizde birdenfazla sütun vardır. Bu da loss fonksiyonunun çok değişkenli bir fonksiyon olacağı
anlamına gelmemktedir. Çok değişkenli fonksiyonların optimizasyonunda benzer teknik kullanılır. Fonksiyonun her değişkene göre türevi alınır.
Böylece bir grup türev ifadesi elde edilir. Fonksiyonların değişkenlere göre ayrı ayrı türevlerine "parçalı türevler (partial derivation)"
da denilmektedir. Parçalı türevlerin oluşturduğu topluluğa ya da vektöre "gradient vector" denir. Gradient vector matematikte ters üçgenle gösterilmektedir.
Gradient vektör minimum noktaya varmak için bir doğrultu vermektedir. Gradeient descent yöntemde belli bir x değerindne işlem başlatılır.
Sonra gradient vektörden bir değer elde edilir. Bu değer bu x değerindne çıkartılır bu işlem çok defa yapılır. Her defasında minimum noktaya biraz daha
yaklaşılmaktadır.
Bir fonksiyonun birinci türevini sıfır yapan farklı noktalar olabilektedir. Bu noktalara "yerel minimum (local minima)" denilmektedir.
Bu minimum noktalardan biri "global minimum (global minima)" noktasıdır. Yerel minumum noktalar arasından global olanı bulma da gradient descent yöntemlerin
önemli problemlerinden biridir. Burada çeşitli yöntemler kullanılabilmektedir.
Gradient descent yöntemlerde gradient vektör doğrultusunda ilerlerken ilerleme adımları önemli olabilmektedir. Eğer ilerleme yani minimal
noktaya yaklaşma büyük adımlarla yapılırsa hedefin yakınlarına hızlıca erişilir ancak hedef daha düşük bir duyarlılıkla elde edilir.
Eğer ilerleme küçük adımlarla yapılırsa bu durumda hedefin yakınlarına yavaş bir biçimde erişilir. Ancak hedef daha yüksek bir duyarlılıkla elde edilir.
Bu ilerleme adımlarının büyüklüğüne makine öğrenmesinde "öğrenme hızı (learning rate)" denilmektedir. Eğer "learning rate" küçük alınırsa
hedefe yakınsama uzun zaman alır ancak hedef daha duyarlıklı elde edilebilir. Eğer "learning rate" yüksek alınırsa hedefin yakınlarına hızlıca erişilmekte ancak
hedef daha düşük bir duyarlılıkla elde edilmektedir. Learning rate'in standart bir birimi yoktur. Bu birim o andaki probleme özgü bir adım büyüklüğünü
belirtir. Uygulamacı bu değeri yükseltip düşürebilir. Ancak bunun farklı problemlerde ortak birimi yoktur.
Gradient descent yönteminde bir minimum noktanın elde edilmesi kabaca şöyle olmaktadır:
<belli bir x değereinden işleme başlanır>
<loss fonksiyonundan gradient vektör elde edilir>
while True:
error = <x değeri gradient vektöre sokulur>
x = x - error
y_new = loss(x)
if abs(y_new, y_old) < epsilon:
break
y_old = y_new
Aşağıdaki örnekte loss = x^2 - 4 biçiminde bir loss fonksiyonunun gradient descent yöntemle minimize edilmesi örneği verilmiştir.
Buradaki LEARNING_RATE göreli adım büyüklüğünü ayarlamkta kullanılmaktadır. EPSILON değeri ise hedefe hangi fuyarlılıkla yaklaşılacağını belirtmektedir.
Tabii burada fonksiyon çok değişkenli ise (yani veri kümesi birden fazla sütundan oluşyorsa) x = x - error işlemi her değişken için yaoılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
# loss = x^2 - 4
# gradient vect = [2x]
import numpy as np
EPSILON = 0.000000000001
LEARNING_RATE = 0.00001
def loss(x):
return x ** 2 - 4
x = -10
y_old = loss(x)
while True:
error = (2 * x) * LEARNING_RATE
x -= error
y_new = loss(x)
if np.abs(y_new - y_old) < EPSILON:
break
y_old = y_new
print(x, loss(x))
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de doğursal regresyonu gradeint descent yöntemle çözmeye çalışalım. Önce en küçüklemeye çalıştığımız loss fonksiyonun her değişken için
türevlerini alarak gradient vektörü elde ederiz. Sonra da adım adım hedefe varmaya çalışırız. Aşağıda basit doğrusal regresyon için bu işlemlerin
nasıl yapıldığı gösterilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('points.csv', dtype=np.float32, delimiter=',')
dataset_x = dataset[:, 0]
dataset_y = dataset[:, 1]
def loss(ypred, y):
return np.sum(np.abs(y - ypred)) / len(ypred)
def linear_regression_gradient(x, y, *, epsilon, learning_rate):
N = len(x)
b0 = 0
b1 = 0
prev_loss = 0
while True:
ypred = b0 + b1 * x
df_b1 = (-2 / N) * np.sum(x * (y - ypred))
df_b0 = (-2 / N) * np.sum(y - ypred)
b0 = b0 - df_b0 * learning_rate
b1 = b1 - df_b1 * learning_rate
next_loss = loss(y, ypred)
if np.abs(prev_loss - next_loss) < epsilon:
break
prev_loss = next_loss
return b0, b1
b0, b1 = linear_regression_gradient(dataset_x, dataset_y, epsilon=0.000000001, learning_rate=0.001)
x = np.linspace(1, 15, 100)
y = b0 + b1 * x
import matplotlib.pyplot as plt
plt.title('Linear Regression with Gradient Descent')
figure = plt.gcf()
figure.set_size_inches((10, 8))
plt.xlabel('x')
plt.ylabel('y')
plt.scatter(dataset[:, 0], dataset[:, 1], color='blue')
plt.plot(x, y, color='red')
plt.show()
print(f'Slope = {b1}, Intercept={b0}')
#----------------------------------------------------------------------------------------------------------------------------
Gradient descent optimizasyon işleminde birbirine benzeyen üç terim kullanılmaktadır: Batch Gradient Descent, Mini Batch Gradient Descent,
Stochastic Gradient Descent.
Batch Gredient Descent yönteminde her iterasyonda eğitim veri kümesinin tamamı işleme sokulup ilerleme bu veri kümesinin tamamı kullanlıarak
yapılmaktadır. Eğer veri kümesi parçalara ayrılıp parça parça işleme sokularak yileştirme yapılıyorsa buna "Mini Batch Gradient Descent" denilmektedir.
Eğer veri kümesindne her defasında rastgele bir satır (nokta) seçilerek ilerleme yapılıyorsa buna da "Stochastic Gradient Descent" denimektedir.
En çok "Stochastic Gradient Descent" yöntemi tercih edilmektedir. Örneğin eğitim veri kümemizde 100 satır olsun. Biz bu 100 satırı tek hamlede
gradient vektöre sokup 100 değer elde edip bu 100 değerden elde ettiğimiz toplam sonuç ile ilerlemeyi yaparsak "Batch Gradient Descent" uygulamış oluruz.
Eğer biz bu 100 satırlık eğitim veri kümesini örneğin 10'luk 10 parçaya ayırıp bu 10'luk parçaları işleme sokup 10 defa ilerleme yaparsak (tabii işlemler de
toplamda çok kez yapılacaktır) "Mini Batch Gradient Descent" uygulamış oluruz. Eğer biz 100 satırlık veri kümesinden rastgele bir satır seçip ilerlemeyi
tek tek yaparsak "Stochastic Gradient Descent" uygulamış oluruz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Doğrusal regresyon (en küçük kareler, Lasso, Ridge, ElasticNet) ve Polinomsal regresyon sınıflandırma biçiminde olmayan
(yani lojistik olmayan) regresyon problemlerinde kullanılabilmektedir. İstatistikte çok uzun süredir bilinen ve sınıflandırma tarzı
problemlerde kullanılan ismine "lojistik regresyon" denilen bir yöntem de vardır. Biz "lojistik regresyon" terimini kursumuzda
genel olarak sınıflandırma problemlerini anlatmak için de kullandık. Burada "istatistiksel lojistik regresyon" demekle istatistikte uzun süredir kullanılan
klasik sınıflandırma tarzı regresyon işlemlerini kastediyoruz.
İstatistikte "lojistik regresyon" denildiğinde default olarak iki sınıflı sınıflandırma işlemleri anlaşılmaktadır. Çok sınıflı
sınıflandırmalar için "multinomial logistic rgeression regresyon" ya da "multiclass logistic regression" terimleri kullanılmaktadır.
İstatistikte "lojistik regresyona" "logit regresyonu" ya da kısaca "logit" de denilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
İstatistikte iki sınıflı lojistik regresyon modeli kabaca şöyle işletilmektedir: Önce x değerleri ve buna karşı gelen sınıfları belirten
0 ve 1'lerden oluşan Y değerleri dikkate alınarak bir regresyon doğrusu oluşturulur. Ancak bu regresyon doğrusu doğrudan bir işe yaramamaktadır.
Çünkü oluşturulan bu doğruda x değerlerine karşı gelen Y değerleri birer sınıf belirtmez birer gerçek değer belirtir. Üstelik
oluşturulan bu regresyon doğrusu x'ler Y değerlerini 0 ve 1 olarak vermemktedir. [-sonsuz, +sonsuz] aralıkta vermektedir. İşte bu noktada
"sigmoid fonksiyonu" devreye girmektedir. Oluşturulan regresyon doğrusunda x değerine karşı gelen Y değeri sigmoid fonksiyonuna sokulduğunda
sigmoid fonksiyonu bu değeri [0, 1] aralığına hapsetmektedir. Doğrusal regresyondan elde edilen Y değerinin sigmoid fonksiyonuna sokulmasıyla elde edilen
değer aslında ilgili x değerinin 0 ya da 1 olma olasılığını belirtit duruma gelmektedir. Bu değer ne kadar 1'e yakonsa o x değerinin 1 olma olasılığı
o kadar yüksektir. Benzer biçimde bu değer ne kadar 0'a yakınsa o x değerinin 0 olma olasılığı o kadar yüksektir. Biz kestirim yaparken
bu sigmoid fonksiyonundan elde edilen değerin 0.5'ten büyük olup olmadığına bakabiliriz.
Sigmoid fonksiyonun tanım kümesinin [-sonsuz, +sonsuz] olduğunu değer kümesinin ise (0, 1) olduğunu anımsayınız. x = 0 için sigmoid
değeri 0.5'tir. Sigmodi fonksiyonu S harfi biçimindedir.
İstatistiksel lojistik regresyonda regresyon doğrusu "en küçük kareler, lasso, ridge, elastic net" gibi yöntemlerle oluşturulmamaktadır.
İsmine "maximum likelihood" denilen bir yöntemle oluşturulmaktadır. Makine öğrenmesinde ise bu regresyon doğrusu nümerik biçimde
"gradient descent" yöntemlerle oluşturulmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Makine öğrenmesinde N boyutlu uzaydaki noktaların bir hyperplane ile ayrıştırılmasına "doğrusal olarak ayrıştırma (linearly seperation)"
denilmektedir. Örneğin biz x1 ve x2 özelliklerine sahip bir kümesindeki noktaları bir doğru ile ayrıştırmaya çalışabiliriz.
Bu tür ayrıştırıcılara "doğrusal olarak ayrıştırıcılar (linear classifier)" denilmektedir. Lojistik regresyon aslında noktaların
n boyutlu uzayda bir hyperplane ile ayrıştırılması anlamına gelmektedir. Yani biz lojistik regresyonda aslında bir regresyon doğrusu
oluşturup bunu sigmoid fonksiyonuna soktuğumuzda ve 0.5'i karar noktası (decision boundary) seçtiğimizde o noktaları bir
hyperplane ile ayrıştırmış olmaktayız. Tabii noktalar eğer bir hyperplane ile ayrıştırılamayacak biçimdeyse burada lojistik regresyon
iyi çalışmayacak demektir.
İki sınıfa ilişkin bir beri kümesi elimizde olsun. Eğer bir veri kümesi bir doğru ile (genel olarak bir hyperplane ile) ayrıştırılabiliyorsa
bu veri kümesine "doğrusal olarak ayrıştırılabilir (linearly seperable)" veri kümesi denilmektedir. İşte bizin veri kümemiz ne kadar
doğrusal olarak ayrıştırılabilirse lojistik regresyon o kadar iyi sonuç verecektir. Öte yandan yapay sinir ağları aslında
doğrusal olmayan bir fonksiyon le ayrıştırma sağlamaktadır. Dolayısıyla veri kümesi doğrusal olarak ayrıştırılabilir değilse yapay sinir ağları
lojistik regresyona göre çok daha iyi bir sonuç vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Lojistik regresyonun bir çeşit doğrusal olarak ayrıştıran mekanizma olduğu basit biçimde ispatlanabilir. Eğer lojistik regresyon
için elde edilen doğru denklemi sigmoid fonksiyonunda yerine konursa şöyel bir durum oluşacaktır:
p(x) = 1 / (1 + e ^ -f(x))
Burada f(x) regresyon doğrusunu belirtiyor olsun. Bir olayın olma olasılığının olmama olasılığına oranına İngilizce "odds ratio" denilmektedir.
Odds Ratio bahislerde çokça kullanılan bir kavramdır). Bu odds ratio kavramını yukarıdaki sigmoid fonksiyonuna uygularsak şöyle bir sonuç elde ederiz:
p(x) / ( 1- p(x)) = e ^ f(x)
Sırada iki tarafın logaritması alınırsa şu durum elde edilecektir:
log (p(x) / (1 - p(x))) = f(x)
Eşitliğin sol tarafına "odds ratio" değerinin logaritması anlamında "logit" denilmektedir. Buradan lojistik regresyonun aslında
bir doğru ile ayrıştırma özelliği olduğu hemen anlaşılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Makine öğrenmesinde lojistik regresyonun gerçekleştirilmesi genellikle tersten yapılmaktadır. Yani biz bir doğru denklemi
elde etmeye çalışırız. Ancak bu doğru denklemini şöyle bir optimizasyon probleminin çözümüyle elde ederiz: "Biz öyle bir doğru denklemi elde etmeliyiz
ki x noktalarını bu doğru denklemine soktuğumuzda elde ettiğimiz değerlerinin sigmoid fonksiyonuna sokulmasıyla elde edilen çıktıları ile
bu değerlerin belirttiği sınıflar arasındaki fark minimum olsun". Örneğin bizim elde etmeye çalıştığımız doğrusal fonksiyon f(x) olsun. x değerlerinin bu
fonksiyona sokulmasıyla bir grup değer elde etmiş olalım. Sonra bu değerleri sigmoid fonksiyonuna sokalım. Sonra da sigmoid fonksiyonun çıktılarının
bu noktaların gerçek sınıfları arasındaki farkları bulup onu minimize etmeye çalışırız:
minimize edilecek fonksiyon: (sigmoid(f(x)) - Y)^2
Tabii bu yöntem aslında "maximum likelihood" denilen yöntemle aynı kapıya çıkmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda bir iki özelliğe sahip bir grup noktanın yukarıda anlatılan "gradient descent" lojistik regresyon yöntemiyle çzümüne bir örnek verilmiştir.
Problemde kullanılan "logistic-points.csv" dosyasının içeriği şöyledir:
x1,x2,class
-0.017612,14.053064,0
-1.395634,4.662541,1
-0.752157,6.538620,0
-1.322371,7.152853,0
0.423363,11.054677,0
0.406704,7.067335,1
0.667394,12.741452,0
-2.460150,6.866805,1
0.569411,9.548755,0
-0.026632,10.427743,0
0.850433,6.920334,1
1.347183,13.175500,0
1.176813,3.167020,1
-1.781871,9.097953,0
-0.566606,5.749003,1
0.931635,1.589505,1
-0.024205,6.151823,1
-0.036453,2.690988,1
-0.196949,0.444165,1
1.014459,5.754399,1
1.985298,3.230619,1
-1.693453,-0.557540,1
-0.576525,11.778922,0
-0.346811,-1.678730,1
-2.124484,2.672471,1
1.217916,9.597015,0
-0.733928,9.098687,0
-3.642001,-1.618087,1
0.315985,3.523953,1
1.416614,9.619232,0
-0.386323,3.989286,1
0.556921,8.294984,1
1.224863,11.587360,0
-1.347803,-2.406051,1
1.196604,4.951851,1
0.275221,9.543647,0
0.470575,9.332488,0
-1.889567,9.542662,0
-1.527893,12.150579,0
-1.185247,11.309318,0
-0.445678,3.297303,1
1.042222,6.105155,1
-0.618787,10.320986,0
1.152083,0.548467,1
0.828534,2.676045,1
-1.237728,10.549033,0
-0.683565,-2.166125,1
0.229456,5.921938,1
-0.959885,11.555336,0
0.492911,10.993324,0
0.184992,8.721488,0
-0.355715,10.325976,0
-0.397822,8.058397,0
0.824839,13.730343,0
1.507278,5.027866,1
0.099671,6.835839,1
-0.344008,10.717485,0
1.785928,7.718645,1
-0.918801,11.560217,0
-0.364009,4.747300,1
-0.841722,4.119083,1
0.490426,1.960539,1
-0.007194,9.075792,0
0.356107,12.447863,0
0.342578,12.281162,0
-0.810823,-1.466018,1
2.530777,6.476801,1
1.296683,11.607559,0
0.475487, 12.040035,0
-0.783277,11.009725,0
0.074798,11.023650,0
-1.337472,0.468339,1
-0.102781,13.763651,0
-0.147324,2.874846,1
0.518389,9.887035,0
1.015399,7.571882,0
-1.658086,-0.027255,1
1.319944,2.171228,1
2.056216,5.019981,1
-0.851633,4.375691,1
-1.510047,6.061992,0
-1.076637,-3.181888,1
1.821096,10.283990,0
3.010150,8.401766,1
-1.099458,1.688274,1
-0.834872,-1.733869,1
-0.846637,3.849075,1
1.400102,12.628781,0
1.752842,5.468166,1
0.078557,0.059736,1
0.089392,-0.715300,1
1.825662,12.693808,0
0.197445,9.744638,0
0.126117,0.922311,1
-0.679797,1.220530,1
0.677983,2.556666,1
0.761349,10.693862,0
-2.168791,0.143632,1
1.388610,9.341997,0
0.317029,14.739025,0
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import pandas as pd
df = pd.read_csv('logistic-points.csv')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
dataset_x = np.append(dataset_x, np.ones((dataset_x.shape[0], 1)), axis=1)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Points for Logistic Regression')
plt.scatter(dataset_x[dataset_y == 0, 0], dataset_x[dataset_y == 0, 1], color='blue', marker='o')
plt.scatter(dataset_x[dataset_y == 1, 0], dataset_x[dataset_y == 1, 1], color='green', marker='^')
plt.xlabel('x1')
plt.ylabel('x2')
plt.legend(['class 0', 'class 1'])
plt.show()
def sigmoid(x):
return 1.0 / (1 + np.exp(-x))
def gradient_descent_logistic(dataset_x, dataset_y, learning_rate=0.001, epoch=50000):
weights = np.ones((dataset_x.shape[1], 1))
for k in range(epoch):
h = sigmoid(np.matmul(dataset_x, weights))
error = dataset_y - h
weights = weights + learning_rate * np.matmul(dataset_x.transpose(), error)
return weights
weights = gradient_descent_logistic(dataset_x, dataset_y.reshape(-1, 1))
x1 = np.linspace(-5, 5, 1000)
x2 = (-weights[2] - weights[0] * x1) / weights[1]
plt.figure(figsize=(10, 8))
plt.title('Points for Logistic Regression')
plt.scatter(dataset_x[dataset_y == 0, 0], dataset_x[dataset_y == 0, 1], color='blue', marker='o')
plt.scatter(dataset_x[dataset_y == 1, 0], dataset_x[dataset_y == 1, 1], color='green', marker='^')
plt.plot(x1, x2, color='red')
plt.xlabel('x1')
plt.ylabel('x2')
plt.legend(['class 0', 'class 1', 'regression line'])
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aslında yukarıdaki gradient descent yöntemle lojistik regresyon işlemini yapan scikit-learn içerisinde sklearn.linear_model içerisinde
hazır bir LogisticRegression sınıfı vardır. Sınıfın __init__ metodunun parametrik yapısı şöyledir:
class sklearn.linear_model.LogisticRegression(penalty='l2', *, dual=False, tol=0.0001, C=1.0, fit_intercept=True, intercept_scaling=1,
class_weight=None, random_state=None, solver='lbfgs', max_iter=100, multi_class='auto', verbose=0, warm_start=False, n_jobs=None, l1_ratio=None)
Metodun birinci parametresi doğru denkleminin elde edilmesinde kullanılacak olan regresyon yöntemini belirtmektedir. Bu parametre None geçilirse
en küçük kareler yöntemi, 'l1' geçilirse Lasso regresyonu, 'l2' geçilirde Ridge regresyonu ve 'elasticnet' geçilirse Elastic Net regresyonu
uygulanmaktadır. Aslında bu parametrelerin hepsi default değerlerle geçilebilir.
LogisticRegression nesnesi yaratıldıktan sonra yine fit işlemi yapılır. Tüm lojistik regresyon işlemleri bu fit metodunda yapılmaktadır.
Sınıfın transform isimli bir metodu yoktur. fit işleminden sonra artık predict işlemi ile kestirim yapılabilir. predict metodu bize doğrudan sınıf
numaralarını vermektedir. Sınıfın predict_proba isimli metodu bize her noktanın sınıfsal olasıklarını vermektedir. Bu metodun verdiği matrisin
satırlarının sütun toplamları 1 olur. Sınıfın score metodu bizden test verileri için x ve Y değerlerini alır. Bu x değerleri için Y değerlerini tahmin ederek
gerçek Y değerleriyle oranını hesaplar ve bize "accuracy" skorunu verir.
Sınıfın coef_ ve intercept_ örnek öznitelikleri bize ayrıştırmayı yapan doğru denklemini vermektedir. Buradaki doğru denklemi, her zaman
B0 + B1 x1 + B2 x2 + ... + Bn xn = 0 biçiminde verilmektedir. Dolayısıyla buradan hareketle doğru çizerken uygun dönüştürmeyi yapmak gerekir.
Normal olarak lojistik regresyon işlemlerinde özellik ölçeklemesi yapmaya gerek yoktur. Ancak LogisticRegression sınıfının penalty parametresi
"l1", "l2" elasticnet" seçildiğinde resgülasyon uygulandığı için özellik ölçeklemesi gerekebilmektedir. Eskiden bu parametre None biçimindeydi.
None değeri "en küçük kareler" yönteminin uygulanacağı anlamına geliyordu. Ancak bu parametrenin default değeri daha sonra "l1" haline getirilmiştir.
Ancak ne olursa olsun özellik ölçeklemesi LogisticRegression sınıfında hiçbir zaman önemli bir etkiye yol açmamaktadır.
Aşağıda daha önce yaptığımız lojistik regresyonun scikit-learn LogisticRegression sınıfı ile gerçekeltirilmesi örneği verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import pandas as pd
df = pd.read_csv('logistic-points.csv')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
dataset_x = np.append(dataset_x, np.ones((dataset_x.shape[0], 1)), axis=1)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 8))
plt.title('Points for Logistic Regression')
plt.scatter(dataset_x[dataset_y == 0, 0], dataset_x[dataset_y == 0, 1], color='blue', marker='o')
plt.scatter(dataset_x[dataset_y == 1, 0], dataset_x[dataset_y == 1, 1], color='green', marker='^')
plt.xlabel('x1')
plt.ylabel('x2')
plt.legend(['class 0', 'class 1'])
plt.show()
def sigmoid(x):
return 1.0 / (1 + np.exp(-x))
def gradient_descent_logistic(dataset_x, dataset_y, learning_rate=0.001, epoch=50000):
weights = np.ones((dataset_x.shape[1], 1))
for k in range(epoch):
h = sigmoid(np.matmul(dataset_x, weights))
error = dataset_y - h
weights = weights + learning_rate * np.matmul(dataset_x.transpose(), error)
return weights
weights = gradient_descent_logistic(dataset_x, dataset_y.reshape(-1, 1))
x1 = np.linspace(-5, 5, 1000)
x2 = (-weights[2] - weights[0] * x1) / weights[1]
plt.figure(figsize=(10, 8))
plt.title('Points for Logistic Regression')
plt.scatter(dataset_x[dataset_y == 0, 0], dataset_x[dataset_y == 0, 1], color='blue', marker='o')
plt.scatter(dataset_x[dataset_y == 1, 0], dataset_x[dataset_y == 1, 1], color='green', marker='^')
plt.plot(x1, x2, color='red')
plt.xlabel('x1')
plt.ylabel('x2')
plt.legend(['class 0', 'class 1', 'regression line'])
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi istatistiksel lojistik regresyon ile yapay sinir ağlarıyla uygulanan lojistik regresyon ve naive bayes yöntemiyle
gerçekleştirilen lojistik regresyon arasındaki farklılıklar nelerdir? Yani bunların hangisi hangi durumlarda kullanılmalıdır.
Bu konuda şunlar söylenebilir:
- Çeşitli yöntemlerin hangisinin daha iyi sonuç vereceğini verilerin dağılımını bilmeden öngörmek çok zor hatta imkansızdır.
Dolayısyla uygulamacının çeşitli yöntemleri deneyip kendi veri kümesi için en iyi olanı tercih etmesi tavsiye edilmektedir.
Zaten "automated araçlar" aslında bunu yapmaktadır.
- İstatistiksel lojistik regresyon yukarıda da belirtildiği gibi "doğrusal olarak arıştırılabien (linearly seperable)"
veri kümeleri için çok uygun bir yöntemdir. Veri kümesindeki noktalar bir doğru ile ayrıştırılabilir olmaktan çıktığında bu yöntemin
performansı düşmeye başlamaktadır.
- Yapay sinir ağları için "doğrusal olarak ayrıştırılabilirlik" biçiminde bir koşul yoktur. Yapay sinir ağları doğursal olarak
ayrıştırılabilir olmayan veri kümelerinde de kullanılabilmektedir.
- Az sayıda veri olduğunda ve bunlar doğrusal olarak ayrıştırılabilir durumdaysa lojistik regresyon yapay sinir ağlarına tercih edilir.
Yapay sinir ağlarının az veriyle eğitilmesi problemlidir.
- Naive Bayes yöntemi ön koşulları olan bir yöntemdir. Sütunların çoğunun kategorik olduğu durumlarda diğerlerine göre daha iyi sonuç verme
eğilimindedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki çörnekte "Breast Cancer" veri kümesi üzerinde LogisticRegression, GaussianNB ve Yapay Sinir Ağları yüntemleri uygulanmıştır.
Bu veri kümesinde LogisticRegression ve yapay sinir ağları aynı sonucu vermiştir. Ancak Naive Bayes daha düşük bir sonuç vermiştir.
Üç yöntemden elde edilen değerler şunlardır:
LogisticRegression accuracy score: 0.9649122807017544
GaussianNB accuracy score: 0.9210526315789473
NeuralNet accuracy score: 0.9649122807017544
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import pandas as pd
df = pd.read_csv("data.csv")
dataset_x = df.iloc[:, 2:-1].to_numpy()
dataset_y = np.zeros(len(df))
dataset_y[df['diagnosis'] == 'M'] = 1
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(training_dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
scaled_test_dataset_x = ss.transform(test_dataset_x)
# LogisticRegression Solution
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(max_iter=1000000)
lr.fit(scaled_training_dataset_x, training_dataset_y)
predict_result = lr.predict(scaled_test_dataset_x)
from sklearn.metrics import accuracy_score
score = accuracy_score(test_dataset_y, predict_result)
print(f'LogisticRegression accuracy score: {score}')
# Naive Bayes Solution
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
gnb.fit(training_dataset_x, training_dataset_y)
predict_result = gnb.predict(test_dataset_x)
score = accuracy_score(predict_result, test_dataset_y)
print(f'GaussianNB accuracy score: {score}')
# Neural Net Solution
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='BreastCancer')
model.add(Dense(64, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=200, verbose=0)
predict_result = (model.predict(scaled_test_dataset_x) > 0.5).astype(int)
score = accuracy_score(predict_result, test_dataset_y)
print(f'NeuralNet accuracy score: {score}')
#----------------------------------------------------------------------------------------------------------------------------
Biz yukarıda iki sınıflı (binary) lojistik regresyon problemlerine örnekler verdik. Çok sınıflı (multinomial/multiclass)
lojistik regresyon problemleri de aslında genel yapı itibari ile iki sınıflı lojistik regresyon problemlerine benzemektedir.
Burada sigmoid fonksiyonu yerine softmax fonksiyonukullanılmaktadır. Böylece regresyon için tek bir doğru değil birden fazla
doğru elde edilmektedir. Lojistik regresyon uygularken Y verileri üzerinde "one hot encoding" uygulanmaz. Y verileri LabelEncoding
yapılır. Yani sınıflar 0, 1, 2, 3, ... n biçiminde temsil edilir.
Aşağıda zambak veri kümesi ("iris.csv") üzerinde çok sınıflı lojistik regresyon işlemi uygulanmıştır. Zambak verilerinin üç sınıfa
ayrıldığını anımsayınız. Bu örnekte ayrıca zambak verileri üzerinde yapay sinir ağı modeli de uygulanmıştır. İki yöntemden de aynı sonuçlar
elde edilmiştir. Bunun nedeni şüphesiz kümlerin birbirlerinden oldukça ayrık durumda lmasındandır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('iris.csv')
dataset_x = df.iloc[:, 1:-1].to_numpy()
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
dataset_y = le.fit_transform(df.iloc[:, -1])
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
# LogisticRegression Solution
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(training_dataset_x, training_dataset_y)
predict_result = lr.predict(test_dataset_x)
from sklearn.metrics import accuracy_score
score = accuracy_score(test_dataset_y, predict_result)
print(f'Multinomial LogisticRegression score: {score}')
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
scaled_training_dataset_x = mms.fit_transform(training_dataset_x)
scaled_test_dataset_x = mms.fit_transform(test_dataset_x)
# Neural Net Solution
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse=False)
ohe_training_dataset_y = ohe.fit_transform(training_dataset_y.reshape(-1, 1))
ohe_test_dataset_y = ohe.fit_transform(test_dataset_y.reshape(-1, 1))
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='Iris')
model.add(Dense(64, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(3, activation='softmax', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(scaled_training_dataset_x, ohe_training_dataset_y, batch_size=32, epochs=200, verbose=0)
predict_result_softmax = model.predict(scaled_test_dataset_x)
import numpy as np
predict_result = np.argmax(predict_result_softmax, axis=1)
score = accuracy_score(test_dataset_y, predict_result)
print(f'Neural Net score: {score}')
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de MNIST örneğini istatistiksel multinomial lojistik regresyonla çözmeye çalışalım. MNIST aslında istatistiksel lojistik
regresyona uygun bir veri kümesi değildir. Çünkü veri kümesinde 784 tane sütun vardır. Bu biçimdeki lojistik regresyonun
başarılı olması çok zordur. Buradan accuracy değerleri şöyle elde edilmiştir:
LogisticRegresson accuracy score: 0.9203
Simple Neural Net accuracy score: 0.9768
Görüldüğü gibi resim tanıma gibi işlemlerde yapay sinir ağları alternatif yöntemlere göre çok daha iyi sonuç vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df_training = pd.read_csv('mnist_train.csv')
df_test = pd.read_csv('mnist_test.csv')
training_dataset_x = df_training.iloc[:, 1:].to_numpy()
training_dataset_y = df_training.iloc[:, 0].to_numpy()
test_dataset_x = df_test.iloc[:, 1:].to_numpy()
test_dataset_y = df_test.iloc[:, 0].to_numpy()
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(max_iter=1000)
lr.fit(training_dataset_x, training_dataset_y)
predict_result = lr.predict(test_dataset_x)
from sklearn.metrics import accuracy_score
score = accuracy_score(test_dataset_y, predict_result)
print(f'LogisticRegresson accuracy score: {score}')
from tensorflow.keras.utils import to_categorical
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
training_dataset_x = training_dataset_x / 255
test_dataset_x = test_dataset_x / 255
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='MNIST')
model.add(Dense(256, activation='relu', input_dim=784, name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(training_dataset_x, ohe_training_dataset_y, epochs=20, batch_size=32)
import numpy as np
softmax_predict_result = model.predict(test_dataset_x)
predict_result = np.argmax(softmax_predict_result, axis=1)
score = accuracy_score(test_dataset_y, predict_result)
print(f'Simple Neural Net accuracy score: {score}')
#----------------------------------------------------------------------------------------------------------------------------
İki sınıflı lojistik regresyonda biz sınıfları ayırmak için bir tane hyperplane yeterlidir. Ancak sınıf sayısı ikiden
fazla olduğunda sınıf sayısı kadar hyperplane gerekmektedir. Örneğin üç sınıflı lojistik regresyonda aslında üç ayrı hyperplane elde edilir.
Her hyperplane bir kümeyi ayrıştırmaktadır. Dolayısıyla predict işlemi sırasında kestirimi yapılacak nokta bu üç hyperplane'e sokulup
elde edilen değerler softmax işlemine sokulmaktadır. Grafiksel olarak aslında her hyperplane bir sınıfı diğerlerinden ayırmaktadır.
Tabii buradaki "ayırma" mutlak anlamda grafiksel bir ayırma değildir. İlgili doğru denkleminden elde edilen softmax değerinin daha yüksek
olması anlamına gelmektedir.
Çok sınıflı lojistik regresyonlardaki elde edilen hyperplane'lerin anlamını gözle görebilmek için iki özelliğe sahip
çok sınıflı örnekler verilebilir. Aşağıdaki örnekte üç tane sınıfa sahip bir veri kğmesi make_blobs fonksiyonu ile luşturulmuştur.
Sonra da istatistiksel lojistik regresyon uygulanıp elde edilen üç regresyon doğrusu çizdirilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from sklearn.datasets import make_blobs
dataset_x, dataset_y = make_blobs(n_features=2, centers=3, cluster_std=0.5, random_state=100)
colors = ['red', 'green', 'blue']
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 8))
for i in range(3):
plt.scatter(dataset_x[dataset_y == i, 0], dataset_x[dataset_y == i, 1], color=colors[i])
plt.show()
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(dataset_x, dataset_y)
x = np.linspace(-15, 15, 300)
plt.figure(figsize=(12, 8))
plt.xlim(-20, 20)
plt.ylim(-20, 20)
for i in range(len(lr.coef_)):
plt.scatter(dataset_x[dataset_y == i, 0], dataset_x[dataset_y == i, 1], color=colors[i])
y = (-lr.intercept_[i] - lr.coef_[i, 0] * x) / lr.coef_[i, 1]
plt.plot(x, y, color=colors[i])
plt.show()
print(lr.score(dataset_x, dataset_y))
#----------------------------------------------------------------------------------------------------------------------------
Destek Vektör Makineleri (Support Vector Machine - SVM) son yıllarda çok popüler olan bir makine öğrenmesi yöntemidir. Bu yöntem
temel olarak sınıflandırma problemleri için kullanılıyro olsa da lojistik olmayan regresyon problemleri için de kullanılabilmektedir.
Aslında temel olarak ele alındığında SVM'ler istatistiksel lojistik regresyonlara benzemektedir. İstatistiksel lojistik regresyonda
gerçek sınıflarla regresyon doğrusundan elde edilen sınıflar arasındaki farklar minimize edilmeye çalışılmaktadır. Oysa SVM'lerin
dayandığı fikir daha farklıdır. SVM'lerde yine hyperplane elde edilmeye çalışılır. Ancak bu hyperplane kendisine en yakın farklı sınıflardaki
noktalar arasındaki toplam uzaklığı en küçüklemek amacıyla olşturulmaktadır. Burada noktanın SVM heyperplane'ine uzaklığı için "dikme uzaklığı"
kullanlmaktadır. İk kümeyi ayırma iddiasında olan pek çok hyperplane çizilebilir. Ancak destek vektör makinelerinde kendisine en yakın iki noktanın
uzaklıkları toplamına bakılmaktadır. Bir doğrunun iki sınıftan da (çok sınıf da söz konusu olabilir) kendisine en yakın noktalarına
"destek vektörleri (support vectors)" denilmektedir. Amaç destek vektörlerinin hyperplane'e toplam uzaklıklarını maksimize etmektir.
Terminolojide en yakın noktaların hyperplane'a uzaklıkları toplamına "marjin (margin)" denilmektedir. Amaç bu marjinin en yüksek olduğu
hyperplane'nin elde edilmesidir.
Noktalar doğrusal olarak ayrıştırılabilir ise istatistiksel lojistik regresyonla destek vektör makineleri benzer sonuçları verme
eğilimindedir. Ancak bu durumda da destek vektör makinelerinin daha adil bir hyperplane oluşturduğu söylenebilir. Yani destek vektör makineleri
bu durumda da sınıfları birbirinden daha iyi ayırabilmektedir. Her ne kadar bunun mevcut noktalarda performansa bir etkisi olmaa da kestirim sırasında
kestirime etkisi olabilmektedir.
Aslında SVM'lerin popüler olmasının en önemli nedeni bu yöntemin "kernel trick" denilen bir transformasyonla doğrusal olarak
ayrıştırılabilir olmayan veri kümelelerine de uygulanabilmesidir. Kernel trick özellik yükseltmesi yaparak noktaları doğrusal olarak ayrıştırılabilir
hale getirmektedir. Bu işlemin nasıl yapıldığının matematiksel açıklaması biraz ağırdır. Biz burada bunun nasıl yapıldığı üzerinde durmayacağız.
İstatistiksel lojistik regresyonda böyle bir "kernel trick" yapılamamaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Destek vektö makineleri için scikit-learn kütüphanesinde sklearn.svm modülü içerisinde SVM isimli bir sınıf bulundurulmuştur.
Sınıfın __init__ metodunun parametrik yapısı şöyledir:
class sklearn.svm.SVC(*, C=1.0, kernel='rbf', degree=3, gamma='scale', coef0=0.0, shrinking=True, probability=False,
tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape='ovr', break_ties=False, random_state=None
Buradaki en önemli, parametre "kernel" parametresidir. Değişik veri kümeleri için değişik kernel trick yöntemleri uygulanabilmektedir.
Scikit-learan şu kernel trick yöntemlerini desteklemektedir: "linear", "poly", "rbf", "sigmoid", "precomputed". Bu parametrenin default değeri
"rbf" biçimindedir. "rbf" kernel doğrusal olarak ayrıştırılamayan veri kümelerini de kapsayan genel bir kernel'dır. "linear"
kernel istatistik lojistik regresyonda olduu gibi kernel trick uygulamadan ayrıştırma yapar. "poly" kernel polinomsal transformasyon
yapmaktadır. Başka bir deyişle "poly" kernel bir polinomla ayrıştırma yapmaya çalışmaktadır. "rbf" kernel'a "radial kernel" da denilmektedir.
Nesne yaratıldıktan sonra diğer scikit-learn sınıflarında olduğu gibi fit işlemi yapılır. fit işleminden sonra doğurdan predict işlemi
yapılabilir. Yine sınıfın predict_proba isimli metodu bize noktaların sınıflar içerisine düşme olasılıklarını vermektedir.
score metodu önce predict işlemi yapıp sonra accuracy değerini hesaplamaktadır.
Sınıfın intercept_ ve coef_ örnek öznitelikleri yine doğru denkleminin katsayılarını bize vermektedir. Burada bize verilen katsayılar
b0 + b1x1 + b2x2 + ... + bnxn = 0 denkleminin katsayılarıdır. Burada verilen değerler yalnızca "linear" kernel
için geçerlidir. Sınıfın support_ ve support_vectors_ isimli örnek öznitelikleri sırasıyla destek vektörlerinin indislerini ve
değerlerini vermektedir.
Aşağıdaki örnekte daha önce kullanmış olduğumuz "logistic-points.csv" dosyasındaki noktalarla "linear" kullanarak SVC işlemi
ve LogisticRegression işlemi yapılıp elde edilen regresyon doğruları çizilmiştir. Sonra da accuracy skorları elde hesaplanmıştır.
Bu örnekte skor olarak şunlar elde edilmiştir:
SVC score: 0.96
Logistic score: 0.95
Görüldüğü gibi destek vektör makineleri %1 oranında daha iyi sonuç vermiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('logistic-points.csv', delimiter=',', skiprows=1)
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.svm import SVC
svc = SVC(kernel='linear')
svc.fit(dataset_x, dataset_y)
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(dataset_x, dataset_y)
x = np.linspace(-4, 4, 1000)
y_svc = -(svc.intercept_ + svc.coef_[0, 0] * x) / svc.coef_[0, 1]
y_logistic = -(lr.intercept_ + lr.coef_[0, 0] * x) / lr.coef_[0, 1]
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 10))
plt.scatter(dataset_x[dataset_y == 0, 0], dataset_x[dataset_y == 0, 1], marker='o')
plt.scatter(dataset_x[dataset_y == 1, 0], dataset_x[dataset_y == 1, 1], marker='^')
plt.plot(x, y_svc)
plt.plot(x, y_logistic)
plt.scatter(svc.support_vectors_[dataset_y[svc.support_] == 0, 0], svc.support_vectors_[dataset_y[svc.support_] == 0, 1], color='red', marker='o')
plt.scatter(svc.support_vectors_[dataset_y[svc.support_] == 1, 0], svc.support_vectors_[dataset_y[svc.support_] == 1, 1], color='red', marker='^')
plt.legend(['Class-1', 'Class-2', 'Support Vector Machine', 'Logistic Regression', "Support Vectors"])
plt.show()
svc_score = svc.score(dataset_x, dataset_y)
logistic_score = lr.score(dataset_x, dataset_y)
print(f'SVC score: {svc_score}')
print(f'Logistic score: {logistic_score}')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte yine "linear" kernel kullanlarak scikit-learn içerisindeki make_blobs fonksiyonu ile üretilen veriler üzerinde
SVC ve LogisticRegression işlemleri yapılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from sklearn.datasets import make_blobs
dataset_x, dataset_y = make_blobs(n_samples=100, n_features=2, centers=2, center_box=[-5, 5])
from sklearn.svm import SVC
svc = SVC(kernel='linear')
svc.fit(dataset_x, dataset_y)
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(dataset_x, dataset_y)
x = np.linspace(np.min(dataset_x[:, 0]), np.max(dataset_x[:, 0]), 1000)
y_svc = -(svc.intercept_ + svc.coef_[0, 0] * x) / svc.coef_[0, 1]
y_logistic = -(lr.intercept_ + lr.coef_[0, 0] * x) / lr.coef_[0, 1]
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 10))
plt.scatter(dataset_x[dataset_y == 0, 0], dataset_x[dataset_y == 0, 1], marker='o')
plt.scatter(dataset_x[dataset_y == 1, 0], dataset_x[dataset_y == 1, 1], marker='^')
plt.plot(x, y_svc)
plt.plot(x, y_logistic)
plt.scatter(svc.support_vectors_[dataset_y[svc.support_] == 0, 0], svc.support_vectors_[dataset_y[svc.support_] == 0, 1], color='red', marker='o')
plt.scatter(svc.support_vectors_[dataset_y[svc.support_] == 1, 0], svc.support_vectors_[dataset_y[svc.support_] == 1, 1], color='red', marker='^')
plt.legend(['Class-1', 'Class-2', 'Support Vector Machine', 'Logistic Regression', "Support Vectors"])
plt.show()
svc_score = svc.score(dataset_x, dataset_y)
logistic_score = lr.score(dataset_x, dataset_y)
print(f'SVC score: {svc_score}')
print(f'Logistic score: {logistic_score}')
#----------------------------------------------------------------------------------------------------------------------------
İç içe dairesel noktalar lojistik regresyon başarısız olmaktadır. Ancak dstek makineleri "rbf" kernel ile tam bir başarı elde
edebilmektedir. Aşağıdaki örnekte iç içe dairesel noktalar make_cirles fonksiyonuyla oluşturulmuş sonra bu noktalar üzerinde
SVC "rbf" kernel ve LogisticRegression işlemleri uygulanmıştır. Buradaki LogisticRegression accuracy sonucu %50 civarındadır.
Ancak destek vektör makineleri %100'lük bir sınıflandırma yapmıştır.
#----------------------------------------------------------------------------------------------------------------------------
from sklearn.datasets import make_circles
dataset_x, dataset_y = make_circles(n_samples=100)
from sklearn.svm import SVC
svc = SVC(kernel='rbf')
svc.fit(dataset_x, dataset_y)
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(dataset_x, dataset_y)
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 10))
plt.scatter(dataset_x[dataset_y == 0, 0], dataset_x[dataset_y == 0, 1], marker='o')
plt.scatter(dataset_x[dataset_y == 1, 0], dataset_x[dataset_y == 1, 1], marker='^')
plt.legend(['Class-1', 'Class-2', 'Support Vector Machine', 'Logistic Regression'])
plt.show()
svc_score = svc.score(dataset_x, dataset_y)
logistic_score = lr.score(dataset_x, dataset_y)
print(f'SVC score: {svc_score}')
print(f'Logistic score: {logistic_score}')
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi biz destek vektör makinelerinde hangi kernel'ı kullanacağınızı nereden bileceğiz? Aslında eğer veri kümesinin
genel dağılımı biliniyorsa uygun lernel seçilebilir. Ancak genellikle bu dapılım da bilinmemektedir. Bu durumda noktalar
hakkında hiçbir bilgimiz yoksa "rbf kernel (radial kernel)" seçilmelidir. Zaten SVC sınıfında "rbf" kernel default durumdur.
SVC "rbf" kernel hemen her zaman LogisticRegression sınıfla elde ettiğimiz sonuçlardan daha iyi ya da onunla eşdeğer bir sonuç vermektedir.
Yapay Sinir ağları daha genel bir yöntemdir ve pek çok durumda diğer alternatif yöntemlere göre daha iyi sonuçlar verebilmektedir.
Ancak ne olursa olsun uygulanacak yöntem verilerin dağılımına ve biçimine göre farklılıklar gösterebilmektedir. Veri kümesine ilişkin
alternatif yöntemlerin denenip en iyi sonucu veren yöntemin tercih edilmesi gerçerli bir yöntemdir.
Aşağıda iç içe girmiş doğrusal olarak ayrıştırılamayan veriler üzerinde SVC, LogisticRegression ve yapay sinir ağı yöntemleri
uygulanıp sonuçlar karşılaştırılmıştır. Buradaki noktalar daireselliği bozulmuş noktalardır. Yani noktalarda bir kalıp vardır ancak
çok belirgin değildir. Şu sonuçlar elde edilmiştir:
SVC accuracy: 0.725
LogisticRegression accuracy: 0.525
Neural Net accuracy: 0.73
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Tabii destek vektör makineleri çok sınıflı lojistik regresyon problemlerinde de kullanılabilmektedir. Aşağıdaki örnekte
üç sınıflı rastgele üretilmiş veriler üzerinde SVC ve LogisticRegression sınıfları kullanılarak bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
from sklearn.datasets import make_blobs
dataset_x, dataset_y = make_blobs(n_samples=100, n_features=2, centers=3, cluster_std=5, random_state=12345)
from sklearn.svm import SVC
svc = SVC(kernel='rbf')
svc.fit(dataset_x, dataset_y)
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(dataset_x, dataset_y)
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 10))
plt.scatter(dataset_x[dataset_y == 0, 0], dataset_x[dataset_y == 0, 1], marker='o')
plt.scatter(dataset_x[dataset_y == 1, 0], dataset_x[dataset_y == 1, 1], marker='^')
plt.scatter(dataset_x[dataset_y == 2, 0], dataset_x[dataset_y == 2, 1], marker='v')
plt.legend(['Class-1', 'Class-2', 'Class-3'])
plt.show()
svc_score = svc.score(dataset_x, dataset_y)
logistic_score = lr.score(dataset_x, dataset_y)
print(f'SVC score: {svc_score}')
print(f'Logistic score: {logistic_score}')
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de gerçek örnekler üzerinde SVC işlemini uygulayalım. SVC işleminde özewllik ölçeklemesi (feature scaling) gerekmektedir.
Aşağıdaki örnekte "breast cancer" veri kümesi üzerinde şu yöntemler uygulanmıştır:
- SVC ("rbf" kernel)
- LogisticRegression
- Naive Bayes
- Neural Net
Elde edilen sonuçlar şöyledir:
SVC accuracy score: 0.956140350877193
LogisticRegression accuracy score: 0.9649122807017544
GaussianNB accuracy score: 0.9210526315789473
NeuralNet accuracy score: 0.956140350877193
Burada istatistiksel lojistik regresyon en iyi sonucu vermiştir. Bunun nedeni noktaların doğrusal olarak ayrıştırılabilir olmasından
kaynaklanmaktadır. SVC işleminde bu sebeple kernel'ı "linear" alabiliriz. Kernel "linear" olarak alındığında bu kez şu sonuçlar elde
edilmiştir.
SVC accuracy score: 0.9649122807017544
LogisticRegression accuracy score: 0.9649122807017544
GaussianNB accuracy score: 0.9210526315789473
NeuralNet accuracy score: 0.956140350877193
Görüldüğü bini noktalar doğrusal olarak ayrıştırılabilir biçimdeyse LogisticRegression ya da "linear" kernel ile SVC
iyi sonuç verme eğilimindedir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import pandas as pd
df = pd.read_csv("data.csv")
dataset_x = df.iloc[:, 2:-1].to_numpy()
dataset_y = np.zeros(len(df))
dataset_y[df['diagnosis'] == 'M'] = 1
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(training_dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
scaled_test_dataset_x = ss.transform(test_dataset_x)
from sklearn.svm import SVC
svc = SVC(kernel='linear')
svc.fit(scaled_training_dataset_x, training_dataset_y)
# LogisticRegression Solution
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(max_iter=1000000)
lr.fit(scaled_training_dataset_x, training_dataset_y)
# Naive Bayes Solution
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
gnb.fit(training_dataset_x, training_dataset_y)
# Neural Net Solution
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='BreastCancer')
model.add(Dense(64, activation='relu', input_dim=dataset_x.shape[1], name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=200, verbose=0)
from sklearn.metrics import accuracy_score
predict_result_svc = svc.predict(scaled_test_dataset_x)
score_svc = accuracy_score(test_dataset_y, predict_result_svc)
predict_result_lr = lr.predict(scaled_test_dataset_x)
score_lr = accuracy_score(test_dataset_y, predict_result_lr)
predict_result_gnb = gnb.predict(test_dataset_x)
score_gnb = accuracy_score(test_dataset_y, predict_result_gnb)
predict_result_nn = (model.predict(scaled_test_dataset_x) > 0.5).astype(int)
score_nn = accuracy_score(test_dataset_y, predict_result_nn)
print(f'SVC accuracy score: {score_svc}')
print(f'LogisticRegression accuracy score: {score_lr}')
print(f'GaussianNB accuracy score: {score_gnb}')
print(f'NeuralNet accuracy score: {score_nn}')
#----------------------------------------------------------------------------------------------------------------------------
Destek vektör makineleri genel olarak resim ve yazıların ve resimlerin sınıflandırılması gibi işlemlerde lojistik regresyona
göre daha iyi bir performans gösterme eğilimindedir. Tabii yukarıda da belirttiğimiz gibi bu tür problemlerde birkaç yöntemi
deneyip en iyi yöntemleri uygulamak gerekir.
Aşağıdaki MNIST veri kümesine destek vektör makineleri yapay sinir ağları ve lojistik regresyon uygulanmıştır. Elde edilen skorlar şöyldir:
LogisticRegresson accuracy score: 0.9203
Simple Neural Net accuracy score: 0.9789
SVC accuracy score: 0.9792
Görüldüğü gibi destek vektör makineleri "rbf" kernel ile iyi bir sonuç vermiştir. Tabii bu örnekteki yapay sinir ağı modeli
iki saklı katmanlı bir modeldir. Resnet gibi derin ağ modelleri daha önceden de gördüğümüz gibi bir iki puan daha iyi sonuç vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df_training = pd.read_csv('mnist_train.csv')
df_test = pd.read_csv('mnist_test.csv')
training_dataset_x = df_training.iloc[:, 1:].to_numpy()
training_dataset_y = df_training.iloc[:, 0].to_numpy()
test_dataset_x = df_test.iloc[:, 1:].to_numpy()
test_dataset_y = df_test.iloc[:, 0].to_numpy()
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(max_iter=1000)
lr.fit(training_dataset_x, training_dataset_y)
predict_result = lr.predict(test_dataset_x)
from sklearn.metrics import accuracy_score
score = accuracy_score(test_dataset_y, predict_result)
print(f'LogisticRegresson accuracy score: {score}')
from tensorflow.keras.utils import to_categorical
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
training_dataset_x = training_dataset_x / 255
test_dataset_x = test_dataset_x / 255
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential(name='MNIST')
model.add(Dense(256, activation='relu', input_dim=784, name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(training_dataset_x, ohe_training_dataset_y, epochs=20, batch_size=32)
import numpy as np
softmax_predict_result = model.predict(test_dataset_x)
predict_result = np.argmax(softmax_predict_result, axis=1)
score = accuracy_score(test_dataset_y, predict_result)
print(f'Simple Neural Net accuracy score: {score}')
from sklearn.svm import SVC
svc = SVC(kernel='rbf')
svc.fit(training_dataset_x, training_dataset_y)
predict_result = svc.predict(test_dataset_x)
score = accuracy_score(test_dataset_y, predict_result)
print(f'SVC accuracy score: {score}')
#----------------------------------------------------------------------------------------------------------------------------
Destek vektör makineleri yanızca sınıflandırma problemlerinde değil lojistik olmayan regresyon problemlerinde de kullanılabilmektedir.
scikit-learn içerisindeki sklearn.svm modülünde bulunan SVR sınıfı lojistik olmayan regresyon problemleri içib destek vektör makinelerinin
kullanılması amacıyla bulundurulmuştur. SVR sınıfının __init__ metodunun parametrik yapısı şöyledir:
class sklearn.svm.SVR(*, kernel='rbf', degree=3, gamma='scale', coef0=0.0, tol=0.001, C=1.0, epsilon=0.1,
shrinking=True, cache_size=200, verbose=False, max_iter=-1)
Buradaki kernel paranetresi yine default durumda "rbf" olarak alınmıştır. Ancak "linear", "poly", "sigmoid" kernel'lar da kullanılabilmektedir.
Sınıfın genel kullanımı diğer scikit-learn sınıflarıyla benzer biçimdedir. Burada da bir heyperplane elde edilmektedir. Elde edilen
hyperplane'e ilişkin katsayı vektörü sınıfın coef_ örnek özniteliği ile, eksen kesim noktası ise intercept_ özniteliği ile
elde edilebilmektedir. Destek vektör makineleri lojistik olmayan regresyon problemlerine uygulanırken özellik ölçeklemesi yapılmalıdır.
Lojistik olmayan regresyon modellerinde destek vektör makineleri LinearRegression, Lasso, Ridge, ElasticNet resgresyonlarına göre
çoğu kez daha kötü sonuç verme eğilimindedir. Ancak problemden probleme bu durum değişebilmektedir.
Aşağıda Boston Haousing Price verileri üzerinde SVR sınıfı ile lojistik olmayan regresyon işlemi destek vektör makineleri kullanılarak
gerçekleştirilmiştir. Bu sonuçlar LinearRegression ve Lasso regresyonu sonuçlarıyla karşılaştırılmıştır. Şu değerler elde edilmiştir:
SVR Mean Absolute Error: 3.7860139251590907
LinearRegression Mean Absolute Error: 3.578927993774414
Lasso Mean Absolute Error: 3.5663931369781494
#----------------------------------------------------------------------------------------------------------------------------
Doğrudan makine yapay zeka ve makine öğrenmesinin bir konusu olmasa da optimizasyon işlemlerinde sıkça karşılaşılan ve ismine
"doğrusal programlama (linear programming)" denilen bir konu vardır. Doğrusal programalama terimindeki programlamanın yazılımsal
programlama ile bir ilgisi yoktur. Üniversitelerde bu konu "yöneylem araştırması (operations research)" ismiyle "endüstri mühendisliği", "ekonometri" ve "matematik"
bölümlerinde gösterilmektedir.
Yöneylem araştırmasının ve doğrusal programlamanın teorisi 2. Dünya Savaşı yıllarında kısıtlı kaynakların verimli kullanılmasının önemli olduğu bir süreç içerisinde
geliştirilmiştir. Yöneylem araştırması "optimizasyon problemleriyle ilgili olan disiplinler arası" bir bilim dalıdır. Yöneylem araştırmasının bazı
alt alanları şunlardır:
- Doğrusal programalama
- Doğrusal olmayan programlama
- Tamsayılı programlama
- Graflar üzerinde optimizasyon işlemleri
- Ulaştırma problemleri
- Çok amaçlı programalam
Ancak alanın en çok bilinen ve en yaygın karşılaşılan alt alanı doğrusal programlamadır. Doğrusal programalama "belli kısıtlar altında
bir fonksiyonun maximum ve minimum değerlerinin" araştırılması ile ilgilenmektedir. Buradaki doğrusallık kısıtların ve amaç fonksiyonunun
birinci dereceden doğrusal fonksiyonlar olduğunu belirtmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Doğrusal programalamada doğrusal kısıtlar "konveks" bir kğme oluşturmaktadır. (Konveks küme demek kümenin herhangi iki noktasını
birleştiren doğurnun her noktasının o küme içerisinde bulunması" demektir.) Konveks kümelerede "uç mokta (extreme point)" denilen
özel noktalar vardır. İşte amaç fonksiyonunu en büyük yapan ya da en küçük yapan noktalar bu uç noktaların birindedir.
Böylece sonsuz sayıda noktayı gözden geçirmek yerine yalnızca uç noktaları gözden geçiren algoritmalar önerilmiştir. Bu algoritmaların
en çok tercih edileni "Simplex" denilen algoritmadır. Simplex algoritmasının iyileştirilmiş biçimine ise "Revised Simplex"
denilmektedir. Bugün doğrusal karar modelleri genellikle "revised simplex" denilen algoritmayla çözülmektedir. Simplex algoritması
tüm uç noktaları dolaşmaz. Ancak çözüme ilişkin olabilecek uç noktaları dolaşır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Doğrusal programalama problemleri genellikle gerçek yaşamdan hareketle sözel bir biçimde ortaya konmaktadır. Sonra problemin bu
sözel anlatımından hareketle matematiksel modeli oluşturulur ve çözüm matematiksel model üzerinde uygulanır. Problemin matematiksel
modeli demekle onun matematiksel terimlerle biçimsel ifadesi kastedilmektedir.
Doğrusal programlamada ayarlanması gereken değişkenlere genellikle x1, x2, x3, ..., xn biçiminde isimler verilir.
Kısıtlar ise doğru denklemleriyle oluşturulmaktadır. Örneğin n tane değişkene ilişkin bir doğrusal programlama modelinin
kısıtları aşağıdaki gibi bir görünümde olabilir:
a11 x1 + a12 x2 + a13 x3 + ... + a1n xn <= b1
a21 x1 + a22 x2 + a23 x3 + ... + a2n xn <= b2
a31 x1 + a32 x2 + a33 x3 + ... + a3n xn <= b3
...
am1 x1 + am2 x2 + am3 x3 + ... + amn xn <= bm
Burada toplam n değişken ve m tane kısıt vardır. Tabii bu kısıtlar prtaik bir biçimde matris çarpımıyla da ifade edilebilmektedR.
Ax <= b
Burada A katsayı matrisidir ve şu biçimdedir:
a11 a12 a13 ... a1n
a21 a22 a23 ... a2n
a31 a32 a33 ... a3n
...
am1 am2 am3 ... amn
x ise aşağıdaki gibi bir sütun vektörüdür:
x1
x2
x3
...
xn
b de bir sütun vektörüdür:
b1
b2
b3
...
bm
Doğrusal programlamada Ax <= b gibi bir kısıtlar altında belli bir fonksiyon en büyüklenmeye ya da en küçüklenmeye çalışılır.
Genellikle bu fonksiyon Z biçiminde isimlendirilmektedir. Buna "amaç fonksiyonu (objective function)" denilmektedir. O halde
amaç fonksiyonun genel biçimi şöyledir:
Zmin ya da Zmax = c1 x1 + c2 x2 + c3 x3 + ... + cn xn
Bu amaç fonksiyonu da matrisel biçimde şöyle ifade edilebilir:
Zmin, Zmax = Cx
Burada C bir satır vektörü x yine yukarıdaki gibi bir sütub vektörüdür:
c1 c2 c3 ... cn
Kısıtların dışında dorğusal karar modellerinde değişkenler üzerinde de aralık temelli kısıtlar bulunabilmektedir. Emn çok karşılaşılan
değişken kısıtları değişkenin 0'dan büyük ya da sıfıra eşit olma kısıtıdır. Bu kısıt şöyle ifade edilebilir:
x >= 0
Doğrusal karar modellerinin matematiksel gösterimi iki biçimde yapılmaktadır: Kanonik biçimde ve standart biçimde. Kanonik biçimde
tük kısıtlar <= ya da >= biçiminde oluşturulmaktadır. Standart biçimde ise tüm kısıtlar = biçiminde oluşturulur. Kanonikj biçim
şöyldir:
Ax <= b
x >= 0
Zmax = Cx
ya da
Ax >= b
x >= 0
Zmin = Cx
Standart biçim ise şöyledir:
Ax = b
x >= 0
Zmin ya da Zmax = Cx
Genellikle kanonik biçim karşımıza çıkar.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Doğrual programalama modeline bir örnek şöle olabilir:
Bir çiftçinin buğday, mısır ve arpa ürünlerinin ekimi için 300 hektarlık arazisi vardır. Çiftçi hektar başına buğdaydan 150 TL,
mısırdan 220 TL, arpadan da 180 TL kar beklemektedir. İşgücü yüzünden çiftçi buğday için 150 hektardan ve arpa için 120 hektardan
daha fazla yer ayırmamalıdır. Verimlilik yönünden ise en az 80 hektar buğday için yer ayırmalı ve mısır ekimi için de toplam
arazinin %30'undan daha fazla yer ayırmamalıdır. Çiftçi karını en büyüklemek istemektedir. Çiftçinin hangi ürün için ne kadar alan
ayırması gerekir?
Şimdi problemin matematriksel modelini oluşturalım. Önce değişkenlere isimler verelim:
x1: ekilecek buğdayın hektar büyülüğü
x2: ekilecek mısırın hektar büyülüğü
x3: ekilecek arpanın hektar büyülüğü
Sorda bir en büyükleme yapılması istenmiştir. Amaç fonksiyonu şöyledir:
Zmax = 150 x1 + 220 x2 + 180 x3
Eğer problemin kısıtları olmasaydı biz 300 hektarın hepsine mısır ekerdik. Ancak problemin uyulması gereken kısıtları vardır:
x1 <= 150 (buğday için en fazla 150 hektar yer ayrılmalıdır)
x3 <= 120 (arpa için en fazla 120 hektar yer ayrılmalıdır)
x1 >= 80 (buğday için en az 80 hektar yer ayrılmalıdır)
x2 <= 90 (mısır için toplam arazinin %30'undan daha fazla yer ayrılmamalıdır)
x1, x2, x3 >= 0 (negatif üretim yapılmaz)
Buradaki matematiksel model ne kanonik ne de standart biçime uygundur. Çünkü kısıtların bazıları <= biçiminde bazıları >= biçimindedir.
Biz kısıtları -1 ile çarparak onların yönünü değiştirebiliriz. Örneğin:
x1 >= 80
ile
-x1 <= -80
aynı anlamdadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Beslenem ya da diyet tarzı problemlerde doğrusal programalama teknikleri yaygın kullanılmaktadır. Buna şöyle bir örnek
verilebilir:
Bir kişi sadece et, süt ve yumurta yiyerek diyet yapmaktadır. Bu kişinin günde en az 15 mg A vitamini, 30 mg C vitamini ve
10 mg D vitamini alması gerekmektedir. Buna karşılık besinlerle aldığı kolesterol 80 br/günü geçmemelidir. 1 litre sütte 1 mg A,
100 mg C, 10 mg D ve 70 birim kolesterol vardır ve sütün litresi 800 TL dir. 1 kg ette 1 mg A, 10 mg C, 100 mg D vitamini ve 50 br
kolesterol vardır. Etin kilosu 3700 TL dir. Yumurtanın düzinesinde 10 mg A, 10 mg C ve 10 mg D vitamini ile 120 birim kolesterol bulunmakta olup,
yumurtanın düzinesi 275 TL'dir. Kişinin istediği, bu diyeti en ucuz yolla gerçekleştirmektir. Buna göre problemin doğrusal
programlama modelini oluşturunuz.
Burada yine önce değişkenleri isimlendirmek gerekir:
x1: Kilogram olarak tüketilecek et miktarı
x2: Litre olarak tüketilecek süt miktarı
x3: Düzine olarak tüketilecek yumurta miktarı
Problemde istenen en az para harcayarak diyet yapmaktır:
Zmin = 800 x1 + 3700 x2 + 275 x3
Tabii hiçbir şey tüketmezsek para da harcamayız. Ancak bu udurm mümkün değildir. Çünkü uymamız gereken bazı sağlık kısıtları vardır:
x1 + x2 + 10 x3 >= 15 (A vitamini kısıtı)
100 x1 + 10 x2 + 10 x3 >= 30 (C vitamini, kısıtı)
10 x1 + 100 x2 + 10 x3 >= 10 (D vitamini kısıtı)
70 x1 + 50 x2 + 120 <= 80 (Kolesterol kısıtı)
Bu değerlerin hiçbiri negatif olamamaktadır:
x1, x2, x3 >= 0
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Python kullanarak doğrusal karar modellerinin çözümü için çeşitli kütüphaneler oluşturulmuştur. Bunlardan en yaygın kullanılanları
SciPy'ın linprog modülü, PuLP kütüphanesi ve Pyomo kütüphanesidir. Biz burada linprog ve PuLP kütüphanelerinin kullanımları üzerinde
duracağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
SciPy i scipy.optimize paketinde bulunan linprog fonksiyonu doğrusal karar modelleri için yaygın biçimde kullanılmaktadır.
linprog fonksiyonunun parametrik yapısı şöyledir:
scipy.optimize.linprog(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, bounds=None, method='highs',
allback=None, options=None, x0=None, integrality=None)
linprog fonksiyonunu kullanabilmek için modelin aşağıdaki biçime dönüştürülmüş olması gerekir:
Zmin = Cx
Ax <= b ya da Ax = b
Yani modelin bir minimizasyon biçiminde olması ve kısıtların da "<=" ya da "=" biçiminde olması gerekir. Eğer problem bir maksimizasyon problemi
ise amaç fonksiyonunun negatifi alınıp problem minimizasyon problemine dönüştürülmelidir. Kısıtların bazılaı ">=" biçimindeyse
eşitsizliğin iki tarafı -1 ile çarpılarak kısıtlar <= biçimine dönüştürülebilir.
Fonksiyonun parametreleri NumPy dizisi olmak zorundadır. c parametresi amaç fonksiyonun katsayı vektörünü belirtir. A_ub
parametresi "<=" kısıtlarının katsayı matirisidir. b_ub kısıtların sağ tarafındaki b katsayılarını belirtmektedir. (Buradaki "up"
eki "upper bound" sözcüklerinden kısaltılmıştır.) A_eq parametresi = kısıtlarının katsayı matrisini b_eq parametresi de "=" kısıtlarının
b değerlerini belirtmektedir. method parametresi kullanılacak çözüm yöntemini belirtmektedir. Yukarıda da belirttiğimiz gibi genel olarak
Simplex yaygın kullanılan bir algoritmadır. Buradaki "highs" değeri "highs" isimli optimizasyon kütüphanesinin kullanılacağını belirtmektedir.
Fonksiyonun bounds parametresi değişkenler üzerindeki kısıtları belirtmektedir. Bu parametre default geçilirse "x >= 0" koşulu anlaşılmaktadır.
Bu parametreye iki elemanlı demet listesi geçilebilir. Demetlerin ilk elemanları minimum değeri, ikinci elemanları maksimum değeri belirtir.
Örneğin:
[(0, 100), (10, None), (None, None)]
Buradaki None değerleri minimum için eksi sonnsuz, maksimum için artı sonsuz anlamına gelmektedir.
linprog fonksiyonu çağrıldığında çözüm gerçekleştirlir. Fonksiyonun geri dönüş değeri bir sınıf nesnesi olarak bize verilir. Bu nesnenin
fun özniteliği amaç fonksiyonunun değerini, success isimli bool türden öznitelik modelin optimal çözümünün oluğ olmadığını belirtmektedir.
int türden status özniteliği çözümün başarısı hakkında ek birtakım bilgiler vermektedir. nit özniteliği ise çözüme kaç iterasyonda
varıldığını belirtmektedir. linprog fonksiyonun geri döndürdüğü nesneye ilişkin sınıfın __repr__ ve __str__ metotları çözüm ile ilgili
bilgileri bize birkaç satır ile yazı biçiminde vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şimdi aşağıdaki modeli linprog ile çözmeye çalışalım:
Kısıtlar
x1 + 2 x2 <= 6
2 x1 + x2 <= 8
-x1 + x2 <= 1
x2 <= 2
Zmax = 3 x1 + 2 x2
x1, x2 >= 0
Burada katsayı matrisleri şöyle oluşturulur:
c = np.array([-3, -2])
aub = np.array([[1, 2], [2, 1], [-1, 1], [0, 1]])
bub = np.array([6, 8, 1, 2])
Problem bir maksimizasyon problemi olduğu için negatif ile çarpılarak minimizasyon ahline dönüştürülmüştür. Tabii çözüm sonucunda
elde edilecek amaç fonksiyonunun değeri de negatif ile çarpılmalıdır. Şu sonuçlar elde edilmiştir:
con: array([], dtype=float64)
fun: -12.666666666636761
message: 'Optimization terminated successfully.'
nit: 5
slack: array([9.95203919e-12, 1.99413819e-11, 3.00000000e+00, 6.66666667e-01])
status: 0
success: True
x: array([3.33333333, 1.33333333])
-----------
Maximum value: 12.666666666636761
Optimal values of variables: [3.33333333 1.33333333]
#----------------------------------------------------------------------------------------------------------------------------
from scipy.optimize import linprog
import numpy as np
c = np.array([-3, -2])
aub = np.array([[1, 2], [2, 1], [-1, 1], [0, 1]])
bub = np.array([6, 8, 1, 2])
result = linprog(c, aub, bub)
print(result)
print('-----------')
print(f'Maximum value: {-result.fun}')
print(f'Optimal values of variables: {result.x}')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki modeli linprog ile çözmeye çalışalım:
Kısıtlar
6 x1 + 4 x2 <= 120
3 x1 + 10 x2 <= 180
x1, x2 >= 0
Zmax = 45 x1 + 5 5x2
Burada katsayı matrisleri şöyle oluşturulabilir:
c = np.array([-45, -55])
aub = np.array([[6, 4], [3, 10]])
bub = np.array([120, 180])
Şu sonuçlar elde edilmiştir:
con: array([], dtype=float64)
fun: -1274.9999989391895
message: 'Optimization terminated successfully.'
nit: 4
slack: array([9.93566687e-08, 1.50681728e-07])
status: 0
success: True
x: array([ 9.99999999, 14.99999999])
-----------
Maximum value: 1274.9999989391895
Optimal values of variables: [ 9.99999999 14.99999999]
#----------------------------------------------------------------------------------------------------------------------------
from scipy.optimize import linprog
import numpy as np
c = np.array([-45, -55])
aub = np.array([[6, 4], [3, 10]])
bub = np.array([120, 180])
result = linprog(c, aub, bub)
print(result)
print('-----------')
print(f'Maximum value: {-result.fun}')
print(f'Optimal values of variables: {result.x}')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki modeli linprog ile çözmeye çalışalım:
Kısıtlar
x1 + x2 + 10 x3 >= 15
10 x2 + 10 x3 >= 30
100 x1 + 100 x2 + 10 x3 >= 10
70 x1 + 50 x2 + 120 x3 <= 80
x1, x2, x3 >= 0
Zmin = 2.25 x1 + 26 x2 + 0.21 x3
Şu sonuçlar elde edilmiştir:
con: array([], dtype=float64)
fun: 115.01558512059408
message: 'The algorithm terminated successfully and determined that the problem is infeasible.'
nit: 5
slack: array([ -4.44573877, 18.95368497, 507.92526722, -256.85002807])
status: 2
success: False
x: array([0.77252132, 4.35243834, 0.54293016])
-----------
Maximum value: 115.01558512059408
Optimal values of variables: [0.77252132 4.35243834 0.54293016]
#----------------------------------------------------------------------------------------------------------------------------
from scipy.optimize import linprog
import numpy as np
c = np.array([2.25, 26, 0.21])
aub = np.array([[-1, -1, -10], [0, -10, -10], [-100, -100, -10], [70, 50, 120]])
bub = np.array([-15, -30, -10, 80])
result = linprog(c, aub, bub)
print(result)
print('-----------')
print(f'Minimum value: {result.fun}')
print(f'Optimal values of variables: {result.x}')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki gibi bir model olsun:
Kısıtlar
x11 + x21 + x31 = 700
x12 + x22 + x32 = 300
x13 + x23 + x33 = 900
x14 + x24 + x34 = 600
x15 + x25 + x35 = 500
x11 + x12 + x13 + x14 + x15 <= 1200
x21 + x22 + x23 + x24 + x25 <= 1200
x31 + x32 + x33 + x34 + x35 <= 1200
x11, x12, x13, x14, x15, x21, x22, x23, x24, x25, x31, x32, x33, x34, x35 >= 0
Zmin = 8 x11 + 12 x12 + 9 x13 + 8 x14 + 0 * x15 + 11 x21 + 9 x22 + 16 x23 + 0 x24 + 8 x25 + 14 x31 + 0 x32 + 10 x33 + 9 x34 + 12 x35
Bu modelde 15 değişken vardır. Modelde "<=" ve "=" kısıtları birlikte bulunmaktadır. Katsayı matrisleri şöyle oluşturulabilir:
c = np.array([8, 12, 9, 8, 0, 11, 9, 16, 0, 8, 14, 0, 10, 9, 12], dtype='float')
aub = np.array([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1]], dtype='float')
bub = np.array([1200, 1200, 1200])
aeq = np.array([[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]], dtype='float')
beq = np.array([700, 300, 900, 600, 500])
Çözümden şu sonuçlar elde edilmiştir:
con: array([1.35525499e-06, 5.77492301e-07, 1.74413185e-06, 1.16082060e-06,
9.66381037e-07])
fun: 14599.999973663016
message: 'Optimization terminated successfully.'
nit: 6
slack: array([2.33400647e-06, 6.00000001e+02, 2.58888008e-06])
status: 0
success: True
x: array([6.99999998e+02, 4.39881404e-09, 4.00994684e-07, 1.23736337e-08,
4.99999999e+02, 2.84052512e-07, 4.48746365e-09, 8.47347710e-10,
5.99999999e+02, 2.08544710e-08, 8.93021817e-08, 2.99999999e+02,
8.99999998e+02, 1.82418488e-08, 3.59282936e-08])
-----------
Maximum value: 14599.999973663016
Optimal values of variables: [6.99999998e+02 4.39881404e-09 4.00994684e-07 1.23736337e-08
4.99999999e+02 2.84052512e-07 4.48746365e-09 8.47347710e-10
5.99999999e+02 2.08544710e-08 8.93021817e-08 2.99999999e+02
8.99999998e+02 1.82418488e-08 3.59282936e-08]
Burada çöümdeki değişken değerleri 0'a çok yakındır. Ancak yuvarlama hataları nedeniyle 0 olmamıştır.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.optimize import linprog
import numpy as np
c = np.array([8, 12, 9, 8, 0, 11, 9, 16, 0, 8, 14, 0, 10, 9, 12], dtype='float')
aub = np.array([[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1]], dtype='float')
bub = np.array([1200, 1200, 1200])
aeq = np.array([[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]], dtype='float')
beq = np.array([700, 300, 900, 600, 500])
result = linprog(c, aub, bub, aeq, beq)
print(result)
print('-----------')
print(f'Maximum value: {result.fun}')
print(f'Optimal values of variables: {result.x}')
#----------------------------------------------------------------------------------------------------------------------------
PuLP kütüphanesi daha kolay anlaşılabilir bir model sunmaktadır. Kütüphane Anaconda içerisinde default olarak bulunmadığı için
aşağıdaki gibi kurulmaldır:
pip install pulp
Kütüphanein dokümantasyonu aşağıdaki bağlantıda bulunmaktadır:
https://coin-or.github.io/pulp/
PuLP kütüphanesinin kullanımı adım adım şöyledir:
1) pulp.LpProblem sınıfı türünden bir nesne yaratılır. Bu yaratım sırasında modele bir isim verilir ve modelin maksimizasyon mu
minimizasyon mu olduğu belirtilir. Örneğin:
lp = pulp.LpProblem('District_School_Problem', pulp.LpMaximize)
2) Modeldeki değişkenler pulp.LpVariable türünden sınıf nesneleri ile temsil edilmektedir. Bu nesneler yaratılırken onlara isimler
alt ve üst limitler verilir. Alt ve üst limitlerin default değerleri eksi sonsuz ve artı sonsuz biçimindedir. Örneğin:
x1 = pulp.LpVariable(name='x1', lowBound=0)
x2 = pulp.LpVariable(name='x2', lowBound=0)
3) Kısıtlar tamamen aritmetik operatörler kullanılarak oluşturulmaktadır. Yani LpProblem sınıfı bunun için çeşitli
operatör metotlarını zaten bulundurmuştur. Örneğin:
lp += x1 + 2 * x2 <= 6
lp += 2 * x1 + x2 <= 8
lp += -x1 + x2 <= 1
lp += x2 <= 2
4) Amaç fonksiyonu da benzer biçimde oluşturulmaktadır. İfadenin sonunda karşılaştırma operatörü olmadığından sınıf bunun amaç fonksiyonu
olduğunu anlamaktadır. Örneğin:
lp += 3 * x1 + x2
5) Modeli çözmek için LpProblem sınıfının solve metodu kullanılmaktadır. Örneğin:
lp.solve()
6) LpProblem sınıfının __repr__ ve __str__ metotları modeli bize yazsısal biçimde vermektedir.
7) Çözüm elde edildikten sonra değişkenlerin değerleri pulp.value fonksiyonu ile elde edilebilmektedir. Amaç fonksiyonun değeri de
pulp.value(lp.objective) çağrısıyla elde edilebilmektedir. LpProblem sınıfının status örnek özniteliği problemin çözüm başarısını bize verir.
constraints özniteliği ise kısıtları bize vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki modeli PuLP kürüphanesi ile çözleim:
Kısıtlar
x1 + 2 x2 <= 6
2 x1 + x2 <= 8
-x1 + x2 <= 1
x2 <= 2
Zmax = 3 x1 + 2 x2
x1, x2 >= 0
Programdan şu sonuçlar elde edilmiştir:
x1 = 3.3333333
x2 = 1.3333333
Maximum value = 12.666666500000002
#----------------------------------------------------------------------------------------------------------------------------
import pulp
lp = pulp.LpProblem('Model-1', pulp.LpMaximize)
x1 = pulp.LpVariable(name='x1', lowBound=0)
x2 = pulp.LpVariable(name='x2', lowBound=0)
lp += x1 + 2 * x2 <= 6
lp += 2 * x1 + x2 <= 8
lp += -x1 + x2 <= 1
lp += x2 <= 2
lp += 3 * x1 + 2 * x2
print(lp)
lp.solve()
print(f'x1 = {pulp.value(x1)}')
print(f'x2 = {pulp.value(x2)}')
print(f'Maximum value = {pulp.value(lp.objective)}')
#----------------------------------------------------------------------------------------------------------------------------
Şimdi aşağıdaki problemi PuLP kütüphanesi ile çözelim:
Kısıtlar
6x1 + 4x2 <= 120
3x1 + 10x2 <= 180
x1, x2 >= 0
Zmax = 45x1 + 55x2
Programdan şu sonuçlar elde edilmiştir:
x1 = 10.0
x2 = 15.0
Maximum value = 1275.0
#----------------------------------------------------------------------------------------------------------------------------
import pulp
lp = pulp.LpProblem('Model-2', pulp.LpMaximize)
x1 = pulp.LpVariable(name='x1', lowBound=0)
x2 = pulp.LpVariable(name='x2', lowBound=0)
lp += 6 * x1 + 4 * x2 <= 120
lp += 3 * x1 + 10 * x2 <= 180
lp += 45 * x1 + 55 * x2
print(lp)
lp.solve()
print(f'x1 = {pulp.value(x1)}')
print(f'x2 = {pulp.value(x2)}')
print(f'Maximum value = {pulp.value(lp.objective)}')
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de aşağıdaki modeli PuLP ile çözelim:
Kısıtlar
x1 + x2 + 10 x3 >= 15
10 x2 + 10 x3 >= 30
100 x1 + 100 x2 + 10 x3 >= 10
70 x1 + 50 x2 + 120 x3 <= 80
x1, x2, x3 >= 0
Zmin = 2.25 x1 + 26 x2 + 0.21 x3
Programın çalıştırılmasıyla şu sonuçlar elde edilmiştir:
x1 = 0.0
x2 = 4.0
x3 = -1.0
Minimum value = 103.79
#----------------------------------------------------------------------------------------------------------------------------
import pulp
lp = pulp.LpProblem('Model-3', pulp.LpMinimize)
x1 = pulp.LpVariable(name='x1', lowBound=0)
x2 = pulp.LpVariable(name='x2', lowBound=0)
x3 = pulp.LpVariable(name='x3', lowBound=0)
lp += x1 + x2 + 10 * x3 >= 15
lp += 10 * x2 + 10 * x3 >= 30
lp += 100 * x1 + 100 * x2 + 10 * x3 >= 10
lp += 70 * x1 + 50 * x2 + 120 * x3 <= 80
lp += 2.25 * x1 + 26 * x2 + 0.21 * x3
print(lp)
lp.solve()
print(f'x1 = {pulp.value(x1)}')
print(f'x2 = {pulp.value(x2)}')
print(f'x3 = {pulp.value(x3)}')
print(f'Minimum value = {pulp.value(lp.objective)}')
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de aşağıdaki modlei PuLP ile çözelim:
Kısıtlar
x11 + x21 + x31 = 700
x12 + x22 + x32 = 300
x13 + x23 + x33 = 900
x14 + x24 + x34 = 600
x15 + x25 + x35 = 500
x11 + x12 + x13 + x14 + x15 <= 1200
x21 + x22 + x23 + x24 + x25 <= 1200
x31 + x32 + x33 + x34 + x35 <= 1200
x11, x12, x13, x14, x15, x21, x22, x23, x24, x25, x31, x32, x33, x34, x35 >= 0
Zmin = 8 x11 + 12 x12 + 9 x13 + 8 x14 + 0 * x15 + 11 x21 + 9 x22 + 16 x23 + 0 x24 + 8 x25 + 14 x31 + 0 x32 + 10 x33 + 9 x34 + 12 x35
Programın çalıştırılmasıyla şu sonuçlar elde edilmiştir:
X12 = 0.0
x13 = 0.0
x14 = 0.0
x15 = 500.0
x21 = 0.0
x22 = 0.0
x23 = 0.0
x24 = 600.0
x25 = 0.0
x31 = 0.0
x32 = 300.0
x33 = 900.0
x34 = 0.0
x35 = 0.0
Minimum value = 14600.0
#----------------------------------------------------------------------------------------------------------------------------
import pulp
lp = pulp.LpProblem('Model-4', pulp.LpMinimize)
names = [f'x{i}{k}' for i in range(1, 4) for k in range(1, 6)]
variables = [pulp.LpVariable(name, lowBound=0) for name in names]
x11, x12, x13, x14, x15, x21, x22, x23, x24, x25, x31, x32, x33, x34, x35 = variables
lp += x11 + x21 + x31 == 700
lp += x12 + x22 + x32 == 300
lp += x13 + x23 + x33 == 900
lp += x14 + x24 + x34 == 600
lp += x15 + x25 + x35 == 500
lp += x11 + x12 + x13 + x14 + x15 <= 1200
lp += x21 + x22 + x23 + x24 + x25 <= 1200
lp += x31 + x32 + x33 + x34 + x35 <= 1200
lp += 8 * x11 + 12 * x12 + 9 * x13 + 8 * x14 + 0 * x15 + 11 * x21 + 9 * x22 + 16 * x23 + 0 * x24 + 8 * x25 + 14 * x31 + 0 * x32 + 10 * x33 + 9 * x34 + 12 * x35
print(lp)
lp.solve()
for variable in variables:
print(f'{variable.name} = {pulp.value(variable)}')
print(f'Minimum value = {pulp.value(lp.objective)}')
#----------------------------------------------------------------------------------------------------------------------------
Doğrusal karar modellerinde çok fazla değişken bulunabilmektedir. Bu durumda modelin bir dosyada oluşturulup yüklenmesi
işlemleri kolaylaştırmaktadır. PuLP kütüphanesinin buna bazı ayrıntıları vardır. Dokümantasyondan bu ayrıntıları
öğrenebilirsiniz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Gerçek dünyadaki pek çok problem bir graf problemi biçiminde ele alınabilmektedir. Graflar üzerinde işlemler veri yapıları ve
algoritmalar dünyasında önemli bir yer tutmaktadır.
Bir graf düğümlerden ve yollardan (düğümler arası bağlantılardan) oluşmaktadır. Düğümlere İngilizce "node" ya da "vertex"
de denilmektedir. Yollar ise İngilizce'de "edge" biçiminde ifade edilmektedir. Graflar "yönlü (directed)" ya da "yönsüz (undirected)"
olabilmektedir. Eğer düğümler arasındaki yolların bir yönü varsa bu tür graflara yönlü graf denilmektedir. Eğer yolların bir yönü yoksa
bunlara da yönsüz graf denilmektedir. Yönsüz graf demek aslında iki yönlü graf demektir. Örneğin:
A ---- B
Burada A düğümünden B düğümüne de B düğümünden A düğümüne de bir yol olduğu varsaylılabilir. Örneğin naviasyon sistemlerinde
her kavşak noktası bir düğüm gibi ele alınır. Böylece harita bir graf biçiminde ifade edilir.
Graflarda düğümlere ve yollara bilgiler iliştirilebilmektedir. Örneğin düğümler şehirleri temsil ediyorsa onların isimleri, nüfusları vs.
söz konusu olabilir. Yollara bilgi iliştirilmesiyle çok karşılaşılır. Örneğin iki düğüm arasındaki yola bir uzaklık
bilgisi iliştirilebilir.
Bir grafta tüm düğümlere tek bir yerden gelinebiliyorsa bu tür graflara "ağaç (tree)" da denilmektedir.
Yukarıda da belirttiğimiz gibi gerçek hayattaki bazı problemler bir graf veri yapısı biçiminde temsil edilip o veri yapısı üzerinde
çeşitli algoritmalar uygulanmaktadır. Örneğin biz kavşak noktalarını düğümlerle temsil edersek, yollar da bu kavşak noktaları arasındaki
uzaklıkları belirtirse böyle bir grafta pek çok algoritmik problemi çözebiliriz. Bir noktadan başka bir noktaya en kısa yol en popüler
graf algoritmalarından biridir.
Graf algoritmaları uzmanlık isteyen bir alandır. Dolayısıyla genellikle programcılar bu konuda çalışan kişilerin oluşturdukları kütüphanelerinin
kullanırlar. Çeşitli programlama dilleri için çeşitli graf kütüphaneleri oluşturulmuştur. C++'ta en yaygın kullanılan ve en verimli olalardan biri
"Boost Graph Library (BGL)" denilen kütüphanedir. Python'da çeşitli seçenkler söz konusudur. Ancak "Netwokx" ve "iGraph" denilen kütüphaneler
diğerlerinden daha fazla tercih edilmektedir. Biz de kurusumuzun bu bölümünde "networkx" kütüphanesini tanıtacağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
newtworx kütüphanesi aşağıdaki gibi yüklenebilir:
pip install networkx
Kürüphanenin dokümantasyonuna aşağıdaki bağlantıdan erişebilirsiniz:
https://networkx.org/documentation/stable/index.html
Genellikle programcılar tarafından kütüphane nx ismiyle import edilmektedir:
import networkx as nx
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bir graph oluşturmak için önce bir graph nesnesi yaratmak gerekir. Dört çeşit graph nesneleri vardır. Örneğin yönsüz (undirected) grafllar için
Graph sınıfı yönlü graflar için DiGraph sınıfı kullanılmaktadır. Graph ve DiGraph sınıfları paralel kenarlara (edges) izin vermemektedir. Bunlara izin veren
MultiGraph ve MultiDiGraph sınıfları mevcutur.
Graf nesnesi yaratılırken birtakım kenarlar da verilebilir. Ayrıca istenildiği kadar isimli parametre yaratılan graf nesnesine iliştirilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.Graph(title='Test Graph', date='26/02/2023')
#----------------------------------------------------------------------------------------------------------------------------
Grafa düğüm (vertex/node) eklemek için add_node metodu kullanılabilir. Bu metodun birinci parametresi zorunlu olan düğüm anahtarını almaktadır.
Düğüm anahtarı hashable herhangi bir nesne olabilir. Örneğin int bir değer bir string hashable nesnelerdir. Bir düğüm eklenirken ona istenilen her türlü
bilgi (networkx kütüphanesinde iliştirilen bilgilere "attribute" denilmektedir) iliştirilebilir. Bunun için isimli parametreler kullanılır.
Örneğin:
import networkx as nx
g = nx.Graph(title='Test Graph'')
g.add_node('A', count=10)
g.add_node('B', count=20)
Tabii düğümlere aynı türden ya da isimli bilgiler iliştirilmesi zorunlu değildir. Ancak genellikle tüm düğümler aynı türden
bilgiler içerir.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.Graph(title='Test Graph')
g.add_node('A', count=100)
g.add_node('B', count=200)
g.add_node('C', count=300)
g.add_node('D', count=400)
g.add_node('E', count=500)
#----------------------------------------------------------------------------------------------------------------------------
Aslında tek hamlede birden fazla düğüm de grafa eklenebilmektedir. Bunun için graf sınıflarının add_nodes_from metodu kullanılır. Bu metoda dolaşılabilir
bir nesne verilirse metot o nesneyi dolaşır, dolaşım sırasında elde edilen değerleri düğüm anahtarı kabul ederek onları tek tek grafa ekler.
Aşağıdaki örnekte 'FGH' stringi dolaşılabilir olduğu için 'F', 'G' ve 'H' için birer düğüm yaratılacaktır. Benzer biçimde range nesnesi de
dolaşılabilir olduğu için oradan elde edilen değerlerle de düğümler yaratılacaktır.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.DiGraph(title='My Graph')
g.add_node('A', count=10)
g.add_node('B', count=20)
g.add_node('C', count=30)
g.add_node('D', count=40)
g.add_nodes_from('FGH')
g.add_nodes_from(range(10))
print(g.nodes)
#----------------------------------------------------------------------------------------------------------------------------
add_nodes_from metodunda isimli parametreler girilirse bunlar eklenen tüm düğümlere iliştirilir. Aşağıdaki örnekte 'F', 'G've 'H' düğümlerinin count
bilgilierinin herpsi 50 olacaktır.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.DiGraph(title='My Graph')
g.add_node('A', count=10)
g.add_node('B', count=20)
g.add_node('C', count=30)
g.add_node('D', count=40)
g.add_nodes_from('FGH', count=50)
print(g.nodes)
#----------------------------------------------------------------------------------------------------------------------------
add_nodes_from metodunda aslında iki elemanlı bir demet listesi girilebilir. Bu listeyi oluşturan demetlerin birinci elemanları düğümün anahtarını, ikinci elemanları
düğüme iliştirilecek bilgileri belirten sözlük nesnesi olmalıdır. Böylece tek hamlede bilgileriyle birlikte pek çok düğümü grafa ekleyebiliriz.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.Graph(title='Test Graph')
g.add_node('A', count=100)
g.add_node('B', count=200)
g.add_node('C', count=300)
g.add_node('D', count=400)
g.add_node('E', count=500)
g.add_nodes_from([('I', {'count': 700}), ('J', {'count': 800})])
print(g.nodes)
#----------------------------------------------------------------------------------------------------------------------------
Graph sınıflarının nodes isimli örnek öznitelikleri bir sözlük gibi davranmaktadır. Biz grafa ilişkin bir düğümün bilgilerini
elde etmek için onun anahtarını veriririz. nodes sözlüğü de bize ona karşı gelen bilgileri bir sözlük olarak verir.
nodes örnek özniteliğinin bize verdiği nesne bir "view" nesnesidir. Yani bu nesne üzerinde değişiklik yapıldığında asıl
graf üzerinde değişiklik yapılmış olur. nodes ile elde edilen view nesnesi dolaşılabilir bir nesnedir. Bu neden dolaşıldığında
biz düğümlerin anahtarlarını elde ederiz.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.Graph(title='Test Graph')
g.add_node('A', count=100)
g.add_node('B', count=200)
g.add_node('C', count=300)
g.add_node('D', count=400)
g.add_node('E', count=500)
g.add_nodes_from([('I', {'count': 700}), ('J', {'count': 800})])
d = g.nodes['D']
print(d) # {'count': 400}
for node in g.nodes:
print(node)
#----------------------------------------------------------------------------------------------------------------------------
nodes örnek özniteliği ile verilen nesne bir sözlük gibi davrandığı için o nesne üzerinde get metodu, items metodu
keys ve values uygulanabilir. get metodu yine anahtar yoksa ikinci parametresiyle belirtilen değere geri dönmektedir.
items metodu dolaşılabilir bir nesne verir. O nesne dolaşıldığında iki elemanlı demetler elde edilmektedir. Bu demetlerin
birinci elemanı düğümün anahtarını, ikinci elemanları düğüme iliştirilen bilgileri temsil eden sözlük nesnelerinden oluşmaktadır.
keys metodu bize anahtarlardan oluşan dolaşılabilir bir nesne, values metodu ise sözlüklerden oluşan dolaşılabilir bir nesne
vermektedir. nodes örnek özniteliğinin data isimli bir metodu da vardır. Bu data metodunun verdiği dolaşılabilir bir nesnedir. Dolaşıldığında iki elemanlı
demetler elde edilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.Graph(title='Test Graph')
g.add_node('A', count=100)
g.add_node('B', count=200)
g.add_node('C', count=300)
g.add_node('D', count=400)
g.add_node('E', count=500)
g.add_nodes_from([('I', {'count': 700}), ('J', {'count': 800})])
d = g.nodes.get('Z', 'Not found')
print(d) # Not found
for t in g.nodes.items():
print(t)
for name, count in g.nodes.items():
print(f'{name} ---> {count}')
for key in g.nodes.keys():
print(key)
for value in g.nodes.values():
print(value)
#----------------------------------------------------------------------------------------------------------------------------
Grafa düğümler eklendikten sonra artık düğümler arasındaki yolların (edges) eklenmesi gerekir. Yol eklemek için add_edge
metodu kullanılmaktadır. Graf sınıflarının add_edge metotları bizden başlangıç ve bitiş düğümüne ilişkin anahtarları ve
aynı zamanda o yola iliştirilecek diğer bilgileri isimli argümanlarla almaktadır. Örneğin:
g.add_edge('A', 'B', length=100, density=0.3)
Aslında önce düğümleri sonra yolları ekleme zorunluluğu yoktur. Zaten add_edge metotları eğer düğümler eklenmediyse onları da
eklemektedir. Yani biz henüz bulunmayan düğümlere yol ekleyebiliriz.
Başlangıç ve bitiş düğümleri aynı olan yollar da eklenebilir. Aynı iki düğüm arasında birden fazla yolun olabilmesi için grafım multi
graf türlerinden olması gerekir.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.Graph(title='Test Graph')
g.add_node('A', count=100)
g.add_node('B', count=200)
g.add_node('C', count=300)
g.add_node('D', count=400)
g.add_node('E', count=500)
g.add_edge('A', 'B', length=100)
g.add_edge('A', 'C', length=200)
g.add_edge('B', 'D', length=150)
g.add_edge('E', 'F', lenght=500) # F düğümü yok ama bu sırada eklenecek
#----------------------------------------------------------------------------------------------------------------------------
Oluşturulan grafı çizdirmek için kütüphanedeki draw ya da draw_networkx metotları kullanılmaktadır. Graf çizmenin çeşitli
algoritmaları vardır. Çünkü aynı graf çok değişik biçimlerde çizdirilebilmektedir. Yaygın kullanılan algoritmalardan biri
"kamada kawai" algoritmasıdır. Metotların with_labels parametresi düğüm yuvarlaklarının içerisinde düğüm anahtarlarının bulundurulup
bulundurulmayacağını belirtir. Bu parametrenin default durumu False biçimdedir. node_size parametresi yuvarlakların büyüklüğünü
belirlemekte kullanılır. Bu göreli bir büyüklüktür. Default değeri 300'dür. Yönlü graflarda arrowsize okun büyüklüğünü belirtmektedir.
Bunun default değeri 10'dur. font_size parametresi düğüm içerisindeki ve yollardaki yazıların font büyüklüklerini belirtmektedir.
node_color isimli parametre düğümlerin renklerini belirtir. Buraya tek bir renk girilirse tm düğümler aynı renkte görüntülenir.
Ancak bir renk dizisi girilirse bu durumda her düğüm ayrı renkte görümtülenebilmektedir. Belli bir algoritmaya göre çizim yapmak için
önce graf o algoritma fonksiyonuna verilir. Bu fonksiyon bir sözlük nesnesi geri döndürür. O sözlük nesnesi draw ya da draw_networkx
metotlarının pos parametresine verilir. Örneğin:
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos=pos, with_labels=True, node_size=1500, font_size=24,
node_color=['yellow', 'green', 'red', 'magenta', 'yellow', 'pink', 'brown'], arrowsize=20)
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.DiGraph(ntitle='Test Graph')
g.add_node('A')
g.add_node('B')
g.add_node('C')
g.add_node('D')
g.add_edge('A', 'B', length=150)
g.add_edge('A', 'C', length=125)
g.add_edge('C', 'D', length=15)
g.add_edge('A', 'F', length=175)
g.add_edge('E', 'D', length=65)
g.add_edge('E', 'F', length=90)
g.add_edge('A', 'E', length=85)
g.add_edge('F', 'B', length=185)
g.add_edge('B', 'E', length=60)
g.add_edge('G', 'D', length=72)
g.add_edge('D', 'G', length=200)
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 8))
pos = nx.circular_layout(g)
nx.draw(g, pos=pos, with_labels=True, node_size=1500, font_size=24, node_color=['yellow', 'green', 'red', 'magenta', 'yellow', 'pink', 'brown'], arrowsize=20)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aslında grafın değişik öğeleri değişik fonksiyonlarla oluşturulabilmektedir. Bu fonksiyonlar şunlardır:
draw_networkx_nodes (düğümleri çizer)
draw_networkx_edges (yolları çizer)
draw_networkx_labels (düğüm içerisindeki yazıları yazar)
draw_networkx_edge_labels (yollardaki yazıları yazar)
Aslında draw metodu bu metotları kullanarak çizimi yapmaktadır. Örneğin biz çizimi aslında tek tek bu metotların bileşimi ile
yapabiliriz:
pos = nx.kamada_kawai_layout(g)
nx.draw_networkx_nodes(g, pos, node_size=1200, node_color=['yellow', 'brown', 'yellow', 'red', 'purple', 'gray', 'pink', 'green', 'magenta', 'red'], alpha=0.5)
nx.draw_networkx_labels(g, pos, font_color='black', font_weight='bold')
nx.draw_networkx_edges(g, pos, width=2, node_size=1200)
nx.draw_networkx_edge_labels(g, pos, edge_labels=nx.get_edge_attributes(g, 'length'), font_weight='bold', font_color='red', label_pos=0.4)
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.DiGraph(ntitle='Test Graph')
g.add_node('A')
g.add_node('B')
g.add_node('C')
g.add_node('D')
g.add_edge('A', 'B', length=150)
g.add_edge('A', 'C', length=125)
g.add_edge('C', 'D', length=15)
g.add_edge('A', 'F', length=175)
g.add_edge('E', 'D', length=65)
g.add_edge('E', 'F', length=90)
g.add_edge('A', 'E', length=85)
g.add_edge('F', 'B', length=185)
g.add_edge('B', 'E', length=60)
g.add_edge('G', 'D', length=72)
g.add_edge('D', 'G', length=200)
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 10))
pos = nx.circular_layout(g)
nx.draw(g, pos=pos, with_labels=True, node_size=1500, font_size=24, node_color=['yellow', 'green', 'red', 'magenta', 'yellow', 'pink', 'brown'], arrowsize=20)
nx.draw_networkx_edge_labels(g, pos=pos, font_size=20, edge_labels=nx.get_edge_attributes(g, 'length'))
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Birden fazla yol da tek hamlede add_edges_from metoduyla grafa eklenebilir. Bu metodun birinci parametresi iki elemanlı demetleri
içeren bir liste olmalıdır. Örneğin:
g.add_edges_from([('A', 'B'), ('A', 'C'), ('C', 'A'), ('B', 'D')])
Aslında add_edges_from metoduna girilen liste üçlü demetlerden de oluşturulabilir. Bu durumda demetlerin üçüncü elemanları
ilgili yola ilişkin bilgileri içeren sözlük nesneleri olmak zorundadır. Örneğin:
g.add_edges_from([('A', 'B', {'length': 100}), ('A', 'C', {'length': 150}), ('C', 'D', {'length': 200}),
('B', 'D', {'length': 130}), ('C', 'B', {'length': 180}), ('D', 'A', {'length': 220})])
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.Graph(ntitle='Test Graph')
g.add_edges_from([('A', 'B', {'length': 100}), ('A', 'C', {'length': 150}), ('C', 'D', {'length': 200}), ('B', 'D', {'length': 130}), ('C', 'B', {'length': 180}), ('D', 'A', {'length': 220})])
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 8))
pos = nx.circular_layout(g)
nx.draw(g, pos=pos, with_labels=True, node_size=1500, font_size=24, node_color='green', arrowsize=20)
nx.draw_networkx_edge_labels(g, pos=pos, font_size=20, edge_labels=nx.get_edge_attributes(g, 'length'),)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Bir graftaki yolların hepsi edges isimli örnek özniteliği ile elde edilebilir. Bu edges elemanı bize yine sözlük gibi kullanılan
bir nesne vermektedir. Biz graftaki belli bir yolun bilgilerini köşeli parantez ile ya da get metodu ile iki düğümün anahtarını vererek
elde edebiliriz. Örneğin:
val = g.edges['A', 'B']
Burada biz A ile B düğümleri arasındaki yolun bilgilerini bir sözlük nesnesi olarak elde etmiş olduk. Tabii bu işlem aslında
Python'da aşağıdaki ile eşdeğerdir:
val = g.edges[('A', 'B')]
Yine edges örnek özniteliği ile biz get metodunu kullanabiliriz. Ancak metodun birinci parametresi iki elemanlı bir demet
olmalıdır. Örneğin:
val = g.edges.get(('A', 'B'), 'Not found')
Burada A ve B düğümleri arasındaki yolun bilgileri elde edilmek istenmiştir. Eğer böyle bir yol yoksa metot exception frlatmak
yerine 'Not Found' yazısını geri döndürecektir. Yine edges örnek özniteliği ile birlikte biz sınıfın keys ve values metotlarını kullanabiliriz.
keys bize yollara ilişkin düğümleri ikişerli demet biçiminde dolaşılabilir bir nesne olarak verir. Örneğin:
for t in g.edges.keys():
print(t)
Tabii yollara ilişkin düğümleri unpack yaparak da elde edebiliriz:
for node1, node2 in g.edges.keys():
print(node1, node2)
values metodu da bize yollara ilişkin bilgileri temsil eden sözlük nesnelerini dolaşmakta kullanabileceğimiz dolaşılabilir
bir nesne vermektedir.
Örneğin:
for d in g.edges.values():
print(d)
Burada her dolaşımda bir yola iliştirilen değerlerin sözlük nesneleri elde edilecektir.
Tabii edges elemanı ile items metodu da çağrılabilir. Bu metot bize yola ilişkin anahtarları ve yola ilişkin bilgilerin
bulunduğu sözlük nesnesini dolaşılabilir bir nesne oalrak verecektir. Örneğin:
for t in g.edges.items():
print(t)
Burada biz her dolaşıma iki elemanlı bir demet elde ederiz. Demetin ilk elemanı yola ilişkin iki düğümün anahtarlarını,
ikinci elemanı ise o düğüme iliştirilen sözlük nesnesini belirtmektedir. Tabii biz unpack işlemi de yapabilirdik:
for (node1, node2), d in g.edges.items():
print(node1, node2, d)
#----------------------------------------------------------------------------------------------------------------------------
Yönlü graflar DiGraph sınıfıyla temsil edilmektedir. Bu sınıfın Graph sınıfından farkı yolların yönlere sahip olmasıdır.
DiGraph sınıfının genel kullanımı aynıdır. Graflar multi değilse iki düğüm arasına ancak tek bir yol eklenebilir. Ancak
iki düğüm arasına birdenfazla yol klenmek istendiğinde bir exception oluşmaz. Yola ilişkin bilgiler yeni eklenen bilgilerle güncellenir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yönlü graflarda bir düğümden gidilebilecek düğümler Graph sınıflarının successors netotlarıyla bir düğüme gelinecek düğümler predecessors
metotlarıyla elde edilebilmektedir. Örneğin:
for node in g.predecessors('A'):
print(node)
for node in g.successors('A'):
print(node)
Yönsüz graflarda bir düğüm ile bağlantılı olan düğümler adj örnek özniteliği ile elde edilmektedir. adj örnek özniteliği
bir sözlük gibi davranmaktadır.
Örneğin:
for node in g.adj['B']:
print(node)
Burada A düğümünün doğrudan bağlı olduğu düğümler elde edilmiştir. Yönlü graflarda adj örnek özniteliği successors metodu
ile aynı düğümleri vermektedir. (Yani ilgili düğümden çıkan düümleri)
Graph sınıflarının adjecency isimli metotları bize bir sözlük giçiminde tüm düğümlerin komşu düğümlerini vermektedir.
Bazen grafta belli bir yolun olup olmadığını anlamak da isteyebiliriz. Bunun için Grapgh sınıflarının has_edge metotları kullanılmaktadır.
Bir grafta her düğüme giren ve çıkan toplam yol sayısı Graph sınıflarının degree metotlarıyla elde edilebilmektedir.
Bu metot parametreli bir biçimde de kullanılabilmektedir.
Bir düğüme gelen yollar yönlü Graph sınıflarının in_edges metotlarıyla çıkan yollar out_edges metotlarıyla elde edilebilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Graflardan düğümler ve yollar silinebilir. Graph sınıflarının remove_node isimli metotları graftan belli bir düğümü
silmek için kullanılmaktadır. Graftan bir düğüm silinirse onunla ilişkli olan bütün yollar da silinmektedir. Benzer biçimde
bir yolu silmek için ise Graph sınıflarının remove_edge metotları kullanılır. Tabii yolsuz düğüm olabileceğine göre bir yolun silinmesi ona ilişkin düğümlerin silinmesine yol açmamaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.DiGraph(ntitle='Test Graph')
g.add_edge('A', 'B', length=10)
g.add_edge('A', 'C', length=20)
g.add_edge('C', 'D', length=30)
g.add_edge('D', 'A', length=40)
g.add_edge('C', 'B', length=35)
g.add_edge('D', 'B', length=40)
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 8))
pos = nx.circular_layout(g)
nx.draw(g, pos=pos, with_labels=True, node_size=1500, font_size=24, node_color='green', arrowsize=20)
nx.draw_networkx_edge_labels(g, pos=pos, font_size=20, edge_labels=nx.get_edge_attributes(g, 'length'),)
plt.show()
g.remove_edge('A', 'B')
plt.figure(figsize=(8, 8))
pos = nx.circular_layout(g)
nx.draw(g, pos=pos, with_labels=True, node_size=1500, font_size=24, node_color='green', arrowsize=20)
nx.draw_networkx_edge_labels(g, pos=pos, font_size=20, edge_labels=nx.get_edge_attributes(g, 'length'),)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Graf dünyasında "komşulıl matrisi (adjacency matrix)" demek iki boyutlu binary bir matris demektir. Bu matriste
0 "iki düğüm arasında yol yok", 1 ise ",iki düğüm arasında yol var" anlamına gelmektedir. İndeksler 0'dan başlatılır. Örneğin:
01100
00110
01011
01000
10100
komşuluk matrisinden bir graf oluşturmak için from_numpy_aray fonksiyonu kullanılabilir. Fonksiyonun birinci parametresi
Numpy dizisini, belirtir. create_using parametresi yaratılacak grafın türünü belirtmektedir.
Aşağıdaki örnekte komşuluk matrisinden hareketle graf oluşturulmuştur.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
import numpy as np
a = np.array([[0, 1, 1, 0, 0], [0, 0, 1, 1, 0], [0, 1, 0, 1, 1], [0, 1, 0, 0, 0], [1, 0, 1, 0, 0]])
g = nx.from_numpy_array(a, create_using=nx.DiGraph)
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 8))
pos = nx.circular_layout(g)
nx.draw(g, pos=pos, with_labels=True, node_size=1500, font_size=24, node_color='green', arrowsize=20)
nx.draw_networkx_edge_labels(g, pos=pos, font_size=20, edge_labels=nx.get_edge_attributes(g, 'length'),)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte rastgele 0 ve 1'lerden oluşan bir NumPy dizisi oluştrulup bundan graf elde edilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
import numpy as np
a = np.random.randint(0, 2, (10, 10))
print(a)
g = nx.from_numpy_array(a, create_using=nx.DiGraph)
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 8))
pos = nx.circular_layout(g)
nx.draw(g, pos=pos, with_labels=True, node_size=1500, font_size=24, node_color='green', arrowsize=20)
nx.draw_networkx_edge_labels(g, pos=pos, font_size=20, edge_labels=nx.get_edge_attributes(g, 'length'),)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
from_edgelist isimli fonksiyon bir ikili demetlerden oluşan bir Python listesi alır. Her demet bir yolu belirtmektedir.
Tabii burada düğüm anahtarlarının sayı olması ve 0'dan başlaması gerekmez.
Aşağıdaki from_edgelist fonksiyonun kullanımına ilişkin örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
a = [(0, 1), (1, 3), (2, 4), (4, 2), (2, 1), (3, 4), (3, 0), (4, 1), (2, 3), ('A', 3), ('B', 0)]
print(a)
g = nx.from_edgelist(a, create_using=nx.DiGraph)
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 8))
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos=pos, with_labels=True, node_size=1500, font_size=24, node_color='green', arrowsize=20)
nx.draw_networkx_edge_labels(g, pos=pos, font_size=20, edge_labels=nx.get_edge_attributes(g, 'length'),)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
from_pandas_adjecency ve from_pandas_edlist fonksiyonları Pandas ile çalışmak üzere düşünülmüştür. Benzer şekilde from'lu fonksiyonların
ters işi yapan to'lu biçimleri de vardır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bir grafın dosyadan okunması için çeşitli fonksiyonlar bulundurulmuştur. read_adjlist fonksiyonu aşağıdaki formata uygun
biçimdeki dosyadan okuma yapar:
a b c d
b c d
...
Burada satırın ilk elemanı düğümü belirtmektedir. Satırdaki diğer elemanlar ise o düğümden bağlantıları belirtir. Yani
yukarıdaki dosyada a->b, a->c, a->d, b->c, b->d bağlantıları vardır.
Aşağıdaki örnekte kullanılan "test.txt" dosyasının içeriği şöyledir:
A B C
C B A
B C D
D A C B
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.read_adjlist('test.txt', create_using=nx.DiGraph)
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 8))
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos=pos, with_labels=True, node_size=1500, font_size=24, node_color='green', arrowsize=20)
nx.draw_networkx_edge_labels(g, pos=pos, font_size=20, edge_labels=nx.get_edge_attributes(g, 'length'),)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
read_multiline_adjlist fonksiyonu yukarıdakine benzerdir. Ancak her satırda aşağıdaki kaç satırın o satır ile bağlantılı
olduğu bilgisi belirtilir. Örneğin:
a 2
b
c
d 1
e
Burada a düğümü aşağıdaki iki satırla bağlantılıdır. Yani a->b ve a->c bağlantısı vardır. Bnezer biçimde d ile yalnızca e
bağlantılıdır.
Aşağıdaki örnekte kullanılan "test.txt" dosyasının içeriği şöyledir:
A 5
B
C
D
E
F
B 2
C
D
C 3
A
D
F
D 2
A
B
E 3
A
C
D
F 1
E
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.read_multiline_adjlist('test.txt', create_using=nx.DiGraph)
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 8))
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos=pos, with_labels=True, node_size=1500, font_size=24, node_color='green', arrowsize=20)
nx.draw_networkx_edge_labels(g, pos=pos, font_size=20, edge_labels=nx.get_edge_attributes(g, 'length'),)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
read_edgelist isimli fonksiyon her satırında aşağıdaki gibi bir yol bilgisi olan dosyayı okumaktadır:
A B {'weight': 10}
B C {'weight': 30}
C D
...
Burada iki düğüm anahtarının yaında istenirse bir sözlük formatıyla yolun özellikleri belirtilebilir.
Aşağıdaki örnek için oluşturulmuş olan "test.txt" dosyası şöyledir:
A B {'weight': 10}
B C {'weight': 30}
C A {'weight': 40}
D A {'weight': 50}
D A
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.read_edgelist('test.txt', create_using=nx.DiGraph)
import matplotlib.pyplot as plt
plt.figure(figsize=(8, 8))
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos=pos, with_labels=True, node_size=1500, font_size=24, node_color='green', arrowsize=20)
nx.draw_networkx_edge_labels(g, pos=pos, font_size=20, edge_labels=nx.get_edge_attributes(g, 'length'),)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Graflar üzerinde test işlemleri için başkaları tarafından oluşturulmuş pek çok veri kümesi vardır. Bunlardan biri "Knut-Miles"
isimli veri kümesidir. Bu veri kümesinin orijinal formatı biraz karmaşıktır. Bu kurs çerçevesinde orijinal format kurs dizininde
iki farklı biçime dönüştürülmüştür:
knut-miles.csv
knut-miles-weghted.csv
Birinci dosya read_adjlist fonksiyonuyla, ikinci dosya ise read_weighted_edgelist fonksiyonuyla okunmalıdır. İkinci dosya
düğümler arasındaki uzaklığa ilişkin bir bilgi de içermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bizim graf veri yapısını oluşturmamızın nedeni bu veri yapısı üzerinde algoritmaları uygulamaktır. Örneğin şehirler düğümleri
şehirler arasındaki yollar grafın yollarını temsil edebilir. Şehirlerarası uzaklıklar da yollara iliştilien bir bilgi durumunda olabilir.
Biz de bir şehirden diğerine en kısa yolu bulmak isteyebiliriz. Daha önceden de belirttiğimiz gibi aslında yüzlerce grafik algoritması vardır.
Ancak bunların birkaç tanesi ile gerçek hayatta çok fazla karşılaşılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
En kısa yol problemi shortest_path isimli fonksiyonla çözülmektedir. Bu fonksiyon bizden grafı, kaynak ve hedef düğüm isimlerini
ve yol uzunluklarını belirten weight parametresini almaktadır. weight parametresi köşelere iliştirilen hangi bilginin yol
uzunluğu olduğunu belirlemekte kullanılır. Örneğin weight='length' yol uzunluğunun kenara iliştirilen 'length' bilgisi olduğu anlamına gelmektedir. Eğer bu parametre girilmezse bu durumda 'weight' isimli bilgi default olarak dikkate alınır. Eğer
ilgili yolda 'weight' isimli bir bilgi yoksa uzunluk 1 alınmakltadır. shortest_path fonksiyonu geri dönüş değeri olarak bize
iki düğüm arasındaki en kısa yolu bir düğüm anahtarlarından oluşan bir liste biçiminde vermektedir. En kısa yolun uzunluğu
ilgili yolun uzunluğu hesaplanarak bulunabilir. Ancak bunun için de shortest_path_length isimli bir fonksiyon bulundurulmuştur.
Bu fonksiyonlarda kaynak düğüm belirtilmezse her düğümden hedefe giden en kısa yollar biz sözlük nesnesi olarak verilmektedir.
Eğer hedef düğüm belirtilmezse bu duurmda kaynaktan her düğüme gidilen en kısa yollar bulunmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.Graph(name='My Graph')
g.add_node('A', count=10)
g.add_node('B', count=20)
g.add_node('C', lenght=30)
g.add_node('D', length=40)
g.add_edge('A', 'B', length=150)
g.add_edge('C', 'A', length=125)
g.add_edge('D', 'C', length=15)
g.add_edge('A', 'F', length=175)
g.add_edge('E', 'D', length=65)
g.add_edge('E', 'F', length=90)
g.add_edge('A', 'E', length=140)
g.add_edge('F', 'B', length=185)
g.add_edge('B', 'E', length=60)
g.add_edge('D', 'G', length=200)
g.add_edges_from([('H', 'I'), ('I', 'J'), ('H', 'A')], length=128)
g.add_edges_from([('J', 'H', {'length': 143}), ('J', 'C', {'length': 450}), ('E', 'C', {'length': 148})])
g.add_edge('G', 'I', length=200)
import matplotlib.pyplot as plt
figure = plt.gcf()
figure.set_size_inches((12, 12))
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos, node_size=1200, with_labels=True, node_color=['yellow', 'brown', 'yellow', 'red', 'purple', 'gray', 'pink', 'green', 'magenta', 'red'])
nx.draw_networkx_edge_labels(g, pos, edge_labels=nx.get_edge_attributes(g, 'length'), font_weight='bold', font_color='red', label_pos=0.4)
plt.show()
result = nx.shortest_path(g, 'F', 'I', weight='length')
print(result)
length = nx.shortest_path_length(g, 'F', 'I', weight='length')
print(f'Shortest path from F to I: {length}')
all_shortest_path_from_F = nx.shortest_path(g, 'F', weight='length')
print(all_shortest_path_from_F)
all_shortest_path_to_F = nx.shortest_path(g, target='F', weight='length')
print(all_shortest_path_to_F)
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda Knut-Miles grafında iki şehir arasındaki en kısa yol bulunmuştur.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.Graph(name='My Graph')
g.add_node('A', count=10)
g.add_node('B', count=20)
g.add_node('C', lenght=30)
g.add_node('D', length=40)
g.add_edge('A', 'B', length=150)
g.add_edge('C', 'A', length=125)
g.add_edge('D', 'C', length=15)
g.add_edge('A', 'F', length=175)
g.add_edge('E', 'D', length=65)
g.add_edge('E', 'F', length=90)
g.add_edge('A', 'E', length=140)
g.add_edge('F', 'B', length=185)
g.add_edge('B', 'E', length=60)
g.add_edge('D', 'G', length=200)
g.add_edges_from([('H', 'I'), ('I', 'J'), ('H', 'A')], length=128)
g.add_edges_from([('J', 'H', {'length': 143}), ('J', 'C', {'length': 450}), ('E', 'C', {'length': 148})])
g.add_edge('G', 'I', length=200)
import matplotlib.pyplot as plt
figure = plt.gcf()
figure.set_size_inches((12, 12))
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos, node_size=1200, with_labels=True, node_color=['yellow', 'brown', 'yellow', 'red', 'purple', 'gray', 'pink', 'green', 'magenta', 'red'])
nx.draw_networkx_edge_labels(g, pos, edge_labels=nx.get_edge_attributes(g, 'length'), font_weight='bold', font_color='red', label_pos=0.4)
plt.show()
result = nx.shortest_path(g, 'F', 'I', weight='length')
print(result)
length = nx.shortest_path_length(g, 'F', 'I', weight='length')
print(f'Shortest path from F to I: {length}')
all_shortest_path_from_F = nx.shortest_path(g, 'F', weight='length')
print(all_shortest_path_from_F)
all_shortest_path_to_F = nx.shortest_path(g, target='F', weight='length')
print(all_shortest_path_to_F)
#----------------------------------------------------------------------------------------------------------------------------
En küçük örten ağaç (minimum spanning tree) gerçek hayatta da uygulamasına rastlanılan diğer önemli bir graf problemidir.
Burada graftaki tüm düğümlere erişen minimum uzunlukta bir ağaç elde edilmeye çalışılır. Bu ağaç tüm düğümleri içermelidir.
Gerçek hayatta bu problem bütün noktaları birbirine bağlayan minimum malzeme harcanacak bağlantının tespit edilmesi amacıyla kullaılmaktadır. Örneğin biz tüm evlere su borusu bağlamak isteyebiliriz. Boruyu bir eve döşediğimiz zaman oradan devam edip
başka bir eve de döşeyebiliriz. Bir biçimde su tüm evlere girmelidir. Ancak bu yapılırken de minimum boru döşenmelidir.
Networkx kütüphanesinde en küçük örten ağaö için minimum_spannig_edges isimli bir fonksiyon bulundurulmuştur.
Aşağıdaki örnekte en küçük örten ağaç bulunmuş ve farklı bir renkte gösterilmiştir. draw_networkx_edges fonksiyonunun edgelist parametresi bir demet listesi biçiminde girilirse (ki zaten minimum_sapnning_edge fonksi,yonu öyle bir çıktı vermektedir)
bu durumda fonksiyon yalnızca o edge'leri çizmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.Graph(name='My Graph')
g.add_node('A', count=10)
g.add_node('B', count=20)
g.add_node('C', lenght=30)
g.add_node('D', length=40)
g.add_edge('A', 'B', length=150)
g.add_edge('C', 'A', length=125)
g.add_edge('D', 'C', length=15)
g.add_edge('A', 'F', length=175)
g.add_edge('E', 'D', length=65)
g.add_edge('E', 'F', length=90)
g.add_edge('A', 'E', length=140)
g.add_edge('F', 'B', length=185)
g.add_edge('B', 'E', length=60)
g.add_edge('D', 'G', length=200)
g.add_edges_from([('H', 'I'), ('I', 'J'), ('H', 'A')], length=128)
g.add_edges_from([('J', 'H', {'length': 143}), ('J', 'C', {'length': 450}), ('E', 'C', {'length': 148})])
g.add_edge('G', 'I', length=200)
import matplotlib.pyplot as plt
figure = plt.gcf()
figure.set_size_inches((12, 12))
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos, node_size=1200, with_labels=True, node_color=['yellow', 'brown', 'yellow', 'red', 'purple', 'gray', 'pink', 'green', 'magenta', 'red'])
nx.draw_networkx_edge_labels(g, pos, edge_labels=nx.get_edge_attributes(g, 'length'), font_weight='bold', font_color='red', label_pos=0.4)
plt.show()
mst = nx.minimum_spanning_edges(g, weight='length')
list_mst = list(mst)
print(f'Minimum spanning tree: {list_mst}')
figure = plt.gcf()
figure.set_size_inches((10, 10))
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos, node_size=1200, with_labels=True, node_color=['yellow', 'brown', 'yellow', 'red', 'purple', 'gray', 'pink', 'green', 'magenta', 'red'])
nx.draw_networkx_edge_labels(g, pos, edge_labels=nx.get_edge_attributes(g, 'length'), font_weight='bold', font_color='red', label_pos=0.4)
nx.draw_networkx_edges(g, pos, edgelist=list(list_mst), width=5, edge_color='red')
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Diğer çok karşılaşılan bir graf problemi de "maximum akış (maximum flow)" isimli problemdir. Bu problemde belli bir kaynak
düğümden hedef düğüme su taşınmaktadır. Bu sular borular kullanılarak düğümden düğüme iletilir. Ancak boruların kapasiteleri farklıdır. Amaç kaynaktan hedefe en yüksek suyu taşımak için düğümlerden hangi düğümlere ne kadar kapasite kullanılarak suyun taşınacağıdır. Buradaki kısıt iki düğüm arasındaki yolda belirtilen kapasitenin aşılmamasıdır. networkx kütüphanesinde bunun
için maximum_flow isimli bir fonksiyon bulundurulmuştur. Bu fonksiyon bize kaynaktan hedefe toplam taşınan su miktarını ve
hangi düğümden hangi düğüme hangi miktarda su taşındığını bir demet olarak vermektedir. Tabii buradaki su bir kavramdır. Su
yerine elektirk tellerindeki kapasite ya da başka bir oldu da söz konusu olabilir. Problem gerçek hayattaki pek çok olguya uygulanabilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.Graph(name='My Graph')
g.add_node('A', count=10)
g.add_node('B', count=20)
g.add_node('C', lenght=30)
g.add_node('D', length=40)
g.add_edge('A', 'B', length=150)
g.add_edge('C', 'A', length=125)
g.add_edge('D', 'C', length=15)
g.add_edge('A', 'F', length=175)
g.add_edge('E', 'D', length=65)
g.add_edge('E', 'F', length=90)
g.add_edge('A', 'E', length=140)
g.add_edge('F', 'B', length=185)
g.add_edge('B', 'E', length=60)
g.add_edge('D', 'G', length=200)
g.add_edges_from([('H', 'I'), ('I', 'J'), ('H', 'A')], length=128)
g.add_edges_from([('J', 'H', {'length': 143}), ('J', 'C', {'length': 450}), ('E', 'C', {'length': 148})])
g.add_edge('G', 'I', length=200)
import matplotlib.pyplot as plt
figure = plt.gcf()
figure.set_size_inches((12, 12))
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos, node_size=1200, with_labels=True, node_color=['yellow', 'brown', 'yellow', 'red', 'purple', 'gray', 'pink', 'green', 'magenta', 'red'])
nx.draw_networkx_edge_labels(g, pos, edge_labels=nx.get_edge_attributes(g, 'length'), font_weight='bold', font_color='red', label_pos=0.4)
plt.show()
maximum_flow = nx.maximum_flow(g, 'F', 'I', capacity='length')
print(maximum_flow)
#----------------------------------------------------------------------------------------------------------------------------
Graf boyama denilen graf algoritmasının değişik biçimleri vardır. Tipik olarak bu problem bir haritadaki komşu şehirlerin
farklı renkte boyanmasına ilişkin bir bir problem olarak betimlenir. Örneğin Türkiye iller haritasında her şehir bir renkle gösteriliyor olsun. Burada biz komşu iki şehri aynı renkle gösteremeyiz. Şüphesiz her için ayrı bir renk kullanılabilir.
Ancak bu problemde boyama renklerinin bir maliyeti olduğu için minimum sayıda rengin kullanılması istenir. networkx kütüphanesinde değişik algoritmik yöntemlerle graf boyaması yapan metotlar vardır. Örneğin equatable_color isimli metot bizim belirlediğimiz
sayıda renkle graf düğümlerini boyamaktadır. Bu metot bir sözlük nesnesine geri döner. Sözlüğün anahtarları graf düğümlerinin
isimlerindee değerleri de renk indekslerinden oluşmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.Graph(name='My Graph')
d = [('A', 'B', {'length': 3}), ('A', 'D', {'length': 6}), ('A', 'E', {'length': 9}), ('B', 'C', {'length': 2}), ('B', 'D', {'length': 4}), ('B', 'E', {'length': 9}), ('C', 'D', {'length': 2}), ('C', 'F', {'length': 8}), ('C', 'G', {'length': 9})]
g.add_edges_from(d)
import matplotlib.pyplot as plt
figure = plt.gcf()
figure.set_size_inches((12, 12))
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos, node_size=1200, with_labels=True)
nx.draw_networkx_edge_labels(g, pos, edge_labels=nx.get_edge_attributes(g, 'length'), font_weight='bold', font_color='red', label_pos=0.4)
plt.show()
result = nx.equitable_color(g, 5)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Gerçek hayatta sıkça karşılaşılan diğer bir graf problemi de "gezgin satıcı problemi (traveling salesman problem (TSP))"
denilen problemdir. Bu problemde bir satıcı merkezden çıkarak birtakım şehirlere uğrayıp yeniden merkeze dönmektedir. Problemde
her şehre yalnızca bir kez gidilmektedir. Gezgin satıcının bu şehirlerin hepsini dolaşıp geriye dönmesini sağlayacak pek
çok alternatif rota vardır. Problemde bu rotaların en kısası bulunmaya çalışılmaktadır. Problem başka alanlarda da dolaylı
bir biçimde karşımıza çıkabilmektedir. Örneğin biigisayar ekranında görsel olarak oluşturulan deliklerin gerçek bir otomatik matkapla delinmesi durumunda delinecek noktalardın hepsine matkabın uğraması gerekir. Ancak kat edilecek mesafenin en küçüklenmesi istenmektedir. Bu tarz problemlere algoritmalar dünyasında NP (Nonpolynomial) tarzı problemler denilmektedir. Ve bu tür
problemlerin çözüm alanı üstel ya da faktöryelsel biçimde artmaktadır. Gezgin satıcı probleminde her şehir ile her şehir
arasında yol varsa tüm olası rotaların toplamı (n - 1)! / 2 kadardır. 100 tane şehir için bile sayı çok çok fazladır.
Networkx kütüphanesinde gezgin satıcı problemi networkx.algorithms.approximation paketindeki traveling_salesman_problem
fonksiyonuyla gerçekleştirilmiştir. Fonksiyon grafı alır ve minimum turu bize verir. Tuda aslında nereden başlandığının
bir önemi yoktur.
Aşağıdaki örnekte rastgele tüm düğümleri birbirine bağlı bir graf oluşturulmuş ve o grafın üzerinde gezgin satıcı problemi uygulanmıştır. Fonksiyonda satıcının başlangıç şehri belirtilmemektedir. Çünkü elde edilen en kısa rotada başlangıç şehrinin
neresi olduğunun önemi yoktur.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
import numpy as np
g = nx.complete_graph(['A', 'B', 'C', 'D', 'E', 'F', 'G'])
for edge in g.edges:
g.edges[edge]['length'] = np.random.randint(0, 100)
import matplotlib.pyplot as plt
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos, node_size=1200, with_labels=True)
nx.draw_networkx_edge_labels(g, pos, edge_labels=nx.get_edge_attributes(g, 'length'), font_weight='bold', font_color='red', label_pos=0.4)
plt.show()
from networkx.algorithms.approximation import traveling_salesman_problem
result = traveling_salesman_problem(g, weight='lenght')
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Networkx kütüphanesinde test amacıyla rastgele graf üreten çok sayıda hazır fonksiyon vardır. Ancak bu fonksiyonlar düğümlere
ya da yollara bilgi iliştirmezler bilginin programcı tarafından iliştirilmesi gerekir. Örneğin complete_graph fonksiyonu tüm düğümleri birbirine bağlı
bir graf oluşturmaktadır. Fonksiyon parametre olarak düğüm anahtarlarını ya da bir sayı alır. Eğer fonksiyona argüman olarak
bir sayı geçilirse fonksiyon düğümlere sırasıyla 0, 1, 2, ... isimlerini vererek n düğümlü bir graf oluşturur.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
import numpy as np
g = nx.complete_graph(10)
for edge in g.edges:
g.edges[edge]['length'] = np.random.randint(0, 100)
import matplotlib.pyplot as plt
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos, node_size=1200, with_labels=True)
nx.draw_networkx_edge_labels(g, pos, edge_labels=nx.get_edge_attributes(g, 'length'), font_weight='bold',
font_color='red', label_pos=0.4)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
cycle_graph fonksiyonu n tane düğümden oluşan ve bir tur oluşturan bir fonksiyondur.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.cycle_graph(10)
import matplotlib.pyplot as plt
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos, node_size=1200, with_labels=True)
nx.draw_networkx_edge_labels(g, pos, edge_labels=nx.get_edge_attributes(g, 'length'), font_weight='bold',
font_color='red', label_pos=0.4)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
star_graph fonksiyonu bir merkezden diğer düğümlere tek bir yolun olduğu bir graf üretir.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.star_graph(10)
import matplotlib.pyplot as plt
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos, node_size=1200, with_labels=True)
nx.draw_networkx_edge_labels(g, pos, edge_labels=nx.get_edge_attributes(g, 'length'), font_weight='bold',
font_color='red', label_pos=0.4)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
wheel_graph fonksiyonu çıtalı tekerler görünümüne sahip bir graf üretir.
#----------------------------------------------------------------------------------------------------------------------------
import networkx as nx
g = nx.wheel_graph(10)
import matplotlib.pyplot as plt
pos = nx.kamada_kawai_layout(g)
nx.draw(g, pos, node_size=1200, with_labels=True)
nx.draw_networkx_edge_labels(g, pos, edge_labels=nx.get_edge_attributes(g, 'length'), font_weight='bold',
font_color='red', label_pos=0.4)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Çeşitli dokümanlarda birtakım graf resimleri oluşturmak için genel amaçlı graf çizim programları oluşturulmuştur. Bunlardan
en ünlüsü ve en yaygın kullanılanı "graphviz" denilen programdır. Graphviz açık kaynak kodlu bir yazılımdır. Dokümanlarda
gördüğümüz grafa benzer çizimlerin çoğu bu utility programla yapılmaktadır.
Graphviz "dot" denilen mini bir dil kullanmaktadır. Kullanıcı bu "dot" dilini kullanarak belirlenmiş kurallara göre bir
script oluşturur. Bunu bir dosya biçiminde save eder. Geleneksel olarak bir "dot" script dosyalarının uzantıları "gv" ya da
"dot" verilmektedir. Bu dosyayı oluşturduktan sonra resim dpsyasının üretilmesi için dot programının aşağıdaki örnekteki gibi çalıştırılması gerekir:
dot -T png sample.gv -o sample.png
Örnek bir script dosyası şöyle olabilir:
digraph G {
main -> parse -> execute;
main -> init;
main -> cleanup;
execute -> make_string;
execute -> printf
init -> make_string;
main -> printf;
execute -> compare;
compare -> cleanup;
}
graphviz utility'sinin dokümantasyonu aşağıdaki bağlantıda bulunmaktadır:
https://graphviz.org/documentation/
#----------------------------------------------------------------------------------------------------------------------------
Python'da graphviz utility'si ile programlama yoluyla işlem yapabilmek için graphviz isimli bir kütüphane bulundurulmuştur.
Tabii bu kütüphane Python'ın standart bir kütüphanesi değildir. Dolayısıyla aşağıdaki gibi manuel biçimde install edilmelidir:
pip install graphviz
Aşağıdaki bağlantıda Python graphviz kütüphanesinin dokümantasyonu bulunmaktadır.
https://graphviz.readthedocs.io/en/stable/manual.html
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Python graphviz kütüphanesi aslında "dot" dili kullanılarak script yazma işleminin programlama yoluyla yapılmasını sağlamaktadır.
Ktüphanenin ayrıntılı kullanımı için orijinal dokümanlara bakılabilir. Aşağıda örnek bir kod verilmiştir:
#----------------------------------------------------------------------------------------------------------------------------
import graphviz
dot = graphviz.Digraph('Test Graph', format='png')
dot.node('A')
dot.node('B')
dot.node('C')
dot.edge_attr.update(arrowhead='vee', arrowsize='2')
dot.edge('A', 'B', label='100')
dot.edge('B', 'C', label='200')
dot.edge('A', 'D', label='300')
print(dot.source)
dot.render('test.dot')
#----------------------------------------------------------------------------------------------------------------------------
Makine öğrenmesinde kimi zaman tercih edilebilecek yöntemlerden biri de "karar ağaçları (decision tress)" denilen yöntemdir.
Bu yöntemin özü oldukça basittir. Yöntemde sütuna dayalı olarak bir soru sorulur. Bu soru ya doğru olur ya da yanlış olur.
Sorunun doğru olduğu durumda ve yanlış olduğu durumda veri kümesi iki parçaya bölünür. Sonra bölünen iki parça için yine
bir sütuna dayalı olarak bir soru sorulur. Böylece veri kümesi yine iki parçaya ayrılır. Böyle böyle işlemler devam ettirilip
bir ağaç elde edilir. Ağaç ikili (binary) bir ağaçtır. Çünkü sorunun yanıtı iki seçenek içermektedir: Doğru ye yanlış.
Bölme ve soru sorma konusundaki detaylar şöyledir:
- Bir soru bir sütuna dayalı olarak sorulur. Sütun kategorikse soru da kategorik olacaktır. Sütun sayısal ise soru da >, < biçiminde
sayısal olacaktır. Örneğin X1 sütunu cinsiyet belirtiyor olsun. Buradaki soru "X1 = "kadın" biçiminde sorulabiir. Bu durumda X1 sütununun
kadın olup olmamasına göre veri kümesi iki kısma ayrılacaktır. Örneğin X2 sütunu uzunluk olabilir. Uzunluk kategorik değildir.
Soru şöyle olabilir: "X2 > 10". Burada da yine veri kümesi X2'nin 10'dan büyük olup olmamasına göre iki parçaya ayrılacaktır.
- Ağaçta her soru sonucunda veri kümesi iki parçaya ayrılır. Her parça için yeniden başka bir soru sorulur. Ağacın iki alt düğümüne
aynı sütuna dayalı soru sormak zorunlu değildir.
- Bir sütuna dayalı sorunun bir kez sorulması da zorunlu değildir. Örneğin X2 > 10 sorusu yukarıda sorulmu olup daha sonra X2 > 30
sorusu aşağıda bir yerde yeniden sorulabilir.
Pekiyi bu soruların sorulup ağacın derinleştirilmesindeki amaç nedir? İşte amaç gitgide düğümlerde homojen (pure) bir durumu olşturmaktır.
Ağacın en aşağısındaki düğümlere "yaprak (leaf)" denilmektedir. Amaç yaprakların homojen (pure) olmasını sağlamktır. Tabii bazen
ağacın yapraklarının homojen olması için çok derinlere inilmesi gerekir. Budurum çeşitli nedenlerden dolayı sitenmeyebilir.
Bu durumda maksimum bir derinlikte işlemler kesilebilir.
Pekiyi ağaç oluşturulduktan sonra kesitirim nasıl yapılacaktır? Kestirim için ağacın tepesinden (ağacın kökü (root) denilmektedir) sorular
sorularak yapraklara kadar ilerlenir. İlgili yaprağa ulaşıldığında o yapraki sınıfların hangisi fazlaysa karar ona göre verilmektedir. Tabii
yukarıda da belirttiğimiz gibi ideal durum yaprakların homojen (pure) olmasıdır. Ancak yapraklar homojen değilse karar gelinen yapraktaki
duruma bakılarak verilir.
Her ne kadar buradaki anlatıma göre karar ağaçları sınıflandırma tarzı problemelere uygun ise de aslında lojistik olmayan regresyon problemlerinde de
benzer biçimde kullanılabilmektedir.
Karar ağaçları yöntemi için en güzel veri kümesi aslında sütunların kategorik olduğu sınıflandırma problemlerine ilişkin veri kümeleridir.
Ancak yukarıda da belirtitğimiz gibi yöntem için sütunlar kategorik olması gerekmez ve yöntem sınıflandırma problemlerinin dışında da kullanılabilmektedir.
Tabii yöntemin en önemli tarafı uygun sorunun uygun sütuna dayalı olarak sorulmasıdır. Bu soru sormanın algoritmik temeli "entropy" denilen
bir kavram ile ilgilidir.
Karar ağaçları da "denetimli (supervised)" bir yöntemdir. Biz eğitim veri kümesi ile eğitimi yaparız. Buradan bir karar ağacı oluştururuz. Sonra
kestirimi bu ağacı kullanarak yaparız.
Karar ağaçları yöntemini kullanırken "özellik ölçeklemesi (feature scaling)" yapmaya gerek yoktur.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
scikit-learn kütüphanesinde karar ağaçları için iki temel sınıf bulundurulmuştur: DecisionTreeClassifier ve DecisionTreeRegressor.
DecisionTreeClassifier sınıfı sınıflandırma problemleri için, DecisionTreeRegressor sınıfı ise lojistik olmayan regresyon problemleri
için kullanılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
DecisionTreeClassifier sınıfının __init__ metodunun parametrik yapısı şöyledir:
class sklearn.tree.DecisionTreeClassifier(*, criterion='gini', splitter='best', max_depth=None, min_samples_split=2,
min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None,
min_impurity_decrease=0.0, class_weight=None, ccp_alpha=0.0)
criterion parametresi bölme konusunda kullanılacak entropy algoritmasını belirtmektedir.Yukarıda da belirttiğimiz gibi temelde
iki algoritma tercih ediilmektedir: "gini impurity" ve "shannon entorpy". Bu parametre "gini" olarak girilirse (default) "gini impurity"
algoritması, "entropy" olarak girilirse "Shannon entropy" algoritması kullanılmaktadır. max_depts parametresine ağacın maksimum yüksekliği
girilebilir. Bu parametre girilmezse yapraklar homojen hale getirilene kadar bölme devam ettirilir. Ancak uygulamacı bunu istemeyip
belli bir maksimum derinlik belirleyebilir.
Nesne yaratıldıktan sonra diğer scikit-learn sınıflarında olduğu gibi önce fit işlemi sonra da predict işlemi uygulanır. Sınıfın score
isimli metodu predict işlemini yapıp accuracy hesaplamaktadır.
Aşağıdaki örnekte "breast cancer" veri kümesi üzerinde DecisionTreeClassifer sınıfı ile karar ağacı yöntemi uygulanmıştır.
Nesne yaratılırken max_depth parametresi girilmediği için tamamen homojen bir yaprak durumu elde edilmiştir. Sonra oluşturulan bu karar ağacından da
bir graphviz dosyası elde edilmiştir. Bunun için sklearn.tree modülündeki export_graphviz fonksiyonu kullanılmıştır. Çrneğin:
export_graphviz(dtc, out_file='breast-cancer.dot')
Sonra da örnekte oluşturulan u graphviz dosyası Python graphviz modülü ile görüntülenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import pandas as pd
df = pd.read_csv("breast-cancer-data.csv")
dataset_x = df.iloc[:, 2:-1].to_numpy()
dataset_y = np.zeros(len(df))
dataset_y[df['diagnosis'] == 'M'] = 1
"""
from sklearn.datasets import load_breast_cancer
bc = load_breast_cancer()
dataset_x = bc.data
dataset_y = bc.target
"""
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier()
dtc.fit(training_dataset_x, training_dataset_y)
predict_result = dtc.predict(test_dataset_x)
print(predict_result)
"""
from sklearn.metrics import accuracy_score
score = accuracy_score(test_dataset_y, predict_result)
print(score)
"""
score = dtc.score(test_dataset_x, test_dataset_y)
print(score)
from sklearn.tree import export_graphviz
export_graphviz(dtc, out_file='breast-cancer.dot')
import graphviz
dot = graphviz.Source.from_file(filename='breast-cancer.dot')
dot.view()
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte zambak veri kümesi üzerinde karar ağacı yöntemi uygulanmıştır. Zambak veri kümesi üç sınıflıdır.
Dolayısıyla yapraklar üç sınıf dikkate alınarak homojen hale getirilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('iris.csv')
dataset_x = df.iloc[:, 1:-1].to_numpy()
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
dataset_y = le.fit_transform(df.iloc[:, -1])
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier()
dtc.fit(training_dataset_x, training_dataset_y)
predict_result = dtc.predict(test_dataset_x)
print(predict_result)
"""
from sklearn.metrics import accuracy_score
score = accuracy_score(test_dataset_y, predict_result)
print(score)
"""
score = dtc.score(test_dataset_x, test_dataset_y)
print(score)
from sklearn.tree import export_graphviz
export_graphviz(dtc, out_file='iris.dot')
import graphviz
dot = graphviz.Source.from_file(filename='iris.dot')
dot.view()
#----------------------------------------------------------------------------------------------------------------------------
Karar ağaçları yalnızca sınıflandırma problemlerinde değil lojistik olmayan regresyon problemlerinde de kullanılabilmektedir.
Ancak lojistik olmayan regresyon problemlerinde karar ağaçları genellikle diğer alternatif yöntemlere göre
daha kötü performans gösterme eğilimindedir. Karar ağaçları lojistik olmayan regresyon problemlerinde kullanılırken yapraklar
eğitim veri kümesindeki y değerlerinden oluşmaktadır. Yani adeta lojistik olmayan regresyon problemindeki y değerleri sürekli
değerler değil birer sınıf belirtiyormuş gibi işlemlere sokulmaktadır. Yani işlem sonucunda eğitim veri kğmesinde olmayan bir değer
elde edilmemektedir.
Karar ağaçlarıyla lojistik olmayan regresyon problemlerini çözmek içi scikit-learn kütüphanesindeki DecisionTreeRgressor
sınıfı kullanılmaktadır. Sınıfın genel kullanımı DecisionTreeClassifier sınıfına benzemektedir. Sınıfın __init__ metodunun parametrik
yapısı şöyledir:
class sklearn.tree.DecisionTreeRegressor(*, criterion='squared_error', splitter='best', max_depth=None, min_samples_split=2,
min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None,
min_impurity_decrease=0.0, ccp_alpha=0.0)
Buradaki önemli parametreler DecisionTreeClassifer sınıfı ile aynıdır. Bu sınıf türünden nesne yaratıldıktan sonra yine fit
işlşemi ve predict yapılmaktadır. Sınıfın score metodu bize R^2 değerini bir ölçüt olarak vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte "Boston Hausing Price" veri kümesi üzerinde karar ağaçları uygulanmıştır. Oluşturulan karar ağacı incelendiğinde
yapraklardaki değerlerin eğitim veri kümesindeki y değerlerindne biri olduğu görülecektir. Yani biz bu yöntemle bir evin fiyatını
tahmin ederken her zaman zaten mevcut bir evin fiyatı bize verilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.tree import DecisionTreeRegressor
dtr = DecisionTreeRegressor()
dtr.fit(training_dataset_x, training_dataset_y)
predict_result = dtr.predict(test_dataset_x)
print(predict_result)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(test_dataset_y, predict_result)
print(f'Mean Absolute Error: {mae}')
score = dtr.score(test_dataset_x, test_dataset_y)
print(f'R^2: {score}')
from sklearn.tree import export_graphviz
export_graphviz(dtr, out_file='boston.dot')
import graphviz
dot = graphviz.Source.from_file(filename='iris.dot')
dot.view()
#----------------------------------------------------------------------------------------------------------------------------
Karar ağaçlarının algoritmik yapısı "entropy" kavramına dayanmaktadır. Entropy düzensizliğin bir ölçüsüdür. Homojenite arttıkça
entropi düşer, homojenite azaldıkça entropi artar. Örneğin "+++++++-" bu kümenin entropisi düşüktür. Ancak örneğin "++++----"
bu kümenin entropisi yüksektir. Entropi hesabı için çeşitli yöntemler kullanılabilmektedir. En sık kullanılan yöntem "Shannon entropisi"
ya da "Gini index'i" yöntemidir. Shannon entropisinin formülü şöyledir:
Toplam (-pi * log2 pi)
Karar ağaçlarının gerçekleştiriminde iki önemli karar vardır: Bölme hangi sütuna göre yapılacaktır ve bölme o sütunun neresinden
itibaren yapılacaktır? Önce belli bir sütuna karar verildiğini varsayalım. Bu sütunun neresinden bölme yapılması en uygundur?
İşte algoritma muhtemel bölme yerlerini tespit edip sanki o yerlerden bölme yapılmış gibi bir entropy hesabı yapmaktadır.
Burada yapılan hesap "üst düğümün entoripisinden iki alt düğümün entropileri toplamının çıkartılmasıdır." Bu çakartma değeri en yüksek
olan yerden bölme yapılır. Üst düğümün entropisinden iki alt düğümün entropisi çıkartıldığında değerin yüksek olması demek alt düğümlerin
entropilerinin düşük olması demektir. Alt düğümlerin entropilerinin düşük olması ise alt düğümlerde daha homojen bir durumun oluşması demektir.
O zaman algoritma şöyle davranmaktadır:
1) Alternatif bölme noktalarını oluştur.
2) alternatif bölme noktalarından sanki bölme yapılmış gibi işlemi gerçekleştir ve üst düğümün entropisinden alt düğümlerin toplam
entropisini çıkart en büyük elde edilen değer bölmenin yapılacağı yeri belirtmektedir.
Pekiyi sütun tespit edildikten sonra bölmenin hangi noktalardan yapılacağına nasıl karar verilmektedir? İşte eğer bölmenin yapılacağı sütun
kategorik ise genellikle tüm kategorik değerlerden bölme yapılır. En iyi değer elde edilir. Eğer sütun kategorik değilse bu duurmda
genellikle belli percentile noktalarından bölme denenmektedir. Örneğin %10, %20, %30, ... %70, %80, %90 gibi.
Pekiyi bölme için hangi sütunun seçileceğine nasıl karar verilmektedir? İşte bu kararın pratik bir biçimde verilmesi mümkün değildir.
Mecburen her sütun için yukarıda belirtilen işlemler yapılır toplam en iyi bölme yeri tespit edilir. Yani genellikle önce sütun tespit edilip
sonra bölme yeri tespit edilmemektedir. Tüm sütunlar dikkate alınıp toplam eni değer bulunup o sütunun o noktasından bölme yapılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi karar ağaçları hangi durumlarda tercih edilmelidir? Aslında diğer alternatif yöntemlere göre karar ağaçlarının tercih edilebileceği
bazı durumlar söz konusu olmakla birlikte bu konuda kesin yargılarda bulunabilme olanağı yoktur. En doğrusu çeşitli alternatif yöntemlerin
denenip bir kararın verilmesidir. Ancak karar ağaçları aşağıdaki durumlarda göz önünde bulundurulması gereken bir yöntemdir:
- Karar ağaçları insan düşüncesine yakındır. Dolayısıyla verilen kararın izlediği yol sözcüklerle daha iyi açıklanabilmektedir.
Böyle bir gereksinim varsa karar ağaçları tercih edilebilir.
- Bilgilerin aşama aşama elde edildiği durumlarda (doktor muayenesi gibi) olayın akışı itibari ile karar ağaçları daha uygun bir yöntem olabilmektedir.
Kişiler karar ağaçlarına bakarak işlemleri manuel biçimde uygulayabilirler.
- Karar ağaçları için özellik ölçeklemesi gibi önişlemler gerekmemektedir. Bu da bu önişlemler için vakit kaybının elimine edilmesi
anlamına gelmektedir.
- Karar ağaçları doğrusal olarak ayrıştırılamayan veri kümelerinde kullanılabilir. ÖÇünkü burada ayrıştırma tek bir doğru ile değil
bir grup doğru ile yapılmaktadır.
- İstatistiksel lojistik regresyon uç değerlerden karar ağaçlarına göre daha olumsuz etkilemektedir. Uç değerler söz konusu ise karar ağaçları bir
alternatif olarak düşünülebilir.
- Sütunların kategorik olduyğu durumda karar ağaçları daha iyi performans verebilmektedir. Sütunlarınb çoğunlukla kategorik olduğu
veri kümelerinde karar ağaçları mutlaka gözönüne alınmalıdır.
- Karar ağaçları birikimli bir eğitime uygun değildirç. Yani veri kümesine bir satır eklendiğinde tamamen bütün ağacın yeniden oluşturulması
gerekir.
- Karar ağaçları "overfitting" olgusuna daha açıktır. Özellikle lojistik olmayan regresyon problemlerinde bu durum daha kötü bir hal
alabilmektedir. Yani karar ağaçları eğitim veri kümesine daha bağlı bir yöntemdir. Köçtü bir küme ile eğitim yapılırsa overfitting
ciddi boyutlara varavbilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Makine öğrenmesinde ve istatistikte "ensemble" yöntemler demekle "bir grup yöntemin bir arada kullanılması" anlaşılmaktadır.
"Ensemble" sözcüğü "topluluk" gibi bir anlama gelmektedir. Dolayısıyla Türkçe "topluluksal metotlar", "topluluk öğrenmesi"
gibi isimlerle de ifade edilebilmektedir.
Ensemble yöntemler değişik kategorilere ayrılarak ele alınabilmektedir. Biz burada en fazla kullanılan ensemble yöntemler üzerinde
duracağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Ensemble yöntemlerden en sık kullanılanlardan biri "oylama" denilen yöntemdir. Oylama yöntemi tipik olarak sınıflandırma
problemlerinde kullanılır. Problem değişik yöntemlerle çözülür. Sonra kestirim yapılırken tüm bu yöntemlere göre kestirim yapılır.
En yüksek oyu alan sınıf kestirimin hinahi değeri olarak belirlenir. Örneğin "yes" ve "no" biçiminde ikili sınıflandırma problemi
söz konusu olsun. Biz de bu problemi alternatif olarak A, B, C, D ve E yöntemleriyle çözmüş olalım. Şimdi kesitirim yaparken
kestirimde bu 5 yöntemi ayrı ayrı kullanalım. Şu sonuçşarın elde edildiğini varsayalım:
A -> yes
B -> no
C -> yes
D -> no
E -> yes
Burada oylma sonucunda "yes" değeri "no" değerinden daha fazla oy almıştır. O zaman kestirimin nihai sonucu olarak biz "yes"
kararını veririz.
Oylama yönteminde tamamen farklı yöntemlerin kullanılması gerekmemektedir. Aynı yöntemler farklı hyper parametrelerle de oylamaya
sokulabilmektedir.
Aşağıdaki örnekte "soğuk terapisi (cryotherapy)" denilemn bir tedaviş yönteminin başarısına ilişkin bir veri kümesi kullanılmıştır.
Bu veri kümesini Excel dosyası olarak aşağıdaki bağlantıdan indirebilirsiniz:
https://archive.ics.uci.edu/ml/machine-learning-databases/00429/
Burada Excel dosyasını manuel biçimde "save as" özelliği ile CSV formatona dönüştürebilirsiniz. Ancak dönüştürülen verilerde
ondalık ayırcın "," olduğuna dikkat ediniz. Ondalık ayıracı da "." biçiminde değiştirmeniz gerekmektedir. Bu örnekte SVC,
DecisionTreeClassifier, GaussianNB, LogisticRegression yöntemleri ile problem çzöülmüş sonra kestirilecek değerler için
oylama yapılmıştır. Buradaki bazı yöntemler özellik ölçeklemesine gereksinim duymaktadır. Gerekmeyenlerde de özellik ölçeklemesi
yapılmıştır. Çünkü ouylama yapılırken her yönteme aynı veriler uygulanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('cryotherapy.csv', delimiter=';')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(dataset_x)
scaled_dataset_x = ss.transform(dataset_x)
from sklearn.svm import SVC
svc = SVC(kernel='rbf')
svc.fit(scaled_dataset_x, dataset_y)
# LogisticRegression Solution
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(max_iter=1000000)
lr.fit(scaled_dataset_x, dataset_y)
# Naive Bayes Solution
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
gnb.fit(scaled_dataset_x, dataset_y)
# Decision Tree
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier()
dtc.fit(scaled_dataset_x, dataset_y)
from scipy.stats import mode
def voting_classifier(estimators, predict_data):
result = np.zeros((len(estimators), len(predict_data)))
for i, estimator in enumerate(estimators):
result[i] = estimator.predict(predict_data)
print(result[i])
return mode(result, axis=0, keepdims=True).mode
import numpy as np
predict_data = np.array([
[2, 17, 5.25, 3, 1, 63],
[2, 23, 11.75, 12, 3, 72],
[2, 27, 8.75, 2, 1, 6],
[2, 15, 4.25, 1, 1, 6]
])
scaled_predict_data = ss.transform(predict_data)
estimators = [gnb, svc, lr, dtc]
result = voting_classifier(estimators, scaled_predict_data)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Oylama yöntemi ortalama esasına dayalı olarak da yapılabilmektedir. Örneğin iki sınıflı bir modelde bazı yöntemler bize
sonucu 0 ya da 1 olarak değil 0 ile 1 arasında bir sayı olarak vermektedir. İşte biz de bu yöntemlerdeki ortalamayı alabiliriz.
Çok sınıflı modellerde de bezner bir sistem uygulanabilmektedir.
Ortalama yöntemi özellikle lojistik olmayan regresyon problemlerinde kullanılmaktadır. Yani örneğin bir grup yöntemle problemi
çözeriz. Sonra kestirim aşamasında bu yöntemlerin hepsiyle kestirimde bulunup birer değer elde ederiz. Nihayi değer olarak bunların
ortalamasını alabiliriz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Oylama yöntemi için scikit-learn içerisinde sklearn.ensemble modülünde VotingClassifier ve VotingRegressor sınıfları bulundurulmuştur.
Sınıfların __init metotlarının parametrik yapısı şöyledir:
class sklearn.ensemble.VotingClassifier(estimators, *, voting='hard', weights=None, n_jobs=None, flatten_transform=True, verbose=False)
class sklearn.ensemble.VotingRegressor(estimators, *, weights=None, n_jobs=None, verbose=False)
Metotların birinci parametreleri yöntemlere ilişkin demet listelerinden oluşmaktadır. Bu listenin her elemanı olan demet bir isim ve
yönteme ilişkin sınıf nesnesi şçermektedir. Metotlardaki weights parametresi yöntemlere ağırlık atamak için kullanılmaktadır.
Yani biz istersek her yönteme farklı bir ağırlık etkisi verebiliriz. Tabii default durumda her yöntemin ağırlığı eşit olmaktadır.
VotingClassifier sınıfının (VotingRegressor sınıfında bu parametre yoktur) voting parametresi "hard" ya da "soft" olarak girilebilmektedir.
"hard" (default değer) tamamen kestirilen sınıfların sayısına bakmaktadır. "soft" ise sınıflarda olasılıksal bir değer üretiliyorsa bu
olasılıkların ortalamasını alarak sonucu kestirmektedir.
Sınıfların kullanımı benzer sınıflarda olduğu gibidir. Önce fit işlemi yapılır sonra da predict işlemi yapılır. score metotları
VotingClassifier için "accuracy" değerini, VotingRegressor sınıfı için ise R^2 değerini vermektedir.
Aşağıdaki örnekte "cryotherapy" veri kümesi üzerinde VotingClassifier sınıfı kullanılarak oylama ensemble yöntemi
uygulanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('cryotherapy.csv', delimiter=';')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(dataset_x)
scaled_dataset_x = ss.transform(dataset_x)
from sklearn.svm import SVC
svc = SVC(kernel='rbf')
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(max_iter=1000000)
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier()
from sklearn.ensemble import VotingClassifier
vc = VotingClassifier([('SVC', svc), ('LogisticRegression', lr), ('GaussianNB', gnb), ('DecisionTreeClassifier', dtc)])
vc.fit(scaled_dataset_x, dataset_y)
import numpy as np
predict_data = np.array([
[2, 17, 5.25, 3, 1, 63],
[2, 23, 11.75, 12, 3, 72],
[2, 27, 8.75, 2, 1, 6],
[2, 15, 4.25, 1, 1, 6]
])
scaled_predict_data = ss.transform(predict_data)
predict_result = vc.predict(scaled_predict_data)
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdakşi örnekte cryotherapy için eğitim ve test veri kümesi ayrıştırılmış ve oylama yönteminin başarısı score metoduyla
elde edilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('cryotherapy.csv', delimiter=';')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
scaled_test_dataset_x = ss.transform(test_dataset_x)
from sklearn.svm import SVC
svc = SVC(kernel='rbf')
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(max_iter=1000000)
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier()
from sklearn.ensemble import VotingClassifier
vc = VotingClassifier([('SVC', svc), ('LogisticRegression', lr), ('GaussianNB', gnb), ('DecisionTreeClassifier', dtc)])
vc.fit(scaled_training_dataset_x, training_dataset_y)
score = vc.score(scaled_test_dataset_x, test_dataset_y)
print(f'Accuracy score: {score}')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte arabaların mil başına yaktıkları yakıtın tahmin edilmesine yönelik "auto-mpg.data" veri kümesi kullanılmıştır.
Örnekte kestirim için LinearRegression, SVR ve DecisionTreeRegressor sınıfları ortalama esasında oylamaya sokulmuştur.
İşlemler scikit-learn içerisindeki VotingRegressor sınıfıyla yapılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('auto-mpg.data', delimiter=r'\s+', header=None)
df = df.iloc[:, :-1]
dataset_df = df[df.iloc[:, 3] != '?']
dataset_ohe_df = pd.get_dummies(dataset_df, columns=[7])
dataset = dataset_ohe_df.to_numpy(dtype='float32')
dataset_x = dataset[:, 1:]
dataset_y = dataset[:, 0]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=1234)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(training_dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
scaled_test_dataset_x = ss.transform(test_dataset_x)
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
from sklearn.svm import SVR
svr = SVR()
from sklearn.tree import DecisionTreeRegressor
dtr = DecisionTreeRegressor()
from sklearn.ensemble import VotingRegressor
vr = VotingRegressor([('LinearRegression', lr), ('SVR', svr), ('DecisionTreeRegressor', dtr)])
vr.fit(scaled_training_dataset_x, training_dataset_y)
r2_score = vr.score(scaled_test_dataset_x, test_dataset_y)
print(f'R^2: {r2_score}')
from sklearn.metrics import mean_absolute_error
predict_test_result = vr.predict(scaled_test_dataset_x)
mae_result = mean_absolute_error(test_dataset_y, predict_test_result)
print(f'Mean absolute error: {mae_result}')
#----------------------------------------------------------------------------------------------------------------------------
Çok kullanılan bir ensemble yöntem de "bagging (bootstrap aggregation)" denilen yöntemdir. ("bootstrap" bir grup veriden
örnekleme yapılarak bir alt grubu seçme anlamına gelmektedir. Aggregation ise toplamak, bir araya getirmek gibi bir anlamda
kullanılmıştır). Bu yöntemde veri kümesindeki satırlar iadeli biçimde belli büyüklerde örneklenerek n tane veri kümesi elde
edilir. Örneğin veri kümemizde 1000 tane satır olsun. Bu veri kümesinden her biri 30'larlı 100 tane rastgele iadeli (with replacement) biçimde
veri kümesi oluşturabiliriz. Bu işleme "boostrap" işlemi denilmektedir. Bagging yönteminde bir tane sınıflandırma ya da
regresyon yöntemi seçilir. Sonra oluşturulan bu alt kümelerle bu model eğitilerek çok sayıda model oluşturulur. Örneğin sınıflandırma
yöntemi olarak DecisinTreeClassifier kullanıyor olalım. Oluşturduğumuz 100 tane küçük veri kümesi ile biz 100 ayrı eğitim yaparak
100 ayrı karar ağacı oluştururuz. Sonra kestirim yapılırken kestirilecek değeri bu 100 farklı karar ağacına da uygularız.
Buradan bir oylama yaparak sonucu kestiririz.
Bagging yöntemi özellikle karar ağaçlarında çokça kullanılmaktadır. Ancak yöntem geneldir. Karar ağaçlarının dışında da
başka tahminleyicilerle kullanılabilir. Yöntemde örneklemenin iadeli yapılması önemli olmaktadır. Pek çok çalışma iadeli
örneklemenin daha iyi sonuç verdiğini göstermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bagging yöntemi için scikit-learn içerisinde sklearn.ensemble modülünde BaggingClassifier ve BaggingRegressor sınıfları bulundurulmuştur.
Sınıfların __init__ metotlarının parametrik yapısı aşağıdaki gibidir:
class sklearn.ensemble.BaggingClassifier(estimator=None, n_estimators=10, *, max_samples=1.0, max_features=1.0,
bootstrap=True, bootstrap_features=False, oob_score=False, warm_start=False, n_jobs=None,
random_state=None, verbose=0, base_estimator='deprecated')
class sklearn.ensemble.BaggingRegressor(estimator=None, n_estimators=10, *, max_samples=1.0, max_features=1.0,
bootstrap=True, bootstrap_features=False, oob_score=False, warm_start=False, n_jobs=None,
random_state=None, verbose=0, base_estimator='deprecated')
Metotların birinci parametreleri rastgele altkümelerin eğitiminde kullanılacak yönetmi yani tahminleyiciyi almaktadır.
Bu parametre girilmezse default olarak BaggingClassifier için DecisionTreeClassifier, BeggingRegressor için DecisionTreeRegressor
sınıfları kullanılır. Programcı ilgili yöntem sınıfı türünden bir nesne yaratarak bu parametreye onu verebilir. Metotların
n_estimators parametreleri kaç tane örnek oluşturulacağını başka bir deyişle kaç tane ilgili sınıf türünden tahminleyici
oluşturulacağını belirtir. Metotların max_samples parametresi çekilecek örneklerin eleman sayısını belirtmektedir. Bu bir oran
olarak verilebileceği gibi doğrudan bir sayı olarak da verilebilir. Bu sınıfta istenirse sütunların hepsi değil rastgele belli sütunlar
da kullanılabilir. Tüm sütunlar yerine rastgele sütunların kullanılması da özellikle overfitting konusunda avantaj sağlamaktadır.
(Yöntemleri bulanların ilk makalesinde "rastgele satırların iadesiz biçimde seçilmesine "Pasting", iadeli biçimde seöilmesine "bagging",
sütunlarınb rastgele seçilmesine "Random Subspacing)", hem satırların hem de sütunların rastgele seçilmesine ise "Random Patching"
biçiminde isimler verilmiştir. Ancak BaggingClassifier ve BaggingRegressor sınıfları hem satırları hem de sütunları rastgele
seçebilmektedir. Yani aslında farklı yöntemlerin bu sınıf bir arada kullanılmasına izin vermektedir.) Metotların bootstrap
parametreleri satırların seçilmesinin iadeli mi (with replacement) iadesiz mi (withput replacement) yapılacağını belirtmektedir.
Default durumda iadeli seçim uygulanmaktadır. bootstrap_features parametresi sütunların rastgele seçilmesi durumundaki seçimin iadeli
mi iadesiz mi yapılacağını belirtmektedir. Bu parametrenin default durumu iadesiz seçimdir. Metotların diğer parametreleri daha az
önemdedir. Default değerlerle geçilebilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte "breast-cancer" veri kümesi üzerinde önce tek bir karar ağacı ile klasik çzöüm uygulanmıştır. Sonra bagging yöntemi ile
satırların %70'i iadeli biçimde örneklenerek 100 ayrı karar ağacı oluşturularak oylama yapılmıştır. Buradaki sonuçta bagging
yönteminin %2 civarında daha başarılı olduğu görülmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import pandas as pd
df = pd.read_csv("breast-cancer-data.csv")
dataset_x = df.iloc[:, 2:-1].to_numpy()
dataset_y = np.zeros(len(df))
dataset_y[df['diagnosis'] == 'M'] = 1
"""
from sklearn.datasets import load_breast_cancer
bc = load_breast_cancer()
dataset_x = bc.data
dataset_y = bc.target
"""
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier(random_state=45678)
dtc.fit(training_dataset_x, training_dataset_y)
score = dtc.score(test_dataset_x, test_dataset_y)
print(f'Single DecisionTreeClassifier score: {score}')
from sklearn.ensemble import BaggingClassifier
bc = BaggingClassifier(DecisionTreeClassifier(), 100, max_samples=0.70, random_state=13456)
bc.fit(training_dataset_x, training_dataset_y)
score = bc.score(test_dataset_x, test_dataset_y)
print(f'Bagging DecisionTreeClassifier score: {score}')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte "Boston Housing Price" veri kğmesine yalnızca DecisionTreeRegressor yöntemi ve BaggingRegressor ensemble
yöntemi uygulanmıştır. İyileşme oldukça tatmin edici düzeydedir. Elde edilen sonuçlar şöyledir:
Single DecisionTreeRegressor Mean Absolute Error: 3.261764769460641
Single DecisionTreeRegressor R^2: 0.5655777616751758
BaggingRegressor Mean Absolute Error: 1.4358725589864394
BaggingRegressor R^2: 0.9256567952768898
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Diğer bir ensemble yöntem grubuna da "stacking" denilmektedir. Bu yöntemde problem n tane tahminleyici ile çözülür. Kestirim yapılırken
bu modellerdne kestirilen değerler başka bir meta modele verilir. Kesitimin nihai sonucu bu metamodelin verdiği sonuç olur.
Tabii eğitim sırasında hem tahminleyiciler hem de meta tahminleyici eğitilmektedir.
Stacking yöntemi scikit-learn içerisindeki sklearn.ensemble modülünde bulunan StackingClassifier ve StackingRegressor sınıfları
yoluyla uygulanabilmektedir. Sınıfların __init__ metotlarının parametrik yapısı şöyledir:
class sklearn.ensemble.StackingClassifier(estimators, final_estimator=None, *,
cv=None, stack_method='auto', n_jobs=None, passthrough=False, verbose=0)
class sklearn.ensemble.StackingRegressor(estimators, final_estimator=None, *, cv=None,
n_jobs=None, passthrough=False, verbose=0)
Metotların birinci parametreleri kullanılacak tahminleyicileri almaktadır. Bu tahminleyiciler birer demet listesi biçiminde oluşturulur.
Demetlerin birinci elemanı tahminleyiciye verilen ismi, ikinci elemanı tahminleyici nesnesindne oluşmaktadır.
Metotların final_estimators parametreleri ise meta tahminleyiciyi almaktadır. Bu parametre belirtilmezse StackingClassifier
sınıfı için LogisticRegression, StackingRegressor sınıfı için RidgeCV nesneleridir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte "cryotherapy" veri kümesi üzerinde "stacking" enseble yöntemi uygulanmıştır. Uygulamada dört tahminleyici
kullanılmış ve bunların çıktıları meta tahminleyiciye verilmiştir. Meta tahminleyici için bir nesne belirtilmediğinden default durumda
LogisticRegression yöntemi kullanılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('cryotherapy.csv', delimiter=';')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
scaled_test_dataset_x = ss.transform(test_dataset_x)
from sklearn.svm import SVC
svc = SVC(kernel='rbf')
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(max_iter=1000000)
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier()
from sklearn.ensemble import StackingClassifier
sc = StackingClassifier([('SVC', svc), ('LogisticRegression', lr), ('GaussianNB', gnb), ('DecisionTreeClassifier', dtc)])
sc.fit(scaled_training_dataset_x, training_dataset_y)
score = sc.score(scaled_test_dataset_x, test_dataset_y)
print(f'Accuracy score: {score}')
import numpy as np
predict_data = np.array([
[2, 17, 5.25, 3, 1, 63],
[2, 23, 11.75, 12, 3, 72],
[2, 27, 8.75, 2, 1, 6],
[2, 15, 4.25, 1, 1, 6]
])
scaled_predict_data = ss.transform(predict_data)
predict_result = sc.predict(scaled_predict_data)
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte "auto-mpg" veri kümesi üzerinde StackingRegressor ensemble yöntemi uygulanmıştır. Burada üç tahminleyici
kullanılmış ve ondan elde edilen değerler meta tahminleyiciye verilmiştir. Meta tahminleyici için bir sınıf belirtilmediğinden dolayı
default olarak RidgeCV sınıfı kullanılmştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('auto-mpg.data', delimiter=r'\s+', header=None)
df = df.iloc[:, :-1]
dataset_df = df[df.iloc[:, 3] != '?']
dataset_ohe_df = pd.get_dummies(dataset_df, columns=[7])
dataset = dataset_ohe_df.to_numpy(dtype='float32')
dataset_x = dataset[:, 1:]
dataset_y = dataset[:, 0]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=1234)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(training_dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
scaled_test_dataset_x = ss.transform(test_dataset_x)
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
from sklearn.svm import SVR
svr = SVR()
from sklearn.tree import DecisionTreeRegressor
dtr = DecisionTreeRegressor()
from sklearn.ensemble import StackingRegressor
sr = StackingRegressor([('LinearRegression', lr), ('SVR', svr), ('DecisionTreeRegressor', dtr)])
sr.fit(scaled_training_dataset_x, training_dataset_y)
r2_score = sr.score(scaled_test_dataset_x, test_dataset_y)
print(f'R^2: {r2_score}')
from sklearn.metrics import mean_absolute_error
predict_test_result = sr.predict(scaled_test_dataset_x)
mae_result = mean_absolute_error(test_dataset_y, predict_test_result)
print(f'Mean absolute error: {mae_result}')
#----------------------------------------------------------------------------------------------------------------------------
Diğer bir grup ensemble yöntemlere "boosting" denilmektedir. Boosting yönteminin de çeşitli varyasyonları vardır. En çok kullanılan
AdaBoosting ve GradientBoosting yöntemleridir.
AdaBoosting yönteminde problem bir tahminleyici ile çözülür. Buna "zayıf tahminleyici (weak estimator/learner)" denilmektedir.
Sonra bu tahminleyicide yanlış tahmin edilen satırların üzerinde özellikle durularak problem yeniden çözülmektedir.
Burada problem yeniden çözülürken zayıf tahminleyicide doğru tahmin edilememiş satırların ağırlıkları kullanılan tahminleyici
algoritmasında yükseltilip doğru tahmin edilen satırlarda duruma göre düşürülmektedir. Böylece ikinci aşamda birinci aşamada yanlış
tahmin edilmiş olan bazı satırlar doğru tahmin edilecektir. Ancak bu sefer birinci aşamada doğru tahmin edilen bazı satırlar da yanlış
tahmin edilebilecektir. Üçünxü aşamda benzer yöntem devam ettirilir. Yani ikinci aşamda yanlış tahmin edilen satırların ağırlıkları
yükseltilmektedir. Böylece üçüncü bir model oluşturulmuş olur. Bu biçimde n defa algoritma devam ettirilir. Böylece bu işlemin
sonucunda elimizde n tane farklıırlıklık değerleriyle eğitilmiş model bulunacaktır. İşte kestirim aşamasında kestirilecek değer
bu n tane modele sokulur ve oylama yöntemi uygulanır.
AdaBoosting yöntemi aslen sınıflandırma yöntemleri için kullanılsa da lojistik olmayan regresyon problemlerinde de bu yöntem kullanılabilmektedir.
scikit-learn kütüphanesi içerisinde AdaBoostClassifier ve AdaBoosRegressor isimli iki sınıf bulunmaktadır. Bu sınıfların __init__
metotları şöyledir:
class sklearn.ensemble.AdaBoostClassifier(estimator=None, *, n_estimators=50, learning_rate=1.0, algorithm='SAMME.R',
random_state=None, base_estimator='deprecated')
class sklearn.ensemble.AdaBoostRegressor(estimator=None, *, n_estimators=50, learning_rate=1.0, loss='linear',
random_state=None, base_estimator='deprecated')
Metotların birinci parametreleri kullanılacak tahminleyiciyi (yani yöntemi) belirtmektedir. Bu parametre girilmezse AdaBoostClassifier
sınıfı için DecisionTreeClassifier, AdaBoostRegressor sınıfı için DecisionTreeRegressor nesneleri yaratılıp kullanılmaktadır.
n_estimator parametresi kullanılacak model sayısını bellirtmektedir. Yani ağırlıklar değiştirilerek toplam kaç yineleme yapılacaktır.
Diğer parametreler daha ayrıntılı ayarlamalar için kullanılmaktadır. SInıfların yine fit, predict ve score metotları vardır. score metotları
AdaBoostClassifier için "accuracy" değerini, AdaBoostRegressor için "R^2" değerini hesaplamaktadır.
Aşağıdaki örnekte cryotherapy için AdaBoostClassfier yöntemi kullanılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('cryotherapy.csv', delimiter=';')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier()
abc = AdaBoostClassifier(dtc, n_estimators=100)
abc.fit(training_dataset_x, training_dataset_y)
score = abc.score(test_dataset_x, test_dataset_y)
print(f'Accuracy score: {score}')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte otomobillerin mil başına yaktıkları yakıtın tahmin edilmesi probleminde AdaBoostRegressor ensemble
yönteminin DecisionTreeRegressor nesnesi ile kullanımına ilişkin örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('auto-mpg.data', delimiter=r'\s+', header=None)
df = df.iloc[:, :-1]
dataset_df = df[df.iloc[:, 3] != '?']
dataset_ohe_df = pd.get_dummies(dataset_df, columns=[7])
dataset = dataset_ohe_df.to_numpy(dtype='float32')
dataset_x = dataset[:, 1:]
dataset_y = dataset[:, 0]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=1234)
from sklearn.ensemble import AdaBoostRegressor
from sklearn.tree import DecisionTreeRegressor
dtr = DecisionTreeRegressor()
abr = AdaBoostRegressor(dtr, n_estimators=100)
abr.fit(training_dataset_x, training_dataset_y)
score = abr.score(test_dataset_x, test_dataset_y)
print(f'R^2 score: {score}')
from sklearn.metrics import mean_absolute_error
predict_test_result = abr.predict(test_dataset_x)
mae_result = mean_absolute_error(test_dataset_y, predict_test_result)
print(f'Mean absolute error: {mae_result}')
#----------------------------------------------------------------------------------------------------------------------------
Boosting yöntemlerinden bir diğerine de "Gradient Boosting" denilmektedir. Bu yöntemde problem bir kez çözülür. Sonra gerçek değerlerle
tahmin edilen değerler arasındaki farklar (residuals) hesaplanır. Bu farkların küçültülmesi için yeniden model oluşturulur. Her defasında
bir önceki değerler sanki gerçek değerlermiş gibi işlemler yürütülmektedir. Burada da yine her adımda yeni bir tahminleyici oluşturulmaktadır.
En sonunda yine oylama yapılarak kestirim gerçekleştirilmektedir.
scikit-learn içersinde Gradient Boosting işlemini yapan iki sınıf vardır: GradientBoostingClassifier ve GradientBoostingRegressor. Bu sınıfların
__init__ metotlarının parametik yapıları şöyledir:
class sklearn.ensemble.GradientBoostingClassifier(*, loss='log_loss', learning_rate=0.1, n_estimators=100, subsample=1.0,
criterion='friedman_mse', min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_depth=3,
min_impurity_decrease=0.0, init=None, random_state=None, max_features=None, verbose=0, max_leaf_nodes=None,
warm_start=False, validation_fraction=0.1, n_iter_no_change=None, tol=0.0001, ccp_alpha=0.0)
class sklearn.ensemble.GradientBoostingRegressor(*, loss='squared_error', learning_rate=0.1, n_estimators=100,
subsample=1.0, criterion='friedman_mse', min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0,
max_depth=3, min_impurity_decrease=0.0, init=None, random_state=None, max_features=None, alpha=0.9, verbose=0,
max_leaf_nodes=None, warm_start=False, validation_fraction=0.1, n_iter_no_change=None, tol=0.0001, ccp_alpha=0.0
Bu sınıflar tahminleyici almamaktadır. Çünkü zaten gradient algoritma loss fonksiyonuna dayandırılmıştır. Defalt loss fonksiyonu
sınıflandırma problemleri için "log_loss", lojistik olmayan regresyon problemleri için "squared_error" biçimindedir.
Sınıfların kullanımı diğer sınıflarda olduğu gibidir. estimators parametresi yine kullanılacak modellerin (tahminleyicilerin)
sayısını belirtmektedir. Bu parametre default olarak 100 girilmiştir.
Aşağıdaki örnekte "cryotherapy" veri kğmesi üzerinde Gradient Boosting yöntemi uygulanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('cryotherapy.csv', delimiter=';')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from sklearn.ensemble import GradientBoostingClassifier
gbc = GradientBoostingClassifier()
gbc.fit(training_dataset_x, training_dataset_y)
score = gbc.score(test_dataset_x, test_dataset_y)
print(f'Accuracy score: {score}')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda otomobillerin mil başına yaktıkları yakıtın Gradient Boosting ensemble yöntemle tahmin edilmesi örneği verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('auto-mpg.data', delimiter=r'\s+', header=None)
df = df.iloc[:, :-1]
dataset_df = df[df.iloc[:, 3] != '?']
dataset_ohe_df = pd.get_dummies(dataset_df, columns=[7])
dataset = dataset_ohe_df.to_numpy(dtype='float32')
dataset_x = dataset[:, 1:]
dataset_y = dataset[:, 0]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=1234)
from sklearn.ensemble import GradientBoostingRegressor
gbr = GradientBoostingRegressor()
gbr.fit(training_dataset_x, training_dataset_y)
score = gbr.score(test_dataset_x, test_dataset_y)
print(f'R^2 score: {score}')
from sklearn.metrics import mean_absolute_error
predict_test_result = gbr.predict(test_dataset_x)
mae_result = mean_absolute_error(test_dataset_y, predict_test_result)
print(f'Mean absolute error: {mae_result}')
#----------------------------------------------------------------------------------------------------------------------------
RandomForest Yöntemi burada açıklanacak
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Çok kullanılan otomatik makine öğrenmesi (Automated Machine Learning) kütüphanelerinden biri de "TPOT" denilen kütüphanedir.
TPOT kütüphanesi ağırlıklı olarak istatistiksle yöntemleri kullanmaktadır ve sckit-learn kütüphanesinin üzerine otururlmuştur.
Kütüphaneye daha sonraları yapay sinir ağları modülü de (nn modülü) eklenmiş durumdadır. Bu modül arka planda PyTorch denilen
kütüphaneyi kullanmaktadır. nn modülü kullanılmasa bile TPOT scikit-learn içerisindeki basit sinir ağı oluşturan MLPClassifier ve MLPRegressor
sınıflarını da modellerde denemektedir.
TPOT çeşitli modelleri dener. Denemeler sırasında hyper parametreleri de ayarlamaya çalışır. Model denemesi ve hyper parametre ayarlaması
için arka planda "genetik algoritmalar" kullanılmaktadır. Genetik algoritmalarda iyi sonuç veren modeller değiştirilerek iyileştirilmeye
çalışılmaktadır.
TPOT kütüphanesinin ana web sayfası aşağıdaki bağlantıda verilmiştir:
http://epistasislab.github.io/tpot/
TPOT pek çok işlemi kendisi yapmaktadır. Örneğin özellik seçimi, ölçekleme işlemleri, modelin seçilmesi, hyper parametrelerin ayarlanması
gibi. TPOT kendis içerisinde genetik algoritmaları kullanarak bu işlemleri uygun biçimde yapmaya çalışmaktadır. Ancak TPOT kullanıcılar için
nispeten basit bir arayüz sunmaktadır. Kütüphanede iki temel sınıf vardır: TPOTClassifier ve TPOTRegerressor. TPOTClassifier sınıfı
sınıflandırma problemleri için TPOTRegressor sınıfı ise lojistik olmayan regresyon problemleri için kullanılmaktadır.
Otomatik makine öğrenmesi araçları iyi bir modelin bulunması için zamana gereksinim duyabilmektedir. Dolayısıyla iyi bir model
için çokça bilgisayar zamanı gerekebilmektedir. Bu tür araçların cloud sistemlerinde uzaktan idaere edilmesi yaygın bir uygulamadır.
TPOT ile iyi bir model bulunduktan sonra elde edilen modele ilişkin Python kodları da üretilebilmektedir. Böylece oluşturulan model
TPOT'tan bağımsız biçimde dış dünyadan da kullanılabilmektedir.
TPOT kütüphanesinin kurulumu şöyle yapılabilir:
pip install tpot
Aslında TPOT Python'dan bağımsız olarak komut satırından da çalıştırılabilmektedir. Bunun için çeşitli komut satırı argümanlar
kullanılmaktadır. Örneğin:
tpot data/mnist.csv -is , -target class -o tpot_exported_pipeline.py -g 5 -p 20 -cv 5 -s 42 -v 2
Eğer TPOT'u komut satırından kullanacaksanız dokümanlardan komut satırı argümanlarının detaylarını incelemelisiniz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
TPOT ile sınıflandırma işlemleri için TPOTClassifier sınıfı kullanılmaktadır. Sınıfın __init__ metodunun parametrik yapısı şöyledir:
class tpot.TPOTClassifier(generations=100, population_size=100,
offspring_size=None, mutation_rate=0.9,
crossover_rate=0.1,
scoring='accuracy', cv=5,
subsample=1.0, n_jobs=1,
max_time_mins=None, max_eval_time_mins=5,
random_state=None, config_dict=None,
template=None,
warm_start=False,
memory=None,
use_dask=False,
periodic_checkpoint_folder=None,
early_stop=None,
verbosity=0,
disable_update_check=False,
log_file=None
)
Buradaki generations, population_size, offspting_size, mutation_rate model aramsında faydalanıan genetik algoritmalara ilişkin parametreleri
belirtmektedir. Genel olarak bu değerler yükseltildikçe modelin iyileşme olasılığı da yükselmektedir. Ancak tabii bu değerlerin yükseltilmesi
çözüm için çok zamanın harcanmasına yol açmaktadır. Eğer uygulamacı genetik algoritmalar hakkında bilgi sahibi değilse bu parametreleri
default değerlerde bırabilir.
Uygulamacı için modelin elde edilme zamanı önemli olabilmektedir. Default parametreler çok fazla beklemeye yol açabilir. Bu nedenle
bekleme zamınını kontrol altında tutabilmek için max_time_mins parametresi bulundurulmuştur. Bu parametreye bir ddakika değeri girilir.
TPOT da bu kadar süre içerisinde en uygun metofu bulmaya çalışır. Biz buradaki denemelerde fazla beklememek için bu değeri düşük tutacağız.
Ancak siz gerçek uygulamalarda bu değeri yükseltmelisiniz. Tabii bazen buradaki zamandan daha önce de model araması biritilmiş olabilmektedir.
Metodun max_eval_time_mins parametresi oluşturualan her bir modelin sınanması için ayrılan maksimum zamanı belirtmektedir. Genellikle bu
parametre default değerle (5 dakika) geçilmektedir. Metdun verbosity parametresi TPOT çalışırken ekrana çıkartılacak bildirimlerin yoğunluğunu
belirtmektedir. Bu değer 0, 1, 2, 3 olabilir. Metodun config_dict paranmetresi uygulanacak modeller ve onların parametreleri üzerinde
uygulamacının etkili olmasını sağlamak için düşünülmüştür. Buradaki sözlüğün nasıl oluşturulacağı dokümanlarda açıklanmıştır:
http://epistasislab.github.io/tpot/using/#customizing-tpots-operators-and-parameters
Metodun score parametresi test işleminde kullanılacak metrik'i belirtmektedir. Default durumda "accuracy" metrik olarak kullanılmaktadır.
Sınıf nesnesi yaratıldıktan sonra eğitim için yine sınıfın fit metodu, kestirim için predict metodu çağrılır. score metodu
ise test işlemini yapmaktadır. Sınıfın export metodu oluşturulan en iyi modelin Python kaynak kodlarını vermektedir.
TPOT kategorik verilerin sayısal biçime dönüştürülmesi işlemini kendisi otomatik yapmamaktadır. (Aslında y verileri üzerinde bunu
yapabilmektedir) Bunun için programcının kategrik verileri kendisinin dönüştürmesi gerekmektedir. Verilerin temizlenmesi işlemlerini ise
uygulamacının yapması gerekmektedir. Ancak TPOT özellik seçimini kendisi yapar. Yani gereksiz sütunların arındırılması uygulamacı yapmak zorunda değildir.
Aşağıda mem kanseri örneği TPOTClassifir sınıfı kullanılarak otomatize biçimde çözülmüştür. Yukarıda da belirttiğimiz gibi
burada maksimum süre olarak 5 dakika aldık. Ancak sizin daha iyi bir sonuç için bu zamanı artırmanız gerekir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import pandas as pd
df = pd.read_csv("breast-cancer-data.csv")
dataset_x = df.iloc[:, 2:-1].to_numpy()
dataset_y = np.zeros(len(df))
dataset_y[df['diagnosis'] == 'M'] = 1
"""
from sklearn.datasets import load_breast_cancer
bc = load_breast_cancer()
dataset_x = bc.data
dataset_y = bc.target
"""
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
from tpot import TPOTClassifier
tpc = TPOTClassifier(max_time_mins=5, verbosity=3)
tpc.fit(training_dataset_x, training_dataset_y)
score = tpc.score(test_dataset_x, test_dataset_y)
print(f'Accuracy scor: {score}')
import numpy as np
predict_data = np.array([[15.1,22.02,97.26,712.8,0.09056,0.07081,0.05253,0.03334,0.1616,0.05684,0.3105,0.8339,2.097,29.91,0.004675,0.0103,0.01603,0.009222,0.01095,0.001629,18.1,31.69,117.7,1030,0.1389,0.2057,0.2712,0.153,0.2675,0.07873], [11.52,18.75,73.34,409,0.09524,0.05473,0.03036,0.02278,0.192,0.05907,0.3249,0.9591,2.183,23.47,0.008328,0.008722,0.01349,0.00867,0.03218,0.002386,12.84,22.47,81.81,506.2,0.1249,0.0872,0.09076,0.06316,0.3306,0.07036]])
predict_result = tpc.predict(predict_data)
print(predict_result)
tpc.export('automated-tpot-breastcancer.py')
#----------------------------------------------------------------------------------------------------------------------------
TPOTClassifier sınıfı ile export edilip bir Python programına dönüştürülen modelin kodları nasıldır? Maalesef üretilen modele
ilişkin kaynak kodlar hemen kullanılacak biçimde değildir. Programcının bunun üzerinde bazı değişikleri yapması gerekmektedir.
Yukarıdaki model için üretilen Python programı aşağıda verilmiştir:
import numpy as np
import pandas as pd
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.model_selection import train_test_split
# NOTE: Make sure that the outcome column is labeled 'target' in the data file
tpot_data = pd.read_csv('PATH/TO/DATA/FILE', sep='COLUMN_SEPARATOR', dtype=np.float64)
features = tpot_data.drop('target', axis=1)
training_features, testing_features, training_target, testing_target = \
train_test_split(features, tpot_data['target'], random_state=None)
# Average CV score on the training set was: 1.0
exported_pipeline = ExtraTreesClassifier(bootstrap=False, criterion="gini", max_features=0.6000000000000001, min_samples_leaf=7, min_samples_split=16, n_estimators=100)
exported_pipeline.fit(training_features, training_target)
results = exported_pipeline.predict(testing_features)
Burada üretilen kodda çeşitli düzenlemelerin yapılması gerekmektedir. Bu düzenlemeler şunlardır:
- Kod içerisinde 'PATH/TO/DATA/FILE' ve 'COLUMN_SEPARATOR' kısımları elle düzeltilmelidir.
- Modelin ürettiği kodda hedeh sütun sayısal biçime dönüştürülmş olmalıdır. Aslında TPOT Classifier kullanımda bu işlemi
kendisi yapmaktadır. Ancak üretilen koddaki CSV dosyasında bu alanın sayısallaştırılmış olması gerekmektedir.
- Üretilen kodda target sütunu CSV dosyasının içerisindedir ve sütun x datalarının oluşturulması sırasında drop edilmektedir.
Tabii üretilen koddaki asıl önemli kısım işlemin yapıldığı pipline işelmleri, kullanılan sınıflar ve hyper parametrelerdir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda "titanic.csv" veri kümesinde sınıflandırma işlemi için TPOTClassifier sınıfı kullanılmıştır. titanic.csv veri kümesinde
bazı sütunlar gereksiz olduğundan atılmıştır. Kategorik sütunlar sayısal biçime dönüştürülmüştür. 5 dakikalık bir çalışma sonrasında
model elde edilmiş ve test edilmiştir. Export edilen model şöyledir:
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import MinMaxScaler
from sklearn.impute import SimpleImputer
# NOTE: Make sure that the outcome column is labeled 'target' in the data file
tpot_data = pd.read_csv('PATH/TO/DATA/FILE', sep='COLUMN_SEPARATOR', dtype=np.float64)
features = tpot_data.drop('target', axis=1)
training_features, testing_features, training_target, testing_target = \
train_test_split(features, tpot_data['target'], random_state=None)
imputer = SimpleImputer(strategy="median")
imputer.fit(training_features)
training_features = imputer.transform(training_features)
testing_features = imputer.transform(testing_features)
# Average CV score on the training set was: 0.8399093863882596
exported_pipeline = make_pipeline(
MinMaxScaler(),
RandomForestClassifier(bootstrap=False, criterion="entropy", max_features=0.35000000000000003, min_samples_leaf=8, min_samples_split=4, n_estimators=100)
)
exported_pipeline.fit(training_features, training_target)
results = exported_pipeline.predict(testing_features)
Buradan görüldüğü gibi TPOT bizim için karar ağaçları kullanarak "random forest" ensemble yöntemini seçmiştir.
Aşağıda modelin tüm kodları verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('titanic.csv')
dataset_y = df['Survived'].to_numpy()
df_dataset_x = df.drop(['PassengerId', 'Survived', 'Name', 'Ticket', 'Cabin'], axis=1)
df_dataset_x['Sex'] = (df_dataset_x['Sex'] == 'male').astype('int')
dataset_x = pd.get_dummies(df_dataset_x, ['Embarked']).to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
from tpot import TPOTClassifier
tpc = TPOTClassifier(max_time_mins=5, verbosity=3)
tpc.fit(training_dataset_x, training_dataset_y)
score = tpc.score(test_dataset_x, test_dataset_y)
print(f'Accuracy scor: {score}')
tpc.export('automated-tpot-titanic.py')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda "cryotherapy.csv" veri kümesi üzerinde TPOT ile model elde edilmiştir. TPOT özelliklerin ölçeklendirilmesini zaten
kendisi yapmaktadır. 5 dakika çalışma sonucunda export edilen model aşağıdaki gibidir:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import MaxAbsScaler, MinMaxScaler
# NOTE: Make sure that the outcome column is labeled 'target' in the data file
tpot_data = pd.read_csv('PATH/TO/DATA/FILE', sep='COLUMN_SEPARATOR', dtype=np.float64)
features = tpot_data.drop('target', axis=1)
training_features, testing_features, training_target, testing_target = \
train_test_split(features, tpot_data['target'], random_state=None)
# Average CV score on the training set was: 0.9314285714285715
exported_pipeline = make_pipeline(
MinMaxScaler(),
MaxAbsScaler(),
MLPClassifier(alpha=0.01, learning_rate_init=0.01)
)
exported_pipeline.fit(training_features, training_target)
results = exported_pipeline.predict(testing_features)
Buradan da görüldüğü gibi TPOT önce sütunları Min-Max ölçeklemesine sonra Max-Abs ölçeklemesine sokmuş sonra da
tek saklı katmanlı bir yapay sinir ağı kullanmıştır. Buradan elde edilen sonuç %100'e yakın olmuştur.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('cryotherapy.csv', delimiter=';')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from tpot import TPOTClassifier
tpc = TPOTClassifier(max_time_mins=5, verbosity=3)
tpc.fit(training_dataset_x, training_dataset_y)
score = tpc.score(test_dataset_x, test_dataset_y)
print(f'Accuracy scor: {score}')
tpc.export('automated-tpot-cryotherapy.py')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda "fish.csv" veri kümesi üzerinde TPOTClassifier sınıfı uygulanmıştır. Bu veri kümesi aşağıdaki gibidir:
Species,Weight,Length1,Length2,Length3,Height,Width
Bream,242,23.2,25.4,30,11.52,4.02
Bream,290,24,26.3,31.2,12.48,4.3056
Bream,340,23.9,26.5,31.1,12.3778,4.6961
...
Bu veri kümesi aslında balığın ağırlığını tahmin etmek için oluşturulmuştur. Ancak biz burada balığın cinsini tahmin etmeye
çalışacağız. Bu veri kümesinde balık türlerini LabelEncoder sınıfı ile sayısal hale dönüştürdük. (Gerçi y verileri üzerinde
dönüştürmeyi TPOT kendisi de yapabilmektedir.)
Veri kümesine aşağıdaki bağlantıdan erişilebilir:
https://www.kaggle.com/datasets/aungpyaeap/fish-market
Export edilen model şöyledir:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.svm import LinearSVC
# NOTE: Make sure that the outcome column is labeled 'target' in the data file
tpot_data = pd.read_csv('PATH/TO/DATA/FILE', sep='COLUMN_SEPARATOR', dtype=np.float64)
features = tpot_data.drop('target', axis=1)
training_features, testing_features, training_target, testing_target = \
train_test_split(features, tpot_data['target'], random_state=None)
# Average CV score on the training set was: 0.9686153846153847
exported_pipeline = LinearSVC(C=15.0, dual=False, loss="squared_hinge", penalty="l2", tol=1e-05)
exported_pipeline.fit(training_features, training_target)
results = exported_pipeline.predict(testing_features)
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('cryotherapy.csv', delimiter=';')
dataset_x = df.iloc[:, :-1].to_numpy()
dataset_y = df.iloc[:, -1].to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
from tpot import TPOTClassifier
tpc = TPOTClassifier(max_time_mins=5, verbosity=3)
tpc.fit(training_dataset_x, training_dataset_y)
score = tpc.score(test_dataset_x, test_dataset_y)
print(f'Accuracy scor: {score}')
tpc.export('automated-tpot-cryotherapy.py')
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de MNIST örneğini TPOT ile çözmeye çalışalım. Anımsanacağı gibi MNIST veri kümesi her biri 28x28'lik gray scale resimlerden
oluşuyordu. Biz de bu veri kümesini iki boyutlu bir dizi biçiminde oluşturup TPOTClassifier sınıfına verdik. 5 dakikalık bir süre içerisinde
TPOT tarafından bulunan model şöyledir:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
# NOTE: Make sure that the outcome column is labeled 'target' in the data file
tpot_data = pd.read_csv('PATH/TO/DATA/FILE', sep='COLUMN_SEPARATOR', dtype=np.float64)
features = tpot_data.drop('target', axis=1)
training_features, testing_features, training_target, testing_target = \
train_test_split(features, tpot_data['target'], random_state=None)
# Average CV score on the training set was: 0.5617666666666666
exported_pipeline = GaussianNB()
exported_pipeline.fit(training_features, training_target)
results = exported_pipeline.predict(testing_features)
Buradan elde edilen sonuç %55 gibi kötü bir değerdir. Bunun bir nedeni model aramasının 5 dakika gibi kısıtlı bir zamanda
yapılmasıdır. Büyük miktarda verilerde model denemesi de yavaş olmaktadır. Dolaısıyla bu tür verilerde 5 dakika çok yetersiz bir zamandır.
Öte yandan resim sınıflandırma gibi işlemler yapay sinir ağlarıyla ve derin ağlarla çok daha iyi bir biçimde gerçekleştirilmektedir.
Programın Python kodu aşağıda verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df_training = pd.read_csv('mnist_train.csv')
df_test = pd.read_csv('mnist_test.csv')
training_dataset_x = df_training.iloc[:, 1:].to_numpy()
training_dataset_y = df_training.iloc[:, 0].to_numpy()
test_dataset_x = df_test.iloc[:, 1:].to_numpy()
test_dataset_y = df_test.iloc[:, 0].to_numpy()
from tpot import TPOTClassifier
tpc = TPOTClassifier(max_time_mins=5, verbosity=3)
tpc.fit(training_dataset_x, training_dataset_y)
score = tpc.score(test_dataset_x, test_dataset_y)
print(f'Accuracy scor: {score}')
tpc.export('automated-tpot-mnist.py')
#----------------------------------------------------------------------------------------------------------------------------
TPOT ile lojistik olmayan regresyon işlemleri için TPOTRegressor sınıfı kullanılmaktadır. Sınıfın __init__ metodunun parametrik
yapısı şöyledir:
class tpot.TPOTRegressor(generations=100, population_size=100,
offspring_size=None, mutation_rate=0.9,
crossover_rate=0.1,
scoring='neg_mean_squared_error', cv=5,
subsample=1.0, n_jobs=1,
max_time_mins=None, max_eval_time_mins=5,
random_state=None, config_dict=None,
template=None,
warm_start=False,
memory=None,
use_dask=False,
periodic_checkpoint_folder=None,
early_stop=None,
verbosity=0,
disable_update_check=False)
Metodun parametrik yapısı TPOTClassifier sınıfına oldukça benzemektedir. Yine burada max_time_mins modelin toplamda kaç dakika
zaman içerisinde bulunacağını belirtmektedir. Diğer parametrelerin çoğu zaten TPOTClassifier sınıfındakilerle aynıdır. Yine bu sınıfta da
kategorik verilerin sayısal hale getirilmesi otomatik yapılmamaktadır. Ancak ölçekleme işlemleri otomatik yapılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte Boston Housing Price veri kümesi üzernde TPOTRegressor sınıfı ile model araştırması yapılmıştır.
TPOT yalnızca scikit-learn değil bazı üçüncü parti kütüphaneleri de kullanmaktadır. Örneğin XGB Boosting işlemini yapan xgboost
kütüphanesi de TPOT tarafından kullanılmaktadır. Boston veri kümesinden ("housing.csv") TPOT'un 5 dakika çalıştırmayla elde ettiği
export edilen model şöyledir:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from xgboost import XGBRegressor
# NOTE: Make sure that the outcome column is labeled 'target' in the data file
tpot_data = pd.read_csv('PATH/TO/DATA/FILE', sep='COLUMN_SEPARATOR', dtype=np.float64)
features = tpot_data.drop('target', axis=1)
training_features, testing_features, training_target, testing_target = \
train_test_split(features, tpot_data['target'], random_state=None)
# Average CV score on the training set was: -9.49656867980957
exported_pipeline = XGBRegressor(learning_rate=0.1, max_depth=6, min_child_weight=3, n_estimators=100, n_jobs=1, objective="reg:squarederror", subsample=0.8500000000000001, verbosity=0)
exported_pipeline.fit(training_features, training_target)
results = exported_pipeline.predict(testing_features)
Görüldüğü gibi burada xgboost kütüphanesinden faydalanılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
from tpot import TPOTRegressor
tpr = TPOTRegressor(max_time_mins=5, verbosity=3)
tpr.fit(training_dataset_x, training_dataset_y)
predict_result = tpr.predict(test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(test_dataset_y, predict_result)
print(f'Mean Absolute Error: {mae}')
tpr.export('automated-tpot-boston.py')
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de sağlık sigortası için poliçe bedelinin tahmin edildiği lojistik olmayan regresyon örneğini TPOT ile çözelim. Bu veri kümesi
"insurance.csv" ismiyle aşağıdaki baplantıdan indirilebilir:
https://www.kaggle.com/code/bbhatt001/predictors-of-medical-expenses/input
"insurance.csv" veri kümesinde "sex", "smoker" ve "region" sütunları kategorik sütunlardır. Bu nedenle bu örnekte biz önce bu sütunları
sayısal hale dönüştürdük. Sonra TPOTRegressor sınıfına soktuk.
TPOT tarafından elde edilen model şöyledir:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from xgboost import XGBRegressor
# NOTE: Make sure that the outcome column is labeled 'target' in the data file
tpot_data = pd.read_csv('PATH/TO/DATA/FILE', sep='COLUMN_SEPARATOR', dtype=np.float64)
features = tpot_data.drop('target', axis=1)
training_features, testing_features, training_target, testing_target = \
train_test_split(features, tpot_data['target'], random_state=None)
# Average CV score on the training set was: -21051246.4
exported_pipeline = XGBRegressor(learning_rate=0.1, max_depth=2, min_child_weight=8, n_estimators=100, n_jobs=1, objective="reg:squarederror", subsample=0.55, verbosity=0)
exported_pipeline.fit(training_features, training_target)
results = exported_pipeline.predict(testing_features)
Programın Python kodu aşağıda verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('insurance.csv')
dataset_y = df['charges'].to_numpy(dtype='float32')
df_dataset_x = df.drop(['charges'], axis=1)
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df_dataset_x['sex'] = le.fit_transform(df_dataset_x['sex'])
df_dataset_x['smoker'] = le.fit_transform(df_dataset_x['smoker'])
df_dataset_x = pd.get_dummies(df_dataset_x, ['region'])
dataset_x = df_dataset_x.to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
from tpot import TPOTRegressor
tpr = TPOTRegressor(max_time_mins=5, verbosity=3)
tpr.fit(training_dataset_x, training_dataset_y)
predict_result = tpr.predict(test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(test_dataset_y, predict_result)
print(f'Mean Absolute Error: {mae}')
tpr.export('automated-tpot-insurance.py')
#----------------------------------------------------------------------------------------------------------------------------
İstatistiksel ve matematiksel yöntemlerle model araması yapan yaygın kullanılan diğer bir otomatik makine öğrenmesi kütüphanesi de
"auto-sklearn" isimli araçtır. Auto-sklearn pek çok bakımdan TPOT kütüphanesine benzemektedir. Bazı araştırmalar iki kütüphaneyi
karşılaştırdıklarında auto-sklearn kütüphanesinin sınıflandırma tarzı problemlerde daha başarılı olduğunu TPOT kütüphanesinin ise
lojistik olmayan regresyon problemlerinde daha başarılı olduğunu bulmuştur.
Auto-sklearn kütüphanesi de scikit-learn kütüphanesi üzerine oturtulmuştur. Yani bu kütüphane de oluşturduğu modellerde sckit-learn
sınıflarını kullanmaktadır.
Maalesef auto-sklearn kütüphanesi aşırı UNIX/Linux bağımlı yazılmıştır. Bu nedenle en azından şimdilik Windows altında çalıştıtılamamaktadır.
Bu nedenle auto-sklearn kütüphanesini ya Linux'ta (ya da biraz uğraşarak macOS sistemlerinde) çalıştırmalısınız. Siz de bir sanal makineye Linux kurup
Linux içerisinde "Anaconda" dağıtımını yükleyip auto-sklearn kütüphanesini kullanabilirsiniz. Linux'un Mint dağıtımı Windows'a benzer bir
masaüstü görüntüsüne sahip olduğu için Windows'a alışmış olan kişilere daha aşina gelmektedir. Ancak bu bağlamda dağırımın bir önemi yoktur.
Auto-sklearn kütüphanesinin resmi sitesi aşağıda verilmiştir:
https://automl.github.io/auto-sklearn/master/#
Dokümanalara ve örnek programlara bu siteden erişilebilir.
Auto-sklearn kütüphanesi Linux'ta şöyle kurulabilir:
pip install auto-sklearn
Auto-sklearn kütüphanesi de tıpkı TPOT kütüpahnesi gibi verilerin hazır hale getirilmesi işlemini büyük ölçüde lendisi
yapmaktadır. Örneğin bizim özellik ölçeklemesi yapmamıza gerek kalmaz. Kütüphane zaten özellik ölçeklemesini kendisi yapmaktadır.
Ancak auto-sklearn de tıpkı TPOT gibi kategorik verilerin sayısal hale dönüştürülmesi işlemini kendisi yapmamaktadır. Bunu
uygulamacının yapması gerekmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Auto-sklearn genel kullanım biçimi olarak TPOT kütüphanesine oldukça benzemektedir. Kütüphane içerisinde iki temel sınıf
vardır: AutoSklearnClassifier ve AutoSklearnRegressor. Sınıflandırma problemleri için AutoSklearnClassifier sınıfı lojistik olmayan
regresyon problemleri için AutoSklearnRegressor sınıfı kullanılmaktadır. Kütüphanede ayrıca birkaç önemli global fonksiyon da bulunmaktadır.
AutoSklearnClassifier sınıfının __init__ metodunun parametrik yapısı şöyledir:
class autosklearn.classification.AutoSklearnClassifier(time_left_for_this_task=3600, per_run_time_limit=None,
initial_configurations_via_metalearning=25, ensemble_size: int | None = None,
ensemble_class: Type[AbstractEnsemble] | Literal['default'] | None = 'default',
ensemble_kwargs: Dict[str, Any] | None = None, ensemble_nbest=50, max_models_on_disc=50,
seed=1, memory_limit=3072, include: Optional[Dict[str, List[str]]] = None, exclude: Optional[Dict[str, List[str]]] = None,
resampling_strategy='holdout', resampling_strategy_arguments=None, tmp_folder=None, delete_tmp_folder_after_terminate=True,
n_jobs: Optional[int] = None, dask_client: Optional[dask.distributed.Client] = None, disable_evaluator_output=False,
get_smac_object_callback=None, smac_scenario_args=None, logging_config=None, metadata_directory=None,
metric: Scorer | Sequence[Scorer] | None = None, scoring_functions: Optional[List[Scorer]] = None,
load_models: bool = True, get_trials_callback: SMACCallback | None = None,
dataset_compression: Union[bool, Mapping[str, Any]] = True, allow_string_features: bool = True)
Tabii bu parametrelerin çok azı sıklıkla kullanılmaktadır. Burada time_left_for_this_task parametresi kullanılacak zaman miktarını saniye cinsinden
belirlemek amacıyla bulundurulmuştur. Bu parametre TPOT kütüphanesindeki max_time_mins parametresine benzemektedir. Ancak bu parametredeki değer
dakika cinsinden değil saniye cinsindendir. Metodun per_run_time_limit parametresi spesifik denenen bir model için maksimum harcanacak
zamanı belirtmektedir. Metodun diğer parametreleri için dokümanlara başvurulabilir.
AutoSklearnClassifier nesnesi yaratıldıktan sonra yine işlemleri başlatmak için fit metodu kullanılır. Model oluşturulduktan sonra predict
metodu ile tahminleme yapılabilir.
Araştırılan modeller hakkında bilgi edinmek için birkaç metot bulundurulmuştur. leaderboard metodu en iyi k tane modeli Pandas
DataFrame nesnesi olarak vermektedir. show_models metodu araştırlan modelleri bize bir sözlük nesnesi olarak vermektedir. score metodu ise
elde edilen en iyi modelin başarısını sınamak için kullanılmaktadır.
Aşağıda "breast-cancer" veri kümesi üzerinde autosklearn ile sınıflandırma işlemi yapılmıştır. Burada AutoSklearnClassifier
sınıf nesnesi yaratılmış ve toplam 5 dakikalık bir model araması yapılmıştır. Bu işlemden %96.4 lük bir başarı elde edilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import pandas as pd
df = pd.read_csv("breast-cancer-data.csv")
dataset_x = df.iloc[:, 2:-1].to_numpy()
dataset_y = np.zeros(len(df))
dataset_y[df['diagnosis'] == 'M'] = 1
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
from autosklearn.classification import AutoSklearnClassifier
aslc = AutoSklearnClassifier(time_left_for_this_task=5*60)
aslc.fit(training_dataset_x, training_dataset_y)
df_lb = aslc.leaderboard(top_k=3)
print(df_lb)
models_dict = aslc.show_models()
print(models_dict)
predict_result = aslc.predict(test_dataset_x)
print(predict_result)
score = aslc.score(test_dataset_x, test_dataset_y)
print(f'Accuracy Score: {score}') # 0.9649
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda belirttiğimiz gibi TPOT ile auto-sklearn arasında performans kıyaslaması yapan makaleler genel olarak auto-sklearn
kütüphanesinin sınıflandırma problemlerinde TPOT kütüphanesinin ise lojistik olmayan regresyon problemlerinde daha iyi sonuç verdiğini
göstermektedir.
Aşağıda "breast cancer veri kümesi her iki kütüphane uygulanarak bir kartşılaştırma yapılmıştır. Bu karşılaştırmada iki aracın da
toplam 5 dakika zaman kullanması sağlanmıştır. Burada elde edilen doğruluk skoru şöyledir:
TPOT Accuracy scor: 0.956140350877193
AutoSklearn Accuracy Score: 0.9649122807017544
Görüldüğü gibi auto-sklearn az da olsa TPOT'tan daha iyi sonuç vermiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import pandas as pd
df = pd.read_csv("breast-cancer-data.csv")
dataset_x = df.iloc[:, 2:-1].to_numpy()
dataset_y = np.zeros(len(df))
dataset_y[df['diagnosis'] == 'M'] = 1
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
# TPOT
from tpot import TPOTClassifier
tpc = TPOTClassifier(max_time_mins=5, verbosity=3)
tpc.fit(training_dataset_x, training_dataset_y)
score = tpc.score(test_dataset_x, test_dataset_y)
print(f'TPOT Accuracy score: {score}') # 0.9561
# AutoSklearn
from autosklearn.classification import AutoSklearnClassifier
aslc = AutoSklearnClassifier(time_left_for_this_task=5*60)
aslc.fit(training_dataset_x, training_dataset_y)
score = aslc.score(test_dataset_x, test_dataset_y)
print(f'AutoSklearn Accuracy Score: {score}') # 0.9649
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda zambak örneği benzer biçimde TPOT ve auto-sklearn kütüphaneleri ile çözülmüştür. Yine her iki çözümde de toplam 5 dakikalık
bir süre kullanılmıştır. Elde edilen doğruluk skoru şöyledir:
TPOT Accuracy scor: 0.9666666666666667
AutoSklearn Accuracy Score: 0.9666666666666667
Görüldüğü gibi buradaki başarı tamamen aynıdır. Tabii bu aynılık durumu veri kümesinin kendisine özgü durumundan kaynaklanmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('iris.csv')
dataset_x = df.iloc[:, 1:-1].to_numpy()
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
dataset_y = le.fit_transform(df.iloc[:, -1])
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
from tpot import TPOTClassifier
tpc = TPOTClassifier(max_time_mins=5, verbosity=3)
tpc.fit(training_dataset_x, training_dataset_y)
score = tpc.score(test_dataset_x, test_dataset_y)
print(f'TPOT Accuracy scor: {score}')
# AutoSklearn
from autosklearn.classification import AutoSklearnClassifier
aslc = AutoSklearnClassifier(time_left_for_this_task=5*60)
aslc.fit(training_dataset_x, training_dataset_y)
score = aslc.score(test_dataset_x, test_dataset_y)
print(f'AutoSklearn Accuracy Score: {score}')
#----------------------------------------------------------------------------------------------------------------------------
Auto-sklearn ile lojistik olmayan regresyon problemleri için AutoSklearnRegressor sınıfı kullanılmaktadır. Sınıfın __init__ metodunun
parametrik yapısı şöyledir:
class autosklearn.regression.AutoSklearnRegressor(time_left_for_this_task=3600, per_run_time_limit=None,
initial_configurations_via_metalearning=25, ensemble_size: int | None = None,
ensemble_class: Type[AbstractEnsemble] | Literal['default'] | None = 'default', ensemble_kwargs: Dict[str, Any] | None = None,
ensemble_nbest=50, max_models_on_disc=50, seed=1, memory_limit=3072, include: Optional[Dict[str, List[str]]] = None,
exclude: Optional[Dict[str, List[str]]] = None, resampling_strategy='holdout', resampling_strategy_arguments=None,
tmp_folder=None, delete_tmp_folder_after_terminate=True, n_jobs: Optional[int] = None,
dask_client: Optional[dask.distributed.Client] = None, disable_evaluator_output=False, get_smac_object_callback=None,
smac_scenario_args=None, logging_config=None, metadata_directory=None, metric: Scorer | Sequence[Scorer] | None = None,
scoring_functions: Optional[List[Scorer]] = None, load_models: bool = True, get_trials_callback: SMACCallback | None = None,
dataset_compression: Union[bool, Mapping[str, Any]] = True, allow_string_features: bool = True)
Görüldüğü metodun çok fazla parametresi vardır. Bu parametrelerin hepsi default değer almaktadır. Yine buradaki en önemli parametre
time_left_for_this_task parametresidir. Bu parametre sınıfın model araştırmak için kullanacağı maksimum saniye sayısını belirtmektedir.
Sınıfın kullanımı AutoSklearnClassifier sınıfında olduğu gibidir. Yine fit işlemi ile eğitim yapılır. predict işlemi ile kestirim yapılır.
score metodu ile de regresyonun başarı skoru elde edilir. score metodu bize R^2 değerini vermektedir. R^2 değeri ne kadar yüksekse kestirimin
başarısı o kadar iyidi olacaktır. Yine sınıfın leaderboard isimli metodu ve show_models isimli metodu bulunmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda "Boston Housing Price" veri kümesi hem TPOT hem de auto-sklearn kullanılarak çözülmüştür. Çözümden sonra "mean absolute error"
değeri elde edilip yazdırılmıştır. Yine her iki deneme için de beşer dakikalık model arama zamanı kullanılmıştır.
Elde edilen değerler şöyledir:
TPOT Mean Absolute Error: 2.407825001548432
AutoSklearn Mean Absolute Error: 2.3694112300872803
Burada auto-sklearn biraz daha iyi bir sonucun elde edilmesine yol açmıştır. Ancak 5 dakikalık bir denemeyle bu iki
kütüphanenin performanslarının kıyaslanması doğru değildir. TPOT arka planda model üretirken genetik algoritmaları kullanmaktadır.
Genetik algoritmalarda iyi bir soncun elde edilmesi için nispeten daha fazla zamana ihtiyaç vardır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
dataset_x = df.iloc[:, :-1].to_numpy(dtype='float32')
dataset_y = df.iloc[:, -1].to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
from tpot import TPOTRegressor
tpr = TPOTRegressor(max_time_mins=5, verbosity=3)
tpr.fit(training_dataset_x, training_dataset_y)
predict_result = tpr.predict(test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(test_dataset_y, predict_result)
print(f'TPOT Mean Absolute Error: {mae}')
from autosklearn.regression import AutoSklearnRegressor
aslr = AutoSklearnRegressor(time_left_for_this_task=5*60)
aslr.fit(training_dataset_x, training_dataset_y)
predict_result = aslr.predict(test_dataset_x)
mae = mean_absolute_error(test_dataset_y, predict_result)
print(f'AutoSklearn Mean Absolute Error: {mae}')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda sağlık sigortası poliçesinin bedelini tahmin etmeye yönelik hazırlanmış olan "insurance.csv" dosyası üzerinde
TPOT ve auto-sklearn uygulanmıştır. Yine model araması için 5 dakikalık bir süre belirlenmiştir. Elde edilen sonuçlar şöyledir:
TPOT Mean Absolute Error: 2281.1085680343954
AutoSklearn Mean Absolute Error: 1955.048485072691
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('insurance.csv')
dataset_y = df['charges'].to_numpy(dtype='float32')
df_dataset_x = df.drop(['charges'], axis=1)
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
df_dataset_x['sex'] = le.fit_transform(df_dataset_x['sex'])
df_dataset_x['smoker'] = le.fit_transform(df_dataset_x['smoker'])
df_dataset_x = pd.get_dummies(df_dataset_x, ['region'])
dataset_x = df_dataset_x.to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
from tpot import TPOTRegressor
tpr = TPOTRegressor(max_time_mins=5, verbosity=3)
tpr.fit(training_dataset_x, training_dataset_y)
predict_result = tpr.predict(test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(test_dataset_y, predict_result)
print(f' TPOT Mean Absolute Error: {mae}')
from autosklearn.regression import AutoSklearnRegressor
aslr = AutoSklearnRegressor(time_left_for_this_task=5*60)
aslr.fit(training_dataset_x, training_dataset_y)
predict_result = aslr.predict(test_dataset_x)
mae = mean_absolute_error(test_dataset_y, predict_result)
print(f'AutoSklearn Mean Absolute Error: {mae}')
#----------------------------------------------------------------------------------------------------------------------------
Makine öğrenmesinde "pipeline (boru hattı)" peşi sıra yapılacak bir grup işlemi belirtmektedir. Çünkü veriler üzerinde
bazı işlemler biribiri ardına yapılarak sonuç elde edilmektedir. Örneğin bir sinir ağında çeşitli katmanlar aslında
peşi sıra yapılan işlemleri belirtmektedir.
Bilindiği gibi scikit-learn kütüphanesi yapay sinir ağı kütüphanesi değildir. İstatistiksel ve matematiksel yöntemlerle makine
öğrenmesi işlemleri için oluşturulmuş bir kütüphanedir. İşte sckit-learn kütüphanesinde de bir dizi işlemler bir pipeline
biçiminde oluşturulabilmekte ve sanki bu bir dizi işlem tek bir işlemmiş gibi tek hamlede işleme sokulabilmektedir.
Yukarıda TPOT kütüphanesinin export edilen dosyalarında da scikit-learn içeisindeki pipeline mekanizmasının kullanıldığını görmüştük.
Sckit-learn içerisinde pipleine meknizamasında sıklıkla karşılaşılan üç öğe Pipeline sınıfı, make_pipline fonksiyonu ve ColumnTransformer
sınıfıdır.
Pipeline mekanizaması ana olarak Pipeline sınıfı tarafından oluşturulmaktadır. nake_pipeline fonksiyonu aslında Pipeline sınıfı
türünden nesne oluşturmak için kullanılan bir wrapper fonksiyondur. ColumnTransformer sınıfı ise veri kümesindeki bazı sütunlar üzerinde
işlem yapmak amacıyla bulundurulmuştur.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
PipeLine sınıfı sklearn.pipeline modülünde bulunmaktadır. Sınıfın __init__ metodunun parametrik yapısı şöyledir:
class sklearn.pipeline.Pipeline(steps, *, memory=None, verbose=False)
Burada asıl önemli parametre steps isimli parametredir. Bu parametre bir demet listesi olmak zorundadır. Demetler iki elemanlıdır.
Demetlerin birinci elemanı bir isimden (bu isim herhangi bir isim olabilir) ikinci elemanı ise uygulanack işlemi belirten sınıf nesnesinden
oluşmalıdır. Tabii demetin son elemanı bir estimator olmak zorundadır.
Pipeline nesnesi oluşturulduktan sonra artık sınıfın çeşitli metotları uygulanabilir. Örneğin fit metodu uygulandığında bu fit metodu
aslında PipeLine nesnesinde belirtilen demetlerdeki elemanlar üzerinde fit_transform ve fit işlemleri yaparak eğitimi gerçekleitir. Örneğin:
pipeline = Pipeline(steps=[('MinMax Scaling', MinMaxScaler()), ('DBScan', KMeans(n_clusters=3))])
pipeline.fit(dataset)
Burada biz fit işlemi yapmakla aslında önce MinMaxScaler nesnesini kullanarak fit_transform yapıp elde edilen sonucu KMenas nesnesini
kullanarak fit yapmış olduk.
Pipeline sınıfının predict metodu benzer biçimde bizim Pipeline nesnesine verdiğimiz demet elemanları üzerinde yine transform işlemini
yapar. Son estimator nesnesi üzerinde de predict işlemini yapar. Örneğin:
predict_result = pipeline.predict(predict_data)
Burada predcit_data üzerinde önce transform işlemi yapılacak (fit_transform değil) ondan elde edilen sonuç ise KMeans sınıfının
predict metoduna sokulacaktır.
Görüldüğü gibi bu pipeline işleminde amaç peşi sıra giden bir grup işlemi sanki tek işlem gibi ele almaktır. Tabii bu mekanizmanın sağlanması için
"çokbiçimli (polymophic)" bir sınıf sisteminin bulunuyor olması gerekir. Gerçekten de sckit-learn içerisinde sınıfların benzer işi yapan
metrotlarına aynı isimler verilmiştir.
Pipeline sınıfının score metodu benzer biçimde önce transform işlemlerini yapıp demetin son estimator elemanında score işlemini gerçekleştirir.
Aşağıdaki örnekte zambak veri kümesi üzerinde sckit-learn kütüphenesindeki pipeline mekanizması uygulanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('iris.csv')
dataset = df.iloc[:, 1:-1].to_numpy()
from sklearn.preprocessing import MinMaxScaler
from sklearn.cluster import KMeans
from sklearn.pipeline import Pipeline
pipeline = Pipeline(steps=[('MinMax Scaling', MinMaxScaler()), ('DBScan', KMeans(n_clusters=3))])
pipeline.fit(dataset)
import numpy as np
predict_data = np.array([[7.7, 3.0, 6.1, 2.3], [6.3, 3.4, 5.6, 2.4], [6.4, 3.1, 5.5, 1.8]])
predict_result = pipeline.predict(predict_data)
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
Örneğin biz önce standart ölçekleme yapıp sonra PCA işlemi ile sütun sayısını 5'e düşürüp bu 5 sütun üzerinde lojistik
regresyon uygulayacak olalım. Pipeline nesnesini şöyle oluşturabiliriz:
scaler = StandardScaler()
pca = PCA(n_componenets=5)
logistic = LogisticRegression(max_iter=10000, tol=0.1)
pipeline = Pipeline(steps=[("scaler", scaler), ("pca", pca), ("logistic", logistic)])
pipeline.fit(training_dataset_x, training_dataset_y)
Aşağıdaki örnekte MNIST veri kümesinde Pipeline mekanizması yoluyla lojistik regresyon uygulunamıştır. Oluşturulan Piplene'da
önce MinMaxScaler işlemi, sonra PCA işlemi sonra da lojistik regresyon işlemi yapılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df_training = pd.read_csv('mnist_train.csv')
df_test = pd.read_csv('mnist_test.csv')
training_dataset_x = df_training.iloc[:, 1:].to_numpy()
training_dataset_y = df_training.iloc[:, 0].to_numpy()
test_dataset_x = df_test.iloc[:, 1:].to_numpy()
test_dataset_y = df_test.iloc[:, 0].to_numpy()
from sklearn.preprocessing import MinMaxScaler
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression
mms = MinMaxScaler()
pca = PCA(n_components=100)
lr = LogisticRegression(max_iter=1000)
from sklearn.pipeline import Pipeline
pipeline = Pipeline(steps=[('MinMaxScaler', mms), ('PCA', pca), ('LogisticRegression', lr)])
pipeline.fit(training_dataset_x, training_dataset_y)
score = pipeline.score(test_dataset_x, test_dataset_y)
print(score)
#----------------------------------------------------------------------------------------------------------------------------
Pipeline sınıfı için make_pipeline isimli yardımcı bir fonksiyon da bulundurulmuştur. make_pipeline fonksiyonu bizden yalnızca
transformer ve estimator nesnelerini almaktadır. İsimlerini almamaktadır. İsimler bu nesnelere ilişkin sınıf isimlerinden otomatik oluşturulmaktadır.
Fonksiyon bize Pipeline nesnesi vermektedir. Fonksiyonun tek faydası nesne isimlerinin girilmesini elimine etmesidir.
Fonksiyonun parametrik yapısı şöyledir:
sklearn.pipeline.make_pipeline(*steps, memory=None, verbose=False)
Fonksiyon trasnformet ve estimator nesnelerini tek tek komut satırı argümanı olarak almaktadır. Örneğin:
mms = MinMaxScaler()
pca = PCA(n_components=100)
lr = LogisticRegression(max_iter=1000)
from sklearn.pipeline import make_pipeline
pipeline = make_pipeline(mms, pca, lr)
Aşağıda yukarıdaki örneğin make_pipeline fonksiyonu ile gerçekleştitrilmesi verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df_training = pd.read_csv('mnist_train.csv')
df_test = pd.read_csv('mnist_test.csv')
training_dataset_x = df_training.iloc[:, 1:].to_numpy()
training_dataset_y = df_training.iloc[:, 0].to_numpy()
test_dataset_x = df_test.iloc[:, 1:].to_numpy()
test_dataset_y = df_test.iloc[:, 0].to_numpy()
from sklearn.preprocessing import MinMaxScaler
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression
mms = MinMaxScaler()
pca = PCA(n_components=100)
lr = LogisticRegression(max_iter=1000)
from sklearn.pipeline import make_pipeline
pipeline = make_pipeline(mms, pca, lr)
pipeline.fit(training_dataset_x, training_dataset_y)
score = pipeline.score(test_dataset_x, test_dataset_y)
print(score)
#----------------------------------------------------------------------------------------------------------------------------
Pipeline konusuyla ilgili olan çok kullanılan bir sınıf da ColumnTransformer isimli sınıftır. Bu sınıf veri kümesinin belirli sütunları
üzerinde hazırlık işlemlerinin yapılması için düşünülmüştür. Örneğin elimizde bir veri kümesi olsun ve biz bu veri kümesinin bir
sütununu "one hot encoding" işlemine sokmak isteyelim. Bir sütununu da eksik veriler nedeniyle impute etmek isteyelim. Bu işlemi
ColumnTransformer sınıfıyla yapabiliriz. ColumnTransformer sınıfı Pipeline sınıfı ile birlikte kullanılacak biçimde tasarlanmıştır.
ColumnTransformer sınıfının __init__ metodunun parametrik yapısı şöyledir:
class sklearn.compose.ColumnTransformer(transformers, *, remainder='drop', sparse_threshold=0.3, n_jobs=None,
transformer_weights=None, verbose=False, verbose_feature_names_out=True)
Metodun en önemli parametresi ilk parametresidir. Bu parametre "transformer" nesnelerinden oluşmaktadır. Parametre üç elemanlı
demetlerden oluşan bir liste biçiminde oluşturulmaktadır. Demetin birinci elemanı transformer nesnesinin ismini belirtir.
Bu isim herhangi bir biçimde girilebilir. İkinci eleman transformer nesnesinin kendisini belirtmektedir. Üçüncü eleman veri kümesindeki
hangi sütunların işleme sokulacağını belirten bir listedir. Metodun remainder parametresi default durumda "drop" biçimindedir. Bu parametre
ilgili sütunlar üzerinde işlemler yapıldıktan sonra veri tablosunun kalan sütunlarının ne yapılacağını belirtmektedir. "drop" işlemi
üzerinde işlem yapılmayan sütunların atılacağı anlamına gelmektedir. "drop" yerine "passthrough" değeri geçilirse diğer sütunlar muhafaza
edilmektedir. Normal olarka bu parametre "drop" geçildiğinde yalnızca üzerinde işlem yapılan sütun sonuçları elde edilir. Nihayetinde bunlar
birleştirilerek sonuç oluşturulur. remainder parametresi "passthrough" geçilirse üzerinde hiç işlem yapılmamış sütunlar nihai sonuçta
bulundurulmaktadır.
ColumnTransformer nesnesi yaratıldıktan sonra fit_transform işlemi yapıldığında her transformer üzerinde fit transform işlemi yapılır.
Her transformer nesnesinin fit_transform metodundan ürettiği değer sonrakine verilmektedir. Tabii fit_transform yerine önce fit sonra
transform işlemi de yapılabilmektedir. fit işlemi yapıdlığında her transform nesnesiyle fit işlemi yapılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte kullanılan "transformer-1.csv" dosyasının içeriği şöyledir:
X,Y,Z
a,12.3,red
a,3.4,green
b,5.9,red
c,nan,blue
c,4.9,red
b,nan,blue
a,8.2,blue
b,9.2,green
b,nan,red
a,6.45,blue
c,4.32,green
c,12,green
Burada bizim amacımız 0'ınci ve 2'inci sütunlara "one hot encoding" uygulamak, 1'inci sütuna ise ortalamayla imputation uygulamak olsun.
ColumnTransformer nesnesini şöyle oluşturabiliriz:
mean_imputer = SimpleImputer(strategy='mean')
frequent_imputer = SimpleImputer(strategy='most_frequent')
ohe = OneHotEncoder(sparse=False)
from sklearn.compose import ColumnTransformer
ct = ColumnTransformer([('OneHotEncoder', ohe, [0, 2]),
('SimpleImputer (Mean)', mean_imputer, [1])],
remainder='drop')
Buarada önce 0 ve 2 numaralı sütunlar "one hot encoding" işlemine sokulmuş, sonra 1 numaralı sütun impute işlemine sokulmuştur.
il ve ikinci işlemin sonuçları birleştirilmiştir.
Yukarıdaki veri kümesine ilişkin bu işlemin kodu aşağıda verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('transformer-1.csv')
data = df.to_numpy()
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder
mean_imputer = SimpleImputer(strategy='mean')
frequent_imputer = SimpleImputer(strategy='most_frequent')
ohe = OneHotEncoder(sparse=False)
from sklearn.compose import ColumnTransformer
ct = ColumnTransformer([('OneHotEncoder-0', ohe, [0, 2]),
('SimpleImputer (Mean)-1', mean_imputer, [1])],
remainder='drop')
data_result = ct.fit_transform(data)
print(data_result)
#----------------------------------------------------------------------------------------------------------------------------
ColumnTransformer sınıfının __init__ metodundaki remainder parametresinin "drop" ya da "passthroug" geçilmesi arasındaki
fark aşağıdaki örnekte açıkça görülmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
import numpy as np
df = pd.DataFrame({'X': ['a', 'b', 'c', 'a', 'c'], 'Y': [3, 5, np.nan, 9, 2], 'Z': [1, 2, 3, 4, 5]})
data = df.to_numpy()
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder
mean_imputer = SimpleImputer(strategy='mean')
ohe = OneHotEncoder(sparse=False)
from sklearn.compose import ColumnTransformer
ct = ColumnTransformer([('OneHotEncoder', ohe, [0]),
('SimpleImputer', mean_imputer, [1])], remainder='drop')
data_result = ct.fit_transform(data)
print(data_result)
ct = ColumnTransformer([('OneHotEncoder', ohe, [0]),
('SimpleImputer', mean_imputer, [1])], remainder='passthrough')
data_result = ct.fit_transform(data)
print(data_result)
#----------------------------------------------------------------------------------------------------------------------------
Maalesef ColumnTransformer sınıfı aynı sütun üzerinde birden fazla işlem yapamamaktadır. Örneğin biz önce bir sütunu impute işlemine sokup onun
sonucunu "one hot encoding" işlemine sokmak isteyebiliriz. Ancak ColumnTransformer sınıfı bunu tek hamlede yapamamaktadır.
Örneğin "transformer-2.csv" dosyası şöyle olsun:
X,Y,Z
a,12.3,red
a,3.4,green
b,5.9,red
c,nan,nan
c,4.9,red
b,nan,blue
a,8.2,green
b,9.2,green
b,nan,nan
a,6.45,blue
c,4.32,nan
c,12,blue
Burada bizim 0'ıncı sütunu one hot encoding, 1'inci sütunu imputing ve 2'inci sütunu da önce impute sonra one hot encoding yapmak isteyelim.
Bunu tek aşamada yapamamaktayız. Pekiyi nasıl yapabiliriz? İlk akla gelen yöntem bu tür durumlarda birden fazla ColumnTransformer nesnesi kullanmak olabilir.
Aşağıda bu yöntem uygulanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('transformer-2.csv')
data = df.to_numpy()
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder
mean_imputer = SimpleImputer(strategy='mean')
frequent_imputer = SimpleImputer(strategy='most_frequent')
ohe = OneHotEncoder(sparse=False)
from sklearn.compose import ColumnTransformer
ct1 = ColumnTransformer([('SimpleImputer (Mean)', mean_imputer, [1]),
('SimpleImputer (Most Frequent)', frequent_imputer, [2])],
remainder='passthrough')
data_result = ct1.fit_transform(data)
print(data_result)
ct2 = ColumnTransformer([('OneHotEncoder', ohe, [1, 2])],
remainder='passthrough')
data_result = ct2.fit_transform(data_result)
print(data_result)
#----------------------------------------------------------------------------------------------------------------------------
Tabii biz İki ColumnTransformer nesnesini Pipeline ile birbirine de bağlayabiliriz. Örneğin:
ct1 = ColumnTransformer([('SimpleImputer (Mean)', mean_imputer, [1]),
('SimpleImputer (Most Frequent)', frequent_imputer, [2])],
remainder='passthrough')
ct2 = ColumnTransformer([('OneHotEncoder', ohe, [1, 2])],
remainder='passthrough')
pipeline = make_pipeline(ct1, ct2)
data_result = pipeline.fit_transform(data)
print(data_result)
Aşağıda bu yöntemin uygulanma örneği verilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('transformer-2.csv')
data = df.to_numpy()
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder
mean_imputer = SimpleImputer(strategy='mean')
frequent_imputer = SimpleImputer(strategy='most_frequent')
ohe = OneHotEncoder(sparse=False)
from sklearn.compose import ColumnTransformer
ct1 = ColumnTransformer([('SimpleImputer (Mean)', mean_imputer, [1]),
('SimpleImputer (Most Frequent)', frequent_imputer, [2])],
remainder='passthrough')
ct2 = ColumnTransformer([('OneHotEncoder', ohe, [1, 2])],
remainder='passthrough')
from sklearn.pipeline import make_pipeline
pipeline = make_pipeline(ct1, ct2)
data_result = pipeline.fit_transform(data)
print(data_result)
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de "insurance" örneğini LinearRegression regresyon yoluyla çözerken için Pipeline mekanizmasını kullanalım. "insurance.csv"
dosyasının görünümü şöyledir:
age,sex,bmi,children,smoker,region,charges
19,female,27.9,0,yes,southwest,16884.924
18,male,33.77,1,no,southeast,1725.5523
28,male,33,3,no,southeast,4449.462
33,male,22.705,0,no,northwest,21984.47061
32,male,28.88,0,no,northwest,3866.8552
Burada sex, smoker sütunları LabelEncoder sınıfı ile nümerik hale dönüştürülmelidir. region sütunu one hot encoding dönüştürmesine
sokulmalıdır. Tahmin edilmeye çalışılan hedef sütun ise charges sütunudur. PipeLine mekanizmasını şöyle oluşturabiliriz:
ct = ColumnTransformer([('OrdinalEncode', OrdinalEncoder(), [1, 4]),
('OneHotEncoder', OneHotEncoder(sparse=False), [5])], remainder='passthrough')
pipeline = make_pipeline(ct, MinMaxScaler(), LinearRegression())
Burada biz orijinal veri kümesinin 0'ıncı ve 4'üncü sütunlarına LabelEncoder uygulamak yerine OrdinalEncoder uyguladık. Anımsanacağı gibi
bu iki sınıf arasındaki tek fark OrdinalEncoder sınıfının alfabetik sıraya göre sayısallaştırma yapmasıdır. LabelEncoder sınıfının fit_transform
metodu bizim pipeline mekanizmasına uygun parametrik yapıya sahip değildir.
Aşağıda örneği tamamı verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('insurance.csv')
dataset_y = df['charges'].to_numpy(dtype='float32')
dataset_x = df.drop(['charges'], axis=1).to_numpy()
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2, random_state=12345)
from sklearn.preprocessing import OrdinalEncoder, OneHotEncoder, MinMaxScaler
from sklearn.linear_model import LinearRegression
from sklearn.compose import ColumnTransformer
ct = ColumnTransformer([('OrdinalEncode', OrdinalEncoder(), [1, 4]),
('OneHotEncoder', OneHotEncoder(sparse=False), [5])], remainder='passthrough')
from sklearn.pipeline import make_pipeline
pipeline = make_pipeline(ct, MinMaxScaler(), LinearRegression())
pipeline.fit(training_dataset_x, training_dataset_y)
predict_result = pipeline.predict(test_dataset_x)
from sklearn.metrics import mean_absolute_error
mae = mean_absolute_error(test_dataset_y, predict_result)
print(mae)
#----------------------------------------------------------------------------------------------------------------------------
Anımsanacağı gibi kursun başlarında makine öğrenmesini üç kısmı ayırmıştık:
1) Denetimli Öğrenme (Supervied Learning)
2) Denetimsiz Öğrenme (Unsupervised Learning)
3) Pekiştirmeli Öğrenme (Reinforcement Learning)
Biz denetimli öğrenme ve denetimsiz öğrenmenin temel yöntemlerini uygulamalı biçimde inceledik. Şimdi de pekiştirme öğrenme denilen
yöntemler grubunu inceleyeceğiz. Pekiştirme öğrenme makine öğrenmesinde ilginç bir konudur. Pekiştirmeli öğrenme yapay zeka kavramının
halk arasındaki karşılığına en yakın kısmını oluşturmaktadır. Pekiştirmeli öğrenme "kendine öğrenen sistemler" oluşturma gayretindedir.
Pekiştirmeli öğrenme psikolojideki "edimsel koşullanma (operant conditioning)" denilen kavramdan hareketle geliştirilmiştir. Edimsel
koşullanma canlılar için en önemli öğrenme yöntemlerinden biridir. Pek çok olgu edimsel koşullanma yoluyla öğrenilmektedir.
Bu öğrenme biçiminde organizma bir eylemde bulunur. Bu eylem sonucunda hoşa giden bir durum oluşursa bu hoşa giden durumu yeniden oluşturabilmek için
organizma eylemi yineler. Böylece yinelenen eylem davranışın bir parçası haline gelir ve öğrenilmiş olur. Bu süreçte organizmanın
hoşuna gidecek uyaranlara "pekiştireç (reinforecer)" denilmektedir. Eylem ne kadar pekiştirilirse o kadar iyi öğrenilir.
Pekiştireçler bir ödelül (reward) niteliğindedir. Örneğin çocuk arzu edeilen bir davranış yaptığında ona şeker verilirse çocuk
o davranışı yeniden yapar. Çocuk terbiyesinde edimsel koşullanma çok etkilidir. Bu konudaki ilk çalışmalar Edward Thorndike isimli
psikolog tarafından yapılmıştır. Thorndike bu süreci fark etmiş ve bunu "etki yasası (rule of effect)" biçiminde isimlendirmiştir.
Ancak kuramı asıl teori haline getiren kişi B. Frederick Skinner'dır. Pekiştireç terimi de Skinner tarafından uydurulmuştur.
Skinner konunun epey ayrıntısına girmiş ve çeşitli yönlerden edimsel koşullanmayı incelemiştir. Edimsel koşullanmanın davranışı
şekillendirmede önemini göstermiştir. Skinner'a göre iki tür pekiştireç vardır: Pozifit ve negatif. Pozitif pekiştireçler doğurdan
organizamaya haz veren uyaranlardır. Negatif pekiştireçler ise organizmanın içinde bulunduğu hoş olmayan durumu ortadan kaldıran pekiştireçlerdir.
Hem pozitif hem de negatif pekiştirilecek sonuçta organizma için daha iyi bir durum sağlarlar. Örneğin arabalarda emniyet kemeri takılmadığında
bir ses çıkmaktadır. Kişiler de bu sesi duymamak için emniyet kemerini takarlar. Sonra zamanla bu sese aldırış etmeden emniyet kemerini takmak normal
bir uygulama haline gelir. Burada emniyet kemeri takıldığında kesilen ses negatif pekiştireçtir. Skinner pekiştireçlerin hangi sıklıkta
uygulanması gerektiği konusunda da çalışmalar yapmıştır. Pekiştireçler arzu edilen her davranış tekrarlandığında verilebilir. Buna sabit oranlı
pekiştirme tarifesi denilmektedir. Eğer pekiştireç belli miktar arzu edilen davranış tekrarlandığında veriliyorsa buna da değişken oranlı pekiştirme
tarififesi denir. Eğer pekiştireç sabit zamanlarda veriliyorsa buna sabit zamanlı tarife, değişken zamanlı veriliyorsa buna da değişken zamanlı
tarife denilmektedir. Örneğin çalışan kişilere verilen maaş "sabit zamanlı (fixed time)" bir pekiştireeçtir. Kumar makinelerinde
kişi tesadüfi belli zamanlarda ödül almaktadır. Bu da "değişkenli zamanlı (variable time)" pekiştirme tarifesine örnektir.
Bir çocuk her soruyu doğru bildiğinde ödül veriliyorsa ya da her n tane soruyu doğru bildiğinde ödül veriliyorsa bu türlü bir pekiştirmeye
"sabit oranlı (fixed ratio)" pekiştirme denir. Eğer çocuk bazen 3, bazen 5 bazen 8 gibi miktarlarda soruyu doğru bildiğinde ödül veriliyorsa
buna da "değişken oranlı (variable ratio)" pekiştirme denilmektedir.
Ceza da bir çeşit edimsel koşullanma süreci oluşturmaktadır. Ceza da bir pekiştireçtir. Ancak bir eylemin tekrarlanmaması için uygulanır.
Eylem karşısında ceza gören organizma o eylimi yinelemez. Böylece istenemeyn davranışlar engellenmiş olur. Ancak ceza pek çok durumda
iyi bir işlev görmemektedir. Çünkü ceza ortadan kalktığında eski davranış geri gelmektedir. Ayrıca cezanın saldırgan davranışları artırma
gibi bir etkisi de vardır. Organizmaya doğurdan acı veren cezalara "birincil cezalar" denilmektedir. Organizmaya dolaylı acı veren cezalara ise
"ikincil cezalar" denir. Yaramazlık yapan çocuğun dövülmesi birincil cezadır. Ancak onun televizyon seyretmesine izin verilmemesi ikincil
cezaya örnektir. Yani ikincil cezada aslında organizmaya acı vermek yerine haz veren bir öğe ortamdan çekilmektedir. O nedenle birincil
cezalar yerine ikincil cezalar tercih edilmelidir.
Pekiyi edimsel koşullanma sonucunda öğrenilmiş bir davranış söz konusu olsun. Artık edim (action) terkrarlandığında pekiştireç
verilmezse ne olur? Örneğin bir çocuk ağladığında ebeyn onun isteğini karşılıyorsa bu uyaran pozitif bir pekiştireçtir. Çocuğun
ağlama davranışını artıracaktır. Ancak zamanla ebeveyn çocuk ağlsa da artık onun istediği şeyi almazsa zamanla "sönümlenme (extinction)"
oluşur. Yani öğrenilen davranış kaybolur.
Bazı canlılar ve özellikle insanlar başkalarını izleyerek onlara verilen pekiştireçleri görerek dolaylı bir biçimde de pekiştirmeye
maruz kalabilmektedir. Örneğin etrafını izleyen bir çocuk başka bir arkadaşının yaptığı davranış üzerine kendisinin değil ama
onun ödül aldığını görürse kendi davranışını da değiştirebilmektedir. Bu öğrenme biçiminde psikolojide "sosyal bilişsel öğrenme (social cognitive learning)"
denilmektedir.
Psikolojide organizmanın bütün bilgiişlem faaliyetlerine "biliş (cognition)" denilmektedir. Yani düşünme, akıl yürütme,
hafıza, algılama, bilinç, zeka gibi konular "bilişsil psikoloji (cognitive pschology)" denilen alana ilişkin konulardır.
Eskidne bilişsel faaliyetler "bilişsel psikoloji" içerisinde ele alınıyordu. Ancak bilişsel faaliyetleri beyin bölgeleriyle ilişkilendirerek inceleyen
ismine "bilişsel nörobilim (cognitive neuroscience)" denilen yeni bir bilim dalı ortaya çıkmıştır. Tıptaki nöroloji hastalık temelli olarak
konuya yaklaşmaktadır. Dolayısıyla sosyal bilişsel öğrenme yani başkalarını taklit ederek ve onların aldığı ödüllere ve cezaları göz önünde
bulundurarak öğrenme bilişsel unsurlar da içermektedir. Halbuki edimsel koşullanmada bilişsel faaliyetler en az düzeydedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Makine öğrenmesinde pekiştirmeli öğrenme tamamen psikolojideki edimsel koşullanma süreci taklit edilerek modellenmektedir.
Makine (yazılımı kstediyoruz) rastgele bir şeyler yapar. Ondan ödül elde ederse onu tekrarlamaya çalışır. En sonunda ödül kazamnak için
hedefi gerçekleştirmeyi öğrenir. Pekiştirmeli öğrenmede bazı terimler sıkça kullanılmaktadır. Öncelikle bu terimleri açıklayalım:
Yazılımsal Etmen (Software Agent): Pekiştirmeli öğrenmede öğrenme işlemini yapan yani edimlerde bulunan yazılıma "yazılımsal etmen"
denilmektedir. Sıklıkla "yazılımsal etmen" yerine yalnızca "etmen" sözcüğü kullanılmaktadır. Etmen öğrenmedeki aktördür.
Çevre (Environment): Etmenin içinde bulunduğu ortama denilmektedir. Örneğin bir labirentte yol bulmaya çalışan etmen söz konusu olsun. Burada
labirent çevreyi oluşturmaktadır. Etmenler bir çevre içerisinde bulunmaktadır.
Eylem (Action): Etmenin çevre içerisinde yaptığı faaliyetlere eylem (action) denilmektedir. Örneğin bir labirentte etmen sola, sağa,
yukarı ya da aşağıya gidebilir. Bu hareketler eylemleri belirtir. Etmen de çevrenin bir parçasıdır. Bir etmenin yapablieceği eylemlerin topluluğuna
"eylem alanı (action space)" denilmektedir.
Durum (Status): Çevrenin içinde bulunduğu hale "durum" denilmektedir. Örneğin etmen bir hareket yaptığında bir kapıılıyor olabilir.
Yani bu eylemde çevrede bir değişilik olmaktadır. İşte çevrenin her farklı haline durum denilmektedir. Bir labirennte etmen sola gidince
artık bir durum oluşmaktadır. Çünkü artık etmen başka bir yerdedir. Etmen de çevrenin bir parçasıdır.
Ödül/Ceza (Reward/Penalty): Ödül ve ceza pekiştirmeli öğrenmenin önemli bir unsudurur. Eemen bir eylem yaptığında çevrede bir durum
değişikliği olur. Bu durumda etmen bir ödül ya da ceza alabilir. Etmenin amacı ödülü maksimize etmek ve cezadan kaçınmaktır.
Yani pekiştirmeli öğrenmede bir eyleme ödül ya da ceza verilmezse öğrenme gerçekleşmez. Ödül ile ceza aynı etkiye yol açmaktadır.
Bir eylem ödülle de tekrarlanabilir. Ceza almayarak da tekrarlanabilir.
Bir çevre (environment) deterministik olabilir ya da olmayabilir. Deterministik çevre demek çevrenin belli bir durumunda belli bir eylem yapıldığında
ne olacağının bilinmesi yani yeni durumun biliniyor olması demektir. Başka bir deyişle deterministik bir çevrede belli bir
durumda belli bir eylem yapıldığı zaman her zaman aynı yeni durum oluşur.
Bir çevre ayrık (discrete) ya da sürekli (contigous) olabilir. Ayrık çevre demek çevredeki eylem sonucunda oluşan durumların sayısının
sınırlı olması demektir. Örneğin bir oyun programında etmen bir hareket yaptığında oluşacak yeni durum sınırlı sayıda ise bu çevre ayrıktır.
Ancak etmen bir eylem yaptığında oluşacak durumların sayısı sonsuz ise bu çevre sürekli bir çevredir. Pekiştirmeli öğrenmede genellikle
sürekli çevre ayrık hale getirilmektedir. Sonsuz sayıda durum ile başa çıkmak pek çok durumda zordur.
Bir çevredeki öğelerin durumlarının tespit edilebildiği çevrelere "gözlemlenebilir (observable)" çevre denilmektedir.
Gözlemlenebilir bir çevrede sonsuz sayıda durum olabilir. Ancak biz bu durumların her birini gerektiğinde öğrenebiliriz.
Eğer biz çevredeki öğelerin durumlarını öğrenemiyorsak böyle çeverelere "gözlemlenemeyen (unobservable)" çevre denilmektedir.
Bazen çevrenin bazı öğeleri gözlemlenebilir iken bazıları gözlemlenemeyebilir. Böyle çevrelere de "kısmi gözlemlenebilir (partially obvervable)"
çevreler denilmektedir. Çevredeki gözlemlenebilen bütün öğelere "gözlem alanı (abservation space)" de denilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Pekiştirmeli öğrenme pek alanda kullanılabilmektedir. Örneğin otomatik kontrol sistemleri pekiştirmeli öğrenme ile gerçekleştirilebilir.
Etmen pekiştirmeli öğrenme ile yeni bir durum karşısında ne yapması gerektiğini öğrenebilmektedir. Yani içinde bulunduğu duruma
uyum sağlayabilmektedir. Pekiştirmeli öğrenme finansal sistemlerde de kullanılabilmektedir. Pek çok veriyi elde edip kar maksimizasyonu
sağlamak için etmenin ne yapması gerektiği pekiştirmeli öğrenmeyle etmene öğretilebilmektedir. Pekiştirmeli öğrenme üretim problemlerine de
uygulanabilmektedir. Benzer biçimde ne zaman ne kadar stok tutulacağı içinde bulunulan duruma bağlıdır. Stok yönetim problemlerinde de pekiştirmeli
öğrenme kullanılabilmektedir. Dağıtım problemlerinde de problemin dinamik yapısından dolayı pekiştirmeli öğrenmeden faydalanılabilmektedir.
Oyun programları pekiştirmeli öğrenme için tipik örnekleri oluşturur.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Pekiştirme öğrenme çalışmaları için çevrenin oluşturulması gerekmektedir. Çevranin oluşturulması da biraz zahmetlidir. Bu
nedenle bu konuda çalışma yapan kurumlar çeşitli çevreler için simülatörler oluşturmaktadır. Böylece bu alanda çalışacak kişiler de
çevreleri kendileir oluşturmak yerine bu hazır simülatörleri kullanabilmektedir.
Pekiştirmeli öğrenme için çevre oluşturan çeşitli simülatör yazılımları gerçekleştirilmiştir. Bunların en ünlülerinden biri OpenAI
denilen kurumun geliştirmiş olduğu "gym" kütüphanesidir. OpenAI son zamanlarda ChatGPT'nin son versiyonlarıyla geniş kesimler
tarafından tanınır hale gelmiştir. OpenAI kar amacı gütmeyen bir kurum olarak kurulmuştu. Bu kurum pek çok büyük yatırımcıdan ve şirketten
yapay zeka çalışmalarını geliştirmek amacıyla maddi destek alıyordu. Ancak son zamanlarda OpenAI ticari dünyaya da açılmış, Microsoft ile
işbirliğine de girişmiştir. Yine son zamanlarda gym simülatörleri OpenAI bünyesinden çıkartılarak Farama denilen kuruma devredilmiştir.
gym ortamını egliştirneler de o kurum bünyesinde çalışmaya başlamıştır. Gym ortamının resmi sitesi şöyledir:
https://www.gymlibrary.dev/
OpenAI kurumunun gym kütüphanesi dışında benzer simülatörler sunan başka kurumlar ve kütüphaneler de bulunmaktadır. Örneğin
Google'ın DeepMind platformu da bu tarz simülatörler sunmaktadır.
gym ortamı aşaıdaki gibi krulabilir:
pip install gym
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
gym kütüphanesi içerisindeki simülatörlere "çevre (envirionment)" denilmektedir. Bu simülatörler çeşitli ana başlıklar altında
gruplandırılmıştır. Biz de kursumuzda bunlardan bir bölümünü kullanacağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Cart Pole isimli simülatörde hareketli bir parçaya (araba) bağlı direk vardır. Amaç bu direğin belli bir açıda düşürülmeden
dik tutulmasıdır. Simülatörde arabaya belli bir kuvvet soldan ya da sağdan uygulanır. Kuvvetin büyüklüğü değiştirilememektedir.
Yalnızca uygulama yönü değiştirilmektedir. (Tabii bir kuvvet belli bir noktaya uygulanırsa bir ivme oluşur). Dolayısıyla
simülatörde etmen için iki eylem tanımlıdır: Sola kuvvet uygulamak ya da sağa kuvvet uygulamak.
Bu simülatörde çevreden (environment) elde edilecek bilgiler dört tanedir: Arabanın x eksenindeki konumu, arabanın o andaki hızı,
direğin o andaki açısı ve direğin o andaki açısal hızı. Direğin açısı dik durumdan 12 dereceden fazla kayarsa denemenin başarısız olduğuna
karar verilmektedir. Başka bir deyişle amaç direğin 12 dereceden daha az bir açıyla dik tutulmasıdır. Simülatörde her bir eylem sonrasında
eğer direk 12 derece açının dışına çıkmamaışsa ödül olarak simülatör 1 puan vermektedir. Simülatör orta nokta 0 olmak üzere -0.05 ile
0.05 arasında rastgele bir x konumundan başlatılmaktadır. Simülasyon şu durumlarda sonlanmaktadır:
1) Arabaya bağlı direğin 12 direceden fazla sola ya da sağa yatması durumunda (başarısızlık).
2) Arabanın x ekseninde +2.4 ya da -2.4'lük bölgeden çıkmasıyla. Burası görünen bölgedir (başarısızlık).
3) 500 defadan fazla eylem yapıldığında (bu durumda işlem başarılmış kabul edilmektedir).
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
gym kütüphanesinin Mountain Car isimli simülatöründe yine bir araç vardır. Bu araç iki tarafı tepe olan bir çukur bölgede
bulunur. Bu araca soldan ya da sağdan sabit bir kuvvet uygulanabilmeketedir. Amaç bu aracın tepeyi aşarak bayrak olan noktaya
erişmesini sağlamaktır. Bu similatörde çevreden elde edilecek iki bilgi vardır. Arabanın x eksenindeki konumu ve arabanın hızı.
Arabaya uygulanacak üç eylem vardır: Soldan kuvvet uygulamak, sağdan kuvvet uygulmaak ve kuvvet uygulamayı kesmek. Bu eylemlerden biri
uygulandığında biz çevrenin o andaki durumunu elde edip sonraki eyleme karar verebiliriz. Tabii insan zekasıyla bu problem kolay bir
biçimde çözülebilmektedir. Arabaya önce soldan sürekli kuvvet uygularız. Araba tepeyi aşamayacaktır. Arabanın hızı 0'a kadar düşecektir.
Bu durumda arabaya ters yönde kuvvet uygularız bu biçimde işlemlerini devam ettiririz. Tabii bu simülatörden amaç arabanın tepeyi aşmasının
kendi kendine öğrenilmesidir. Simülatörde araba her eylem sonucunda eğer hedefe (bayrağa) ulaşamamışsa -1 ceza puanı verilmektedir.
Ödül ile cezanın benzer etkiler yaptığını anımsayınız. Simülatör araban konumunu -0.6 ile -0.4 arasında rastgele bir yerden başlatır.
Başlangıç hızı da 0 olmaktadır. Simülatör şu iki durumda sonlamkatadır:
1) Araba bayrak noktasına gelmiştir. Bayrak noktası +0.5 pozisyonundadır (başarı durumu).
2) 200 eylemden dfaha fazla eylem uygulanmıştır (başarısızlık).
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Taxi isimli simülatörde 5x5'lik bir matris içerisinde 4 tane durak vardır. Durakların yerleri sabittir. Yani simülatör her
çalıştırıldığında duraklar aynı yerlerde bulunur. Taksi matris içerisinde dört yöne hareket edilebilmektedir. Ancak matris içerisinde
duvarlar da vardır. Duvara çarpılırsa bu durum oyunun sonlanmasına yol açmaz ancak bir konum değişikliği oluşturmamaktadır.
Dört taksi durağı RGYB harfleriyle temsil edilmiştir. Mavi renkteki durak yolcunun alınacağı durağı, kırmızı renkteki durak yolcunun
bırakılacağı durağı belirtmektedir. Araba eğer içerisinde yolcu yoksa kırmızı renkte varsa mavi renkte gözükmektedir.
Bu similatörde etmenin (taksinin) 6 tane eylemi vardır. Yani simülatörün eyşem alanı (action space) 6 seçenekten oluşmaktadır. Simülatörderki
belli bir andaki durum sayısı 500 tanedir. Çünkü araba 5x5'lik matristeki herhangi bir yerde bulunuyor olabilir (25 seçenek).
Yolcu o sırada herhangi bir durakta ya da arabanın içerisinde bulunuyor olabilir (5 seçenek). Yolcu herahngi bir durağa bırakılacak
olabilir (4 seçenek). Dolayısıyla simülatörün "gözlem uzayı (observation sapce)" 500 durumdan birine ilişkin olabilir.
Simülatörün verdiği ödül/ceza puanları şöyledir:
- Eğer yolcu doğru yerde bırakılırsa +20 puan ödül
- Eğer yolcu yanlış yerden alınıp ya da yanlış yere bırakılırsa -10 puan ceza
- Her adımda -1 puan ceza
Pekiştirmeli öğrenmede ödülün en büyüklenmesi ve cezanın en küçüklenmesi istendiğine göre eğitim soncunda taksi en kısa yoldan
hedefine varmaya çalışacaktır.
Bu oyunda bitme koşulu yoktur.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Frozen Lake (donmuş göl) isimli simülatörde 4x4'lük ya da 8x8'lik bir göl vardır. Bu göl donmuş olmakla birlikte çatlak
kısımlardan oluşmaktadır. Amaç bir kişinin S noktasından G noktasına çatlak yerlere (bunlara delik de diyebiliriz) basmadan gidebilmesidir.
Simülatör kaygan (slippery) ya da kaygan olmayan modda çalıştırılabilemktedir. Kaygan olmayan modda etmen hangi yöne gidilecekse gerçekten o yöne
gitmektedir. Ancak kaygan modda etmen bir yöne gitmek isterken o yöne dik olan bir yöne de kayma neticesinde gidebilir. Örneğin
kaygan modda etmen sağa gitmek istesin. Sağ yöne dik olan yön yukarı aşağısıdır. O zaman etmen kayarak sağa gitmek isterken yukarı ya da
aşağıya da gidebilir. Kaygan modda etmen bir yöne giderken 1/3 olasılıkla istediği yöne gider. 1/3 olasılıkla dik yönlerden birine,
1/3 olasılıkla da dik yönlerden diğerine gitmektedir.
Bu simülatörde etmenin yapacağı eylem sayısı dört tanedir. Yani simülatörün eylem uzayı 4 elemandan oluşmaktadır. Simülatörden
elde edilen durum bilgisi yani gözlem uzayı 4x4'lük versiyon için 16 elemandan 8x8'lik versiyon için 64 elemandan oluşmaktadır.
Simülatör deliğe düşmede ve hedefe varamayan her adımda 0 puan, hedefe varıldığında ise +20 puan ödül vermektedir. Deliğe düşüldüğünde
oyun bitmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Lunar Lander (Aya inme) isimli simülatörde bir ay aracı vardır. Bu ay aracı iki bayrağın arasındaki bölgeye inmeye çalışmaktadır.
Etmen için (ay aracı) dört eylem söz konusudur: Motoru sola çalıştırmak, motoru sağa çalıştırmak, ana motoru çalıştırmak ve
motorları susturmak. Çevreden elde edilecek bilgiler ise 8 tanedir: Ay aracının x ve y eksenindeki koordinatları (2 tane),
x ve y eksenindeki doğrusal hızları (2 tane), açısı (1 tane), ay aracının açısal hızı (1 tane), ay aracının iki ayağının ayrı ayrı
yere deyip deymediği bilgisi (2 tane). Oyunun sonlanma koşulları şunlardır:
- Ay aracı hızlı bir biçimde yerle temas ederek parçalandığında
- Ay aracı görünen bölgenin sınırları dışına çıktığında
Simülatör eğer ay aracı bayrakla belirtilen bölgeye indirilmişse indirilmenin yumuşaklığına göre 100 puan ilke 140 puan arasında
ödül verilmektedir. Eğer ay aracı bayraklı bölgenin dışına indirildiğinde herhangi bir ödül ya da ceza verilmemektedir.
Eğer ay aracı yere sert inerek parçalanırsa -100 ceza puanı verilmektedir. Ana motor çalıştırıldığında -0.3 ceza puanı verilmektedir.
Sol ve sağ motorlar çalıştırılında ise -0.03 ceza puanı verilmektedir. Eğer ay aracı yere inip kararlı bir duruma gelirse +100
puan ödül verilmektedir.
Amaç en etkin biçimde ve en az ceza alacak biçimde (yani en yumuk ve hızlı biçimde) aracı indirmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
gym kütüphanesinde bol miktarda atari oyunları da vardır. Örneğin Pac-Man oyunu dünyanın en popüler atari oyunlarındandır.
Bu oyunda hareket eden oyuncuyu 4 hayalet öldürmeye çalışmaktadır. Oyuncu noktalarla belirtilen besinleri yiyerek onları bitirmeye çalışır.
Büyük yopaklar oyuncuya kuvvet kazandırmaktadır. Eğer büyük bir topak yenirse hayaletler maviye döner ve artık oyuncu da hayaletleri
yiyebilir hale gelir. Oyuncu hayalete yakalnırsa can kaybetmektedir. Oyuncunun toplamda dört canı vardır. Oyunda zaman geçtikçe
hayaletlerin hızları artar. Artan puan değerleri için her turda exkstra bir meyve verilir. Meyve yenirse ekstra puan kazanılır.
Oyunun başka ayrıntıları da vardır.
Pac-Man simülasyonunda simülatörün konfigürasyonuna göre eylemler değişebilmektedir. Temel eylem oyuncunun dört yönden birine gitmesi ya da
hiçbir yere gitmemesidir. Oyunda gözlem uzayı olarak bize PAc-Man'nin resimsel görüntüsü verilmektedir. Yani oyunun oynandığı alanın
resmi bize her adımda verilmektedir. Verilen bu resim 210x160x3 boyutlarındadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Tuğla kırmaca (breakout) en eski oyunlardan biridir. Yukarıda tuğlalar, aşağıda bir raket vardır. Top rakete çarpınca yansımaktadır.
Amaç topla tüm tuğlaları kırmaktır. Oyunda etmenin (raket) dört eylemi vardır: Raketi sola bir birim götürmek, sağa bir birim götürmek,
oyunu başlatan "fire" düğmesine basmak, hiçbir şey yapmamak. Oyun bize gözlemolarak yine o andaki durumu bir gri tonlamalı bir resim biçiminde vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
gym simülatörlerinde son zamanlarda bazı değişiklikler yapılmıştır. 0.21 versiyonu ile kursun yapıldığı sıradaki en yüksek versiyon
olan 0.26 arasındaki değişiklikler kodun gözden geçirilmesini gerekli hale getirebilmektedir. Aşağıdaki örnekler daha çok 0.21 versiyonu
dikkate alınarak verilmiştir.
gym simülatörleri şu adımlardan geçilerek kullanılmaktadır:
1) Her simülatörün bir ismi vardır. Bu isimlerin neler olduğu orijinal dokğmanlardan öğrenilebilir. gym modülündeki
make isimli fonksiyon simülatörün ismini ve bazı diğer parametrik bilgileri alır ve bize ismine "çevre nesnesi (environment object)"
denilen bir nesne verir. Çevre nesneleri farklı sınıflardan oluşsa da aslında hep aynı biçimde kullanılmaktadır. Örneğin:
import gym
env = gym.make('CartPole-v1)
Eskiden make işlemi sırasında render_mode belirtilmek zorunda değildi. Ancak kütüphanenin yeni versiyonlarında render işlemi için
artık render_mode gerekmektedir. Tipik render_mode "human" biçiminde kullanılabilir. Örneğin:
env = gym.make('CartPole-v1, render_mode='human')
2) Çevre nesnesi elde edildikten sonra reset metodu çağrılarak simülatörün reset edilmesi gerekir. reset metodu bize oyunun
başladığı ilk durumun gözlem değerini bir NumPy dizisi olarak vermektedir. Örneğin CartPole simülasyonunda bu gözlem değeri
aranbanın x eksenindeki knumu, arabanın hızı, diğerin açısı ve direğin açısal hızından oluşmaktadır. Örneğin:
obs = env.reset()
3) Etmene eylem yaptırmak için çevre sınıfının step metotları kullanılmaktadır. step metotları yapılacak eylemi anlatan bir
parametreye sahiptir. Tabii step metotların parametreleri simülatöre göre değişebilmektedir. Örneğin CartPole simülasyonunda
eylem 0 ya da 1'den oluşmaktadır. 0 motorun sola çalıştırılacağını 1 ise sağa çalıştıralacağını belirtmektedir. step metodu kütüphanenin
0.21 versiyonunda dörtlü bir demete geri dönerken 0.26'lı son versiyonda beşli bir demete geri dönmektedir.
0.21 versiyonundaki dörtlü demetin birinci elemanı eylem sonrasındaki çevrenin durumunu yani gözlem sonucunu vermektedir.
İkinci eleman eylem sonucunda elde edilecek ödülü belirtmektedir. (Tabii eğer bu değer pozitifse bir ödül, negatifse biz ceza söz konusudur.)
Demetin üçüncü elemanı oyunun bitip bitmediğini belirten bool bir değerdir. Eğer bu değer True ise oyun bitmiş, False ise bitmemiştir.
Demtin son elemanı ise simülatörün bazı bilgilerini vermektedir. Genellikle bu eleman kullanılmamaktadır. Örneğin:
obs, reward, done, info = env.step(action)
Ancak kütüphanein 0.26'lı versiyonlarında step metodu beşli bir demete geri dönmektedir. Demetin done ve info elemanları arasında
trunacted diye isimlendirilen simülatörün erken sonlanıp sonlanmadığını belirtien bir eleman daha vardrı:
obs, reward, done, truncated, info = env.step(action)
Eğer bu info değerleri ile ilgilenilmiyorsa okunabilirliği artırmak için demetin son elemanı _ ile isimlendirilebilir:
obs, reward, done, _ = env.step(action)
step metodunun geri dönüş değerine ilişkin demete "truncation" elemanı yeni eklenmiştir. Eskşden step metodu 4 elemanlı bir
demete geri dönmekteydi.
step metodu bir kez değil bir döngü içerisinde çağrılarak işlemler peşi sıra yaptırılır. Tipi bir döngü şöyle olabilir:
while True:
obs, reward, done, _ = env.step(action)
if done:
break
env.render()
4) reset ve step işlemlerinden sonra simülatörün durumunu görüntülemek için çevre sınıfının render metodu kullanılmaktadır.
Bu metot make fonksiyonunda belirtilen render_mode parametresine göre görüntüleme yapmaktadır. En normal render_mode 'human'
biçimindedir. gym kütüphanesi simülatörü görüntülemek için "pygame" isimli bir programı kullanmaktadır. Dolayısıyla sistemimizde
bu programın da kurulu olması gerekir.
5) En sonunda simülatörü çevre sınıfının close metodu ile kapatmak gerekir.
Simülatör çalıştırılırken step metodundan elde edilen bilgilerden hareketle done koşulu sağlandığında işlem sonlandırılmalıdır.
Aslında bu sonlandırma zorunlu değildir. Ancak oyunun bitme koşullarına uyulacaksa bu sonlandırma yapılmalıdır.
çevre nesnelerinin action_space isimli bir örnek özniteliği vardır. Bu öznitelik bize bir space nesnesi vermektedir. Bu space nesnesinin
n örnek özniteliği toplam eylemlerin sayısını vermektedir. Bu sınıfın sample isimli metodu bize rasgele bir eylem vermektedir.
Örneğin biz simülatöre rastegele eylemleri şöyle yaptırabiliriz:
while True:
action = env.action_space.sample()
obs, reward, done, _ = env.step(action)
if done:
break
env.render()
Simülatörün 0.26'lı versiyonlarında çıkış koşulu için truncated elemanına da bakılmalıdır:
while True:
action = env.action_space.sample()
obs, reward, done, truncated, _ = env.step(action)
if done or truncated:
break
env.render()
Çevre nesnelerinin observation_space isimli örnek öznitelikleri yine bir space nesnesi vermektedir. Buradan elde edilen space
nesnesinin en önemli iki örnek özniteliği low ve high öznitelikleridir. low özniteliği bize gözlem alanının en düşük değerlerini
vermektedir. high ise en tüksek değerlerini verir. Yine bu space nesnesinin sample metodu rastgele bir gözlem değerini bize verir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte CartPole simülatörüne rastgele hareketler yaptırılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import gym
env = gym.make('CartPole-v1')
obs = env.reset()
env.render()
while True:
action = env.action_space.sample()
obs, reward, done, _ = env.step(action)
if done:
break
env.render()
env.close()
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte MountainCar simülatörünün insan zekasıyla görevi başarması sağlanmıştır. Burada biza önce motoru sağa
çalıştırıp hızın tepeden dolayı dçok düştüğü noktada sola çalıştırıyoruz. Böylece hem potansiyel enerjiden hem de motor enerjisinden
faydalanıyoruz. Hız sıfır olmayabilir ancak sıfıra yaklaşabilir. Bu nedenle if içerisinde bir epsilon değeri kullanılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import gym
env = gym.make('MountainCar-v0', render_mode='human')
obs = env.reset()
env.render()
action = 2
counter = 0
while True:
obs, reward, done, _ = env.step(action)
if done:
break
env.render()
if abs(obs[1]) < 0.001:
action = 0 if action == 2 else 2
env.close()
#----------------------------------------------------------------------------------------------------------------------------
Taxi simülatörü yine make fonksiyonu ile aşağıdaki gibi yaratılır:
env = gym.make('Taxi-v3')
Daha sonra simülatör reset edilir ve oradan gözlem değerleri elde edilir. Taxi simülatöründe bir tane gözlem değeri vardır. O da
bir sayıdır. Bu sayı taksinin nerede olduğunu, yolcunun durumuna göre belirlemektedir. Örneğin:
obs = env.reset()
Burada simülatörün durumu tek bir değerler bize verşlmektedir. Bu değerin anlamı şöyledir:
arabanın satır numarası * 100 + arabanın sütun numarası * 20 + yolcunun konumu * 4 + yolcunun bırakılacağı yer
Burada arabanın satır ve sütun numarası sol-üst köşeden itibaren 0 orijinli bir biçimde belirtilmektedir. Yolcunun konumu ise
0, 1, 2, 3, 4 ile temsil edilmeketedir. Bu değerler sırasıyla R, G, Y, B ve taksinin içiyle eşleşmektedir. Yolcunun bırakılacağı durak da
yine 0, 1, 2, 3 ile temsil edilir ve bunlar da R, G, Y, B ile eşleştirilmiştir.
Simülatörde durakların yerleri hiç değişmemektedir. Duraklar şu pozisyonlarda bulunmaktadır:
R -> (0, 0)
G -> (0, 4)
Y -> (4, 0)
B -> (4, 3)
Bu değeri set eden ve reset eden çevre sınıfının encode ve decode isimli metotları bulunmaktadır. encode metodu bizden
sırasıyla taksinin bulunduğu yerin satır numarasını, sütun numarasaını, yolcunun bulunduğu yeri ve yolcunun bırakılcağı
durağı parametre olarak almaktadır. Bize bunun sayısal bir biçimde kodlanmış halini vermektedir. Örneğin:
env.encode(2, 2, 2, 1)
Out[55]: 249
Bu sayı şöyle elde edilmiştir:
2 * 100 + 2 * 20 + 2 * 4 + 1 = 249
Dokğmante edilmemiş olsa da kaynak kodlara bakıldığında simülatgörün belli bir duruma getirilmesi için env.env.s
örnek özniteliğinin kullanıldığı anlaşılmaktadır. Bu sayede biz reset metoduyla rastgele bir durumdan başlamak yerine
belirli bir durumdan başlayabiliriz. Örneğin taksinin 2'nci satır, 2'inci sütunda olmasını yolcunun sağ üst köşedeki G'de
olmasını ve yolcunun bırakılacağı yerin de sol üst köşedeki R olmasını isteyelim. Bunu şöyle sağlarız:
import gym
env = gym.make('Taxi-v3')
obs = env.reset()
val = env.encode(2, 2, 1, 0)
env.env.s = val
env.render()
Çevre sınıfının decode metodu ters işlemi yapmaktadır. Yani bizden bir sayı alır. Bu sayıyı dörlü bir demete dönüştürür.
Demetin ilk elemanı taksinin bulunduğu satır numarası, ikinci elemanı sütun numarası, üçüncü elemanı yolcunun konumunu ve
dördüncü elemanı da yolcunun bırakılacağı yeri belirtmektedir. Örneğin biz önce encode sonra decode işlemi yaparsak aynı değeri
elde ederiz:
import gym
env = gym.make('Taxi-v3')
obs = env.reset()
val = env.encode(2, 2, 1, 0)
row, col, passenger, drop = env.decode(val)
print(row, col, passenger, drop)
Burada aktardığımız bilgiler dokümanlarda bulunmamaktadır. Tamamen kaynak kodların incelenmesiyle elde edilmiştir. Bu nedenle
buradaki bilgiler simülatörür sonraki versiyonlarında değişebilmektedir. Öte yandan simülatörün tasarımında da eksikler vardır.
Programcının simülatörü rastgele bir konumdan değil belli bir konumdan başlatılmasi gerekebilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Taksi simülatöründe taksiyi hareketli bir biçimde göstermek için şöyle bir yöntem kullanılmaktadır: render metodunda mode parametresi
'ansi' girilirse render metodu ekrana bir şey basmaz ancak "ansi teminal karakterleri" denilen karakterlerden oluşan bir yazı
verir. Bu yazı print fonksiyonuyla bastırılırsa aynı görüntü karşımıza çıkmaktadır. Örneğin:
s = env.render(mode='ansi')
print(s)
Burada aslında yapılmak istenen şey aşağıdakiyle aynıdır:
env.render()
render metodu zaten default durumda ekrana batırmaktadır. Fakat ANSI terminal karakterleri print ile basılırken "bulunuln satırın
başına geçmek için uygun ANSI terminal karakteri kullanılırsa basım hep aynı yere yapılır. Böylece hareketli bir görüntü elde edilir.
Aşağıda bu biçimdeki harekete bir örnek verilmektedir:
#----------------------------------------------------------------------------------------------------------------------------
import time
import gym
env = gym.make('Taxi-v3')
obs = env.reset()
env.render()
for i in range(100):
action = env.action_space.sample()
obs, reward, done, _ = env.step(action)
s = env.render(mode='ansi')
print('\x1b[1J' + s, end='')
time.sleep(0.5)
#----------------------------------------------------------------------------------------------------------------------------
Frozenlake sümülatörü de benzer biçimde kullanılmaktadır. Burada gözlem değeri (durum bilgisi) tek değerden oluşmaktadır.
Bu değer kişinin bulunduğu yerin "satır numarası * 8 + sütun numarası" biçimindedir. Bu simülatörde etmenin yapacağı hareketler
4 tanedir:
0 --> Sola
1 --> Aşağıya
2 --> Sağa
3 --> Yukarı
Simülatörden amaç etmene S noktasından G noktasına deliğe düşmeden etkin biçimde gitmeyi öğretmektir. Simülatör burada etmene
hedefe vardığında 1 puan, varamadığında her eylem için 0 puan vermektedir. Simülatörün 4x4'lük bir versiyonu da vardır.
Aşağıdaki örnekte simülatörde etmene rastgele hareketler yaptırılmıştır. Etmen deliğe düştüğünde işlem sonlandırılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import time
import gym
env = gym.make('FrozenLake-v1', map_name="8x8")
obs = env.reset()
env.render()
for i in range(100):
action = env.action_space.sample()
obs, reward, done, _ = env.step(action)
s = env.render(mode='ansi')
print('\x1b[1J' + s, end='')
if done:
break
time.sleep(0.5)
#----------------------------------------------------------------------------------------------------------------------------
MsPacman oyununa ilişkin simülatörün kullanılabilmesi için bazı gym sürümlerinde aşağıdaki kurulumların yapılması gerekmektedir:
pip install gym[atari]
pip install gym[accept-rom-license]
MsPacman oyununda her bir eylem sonrasında çevreye ilişkin gözlem (durum) bilgisi o andaki oyunun resimsel görüntüsüdür.
Bu görüntü bize (210, 160, 3) biçiminde RGB bir resim olarak verilmektedir. Örneğin biz simülatörü reset edip elde ettiğimiz
gözlem değerine ilişkin resmi aşağıdaki gibi çizdirebiliriz:
import gym
env = gym.make("MsPacman-v4")
obs = env.reset()
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 10))
plt.imshow(obs)
Simülatörde etmenin yapacağı eylemler şunlardır:
0 --> NOOP
1 --> UP
2 --> RIGHT
3 --> LEFT
4 --> DOWN
5 --> UPRIGHT
6 --> UPLEFT
7 --> DOWNRIGHT
8 --> DOWNLEFT
Ancak oyun belli bir süre başlamamaktadır. Belli bir süreden sonra başlamaktadır.
Aşağıdaki örnekte simülatöre rastgele 1000 tane hareket yaptırılmıştır. Önce Pacman hareket etmemekte yaklaış 90 adımdan
sonra hareketler başlamaktadır. Oyunda toplam 3 can vardır. 3 can bitince done koşulu gerçeklemektedir.
#----------------------------------------------------------------------------------------------------------------------------
import gym
env = gym.make("MsPacman-v4", full_action_space=False)
obs = env.reset()
import matplotlib.pyplot as plt
for i in range(1000):
action = env.action_space.sample()
obs, reward, done, _ = env.step(action)
if done:
break
plt.figure(figsize=(10, 10))
plt.imshow(obs)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Tuğla kırmaca oyununda toplamda 4 eylem vardır:
0 --> NOOP
1 --> FIRE
2 --> RIGHT
3 --> LEFT
Oyunda başlangıçta FIRE düğmesine basılmazsa raket hareket eder ancak top düşmez böylece oyun da başlamaz.
Yine her eylemden sonra simülatör bize o andaki oyunun resimini (210, 160, 3) boyutlarında bir resim olarak vermektedir.
Oyunda toplam 5 hak vardır. Bu 5 hak kaybedildiğinde done koşulu sağlanır.
Oyunda tuğlalar kırıldıkça ödül verilmektedir. Ancak verilen ödül kırılan tuğlanın rengine de bağlıdır. Ödül mekznizması
için Atari'nin orijinal dokümanlarına başvurrulabilir:
https://atariage.com/manual_html_page.php?SoftwareID=889
Aşağıdaki örnekte raket rastgele hareket ettirilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import gym
env = gym.make("Breakout-v4")
obs = env.reset()
obs, reward, done, _ = env.step(1)
import matplotlib.pyplot as plt
for i in range(1000):
action = env.action_space.sample()
obs, reward, done, _ = env.step(action)
if done:
break
plt.figure(figsize=(10, 10))
plt.imshow(obs)
plt.show()
print('\x1b[1J')
#----------------------------------------------------------------------------------------------------------------------------
Pekiştirmeli öğrenmede kullanılan birkaç önemli yöntem vardır. Bunların en yalını ve yaygın kullanılanı Q-Learning denilen
yöntemdir. Buradaki "Q" harfi "Quality (kalite)" sözcüğünden hareketle uydurulmuştur.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Q-Learning yönteminde ismine Q-Tablosu denilen bir tablo kullanılır. Q-Tablosu temelde iki boyutlu bir matris gibi düşünülebilir.
Bu tablonun satırlarında olası bütün durumlar (yani gözlem değerleri) sütunlarında da olası tüm eylemler bulunmaktadır. Örneğin
etmenin içinde bulunduğu durum 5 farklı durumdan birisi olabilsin. Belli bir durumda da üç eylem söz konusu olsun. Bu durumda
Q-Tablosu şöyle olacaktır:
A1 A2 A3
S1
S2
S3
S4
Burada matris 5x3 = 15 tane eleman içerecektir. Başlangıçta Q-Tablosu 0'larla doludur:
A1 A2 A3
S1 0 0 0
S2 0 0 0
S3 0 0 0
S4 0 0 0
Ancak zamanla öğrenme gerçekleştiğinde tablo yavaş yavaş dolmaya başlar. Nihayetinde dolu bir tablo haline gelir. Örneğin:
A1 A2 A3
S1 0.2 0.5 0.3
S2 0.8 0.6 0.1
S3 0.2 0.7 0.2
S4 0.3 0.2 0.6
Q-Tablosunun dolmuş olduğunu varsayalım. Artık etmen belli bir durumdayken en iyi eylemi gerçekleştirebilir durumda olur.
Örneğin reset durumunda etmen S3 durumunda olsun. S3 satırı hangi eylemlerin hangi kalitede olduğunu bize vermektedir. Oradaki
en kaliteli yani puanı en yüksek olan eylem seçilir. Örneğimizde S3 durumundaki en iyi eylem A2 eylemidir. Etmen A2 eylemini yaptığında
başka bir durum oluşacaktır. Bu durumun S1 olduğunu düşünelim. Artık etmen S1 durumundadır. S1 durumundaki en iyi eylem yine A2 eylemidir.
Bu kezmen yine A2 eylemini yapar. Şimdi artık yeni bir durum içerisine girilmiştir. Bu yeni durumun S2 olduğunu varsayalım.
Bu durumda etmen en iyi eylem olan A1 eylemini gerçekleştirecektir. Özetle Q-Tablosu hangi durumdaki eylemin en iyi eylem olduğunu vermektedir.
Q-Learning yöntemini uygulayabilmek için bizim şu bilgilere gereksinimimiz vardır:
- Ortamdaki tüm durumların neler olduğu bilgisine
- Belli bir durumdaki eylemlerin neler olduğu bilgisine
- Q-Tablosunun nasıl güncelleneceğine ilişkin bilgi
Q-Tablosu tipik olarak bir matris biçimindedir. Baştan seyrek (sparse) görünümdedir. Bazı olgularda her durumda her eylem yapılamaz.
Bu durumda Q-Tablosunun bir matris biçiminde değil bir sözlük biçiminde oluşturulması daha etkin bir yöntem olabilir.
Belli bir ortamdaki tüm durumların ve eylemlerin tespit edilmesi ve bunun Q-Tablosunun satırları haline getirilmesi önemli bir aşamadır.
Belli bir durumsal özelliğin sürekli olması sonsuz sayıda satırın bulunmasına yol açar. Bunun Q-Tablosu yönteminde sonlu sayıda durum
haline getirilmesi gerekmektedir. Yani bizim sürekli durum özelliklerini ayrık hale getirmemiz gerekir. Örneğin CartPole simülatöründe
aracın X eksenindeki konumu, aracın hızı, direğin açısı ve direğin açısal hızı sürekli bilgilerdir. Bu sürekli bilgilerin ayrık hale
getirilip sonlu sayıda durumun oluşturulması gerekir. Bunu sağlamanın basit bir yolu şöyledir: Sürekli değerin iki uç sınırı arasındaki
uzaklık hespalanır. Sonra bu uzaklık belli bir sayıya bölünür. Böylece aslında sürekli bir durum ayrıkmış gibi ele alınır. Örneğin CartPole
similatöründe arabanın X eksenindeki konumu [-4.8, +4.8] aralığındadır. Buradaki toplam uzaklık 9.6'dır. Biz bu aralığı 100 eşit parçaya
bölersek her 0.096'lık konum ayrı bir durum gibi ele alınır. Tabii bu ayrık hale getirme işlemi bilgi kaybına da yol açmaktadır. Zira örneğin
CartPole simülatöründeki konum bilgisini 100 eşit parçaya böldüğümüzde biz her 0.096'lık aralığı aynı durum kabul ederiz. Yani adeta bize
göre araba sıçrayarak gitmektedir. Şüphesiz sürekli olguları bu biçimde ayrık hale getirdiğimizde bölme faktörünü yükseltirsek daha az bilgi
kaybederiz. Ancak bu sefer de Q-Tablosunu büyütmüş oluruz.
Şimdi daha önce görmüş olduğumuz gym simülatörlerinde bu durum bilgisinin ve eylemlerin neler olacağına bakalım.
- CartPole simülatöründe her eylem sonrasında simülatör bize çevreye (duruma) ilişkin 4 bilgi veriyordu: Aracın X erksenindeki konumu,
aracın hızı, direğin açısı ve direğin açısal hızı. Maalesef bu dört bilgi de ayrık değil süreklidir. Bu dört bilgi aslında Q-Tablosunun
satırlarını oluşturmaktadır. Ancak bu dört bilginin ayrık hale getirilmesi gerekir. Bu biçimde birden fazla sürekli durum bilgisinin
ayrık hale getirilmesinde hep aynı bölme faktörü kullanılmak zorunda değildir. Örneğin biz X eksenindeki konumu 100 parçaya bölerken
direğin açısal hızını 50 parçaya bölebiliriz. Ancak şimdi biz burada bu dört durum bilgisini de 100 parçaya böldüğümüzü düşünelim.
Bu durumda Q-Tablosunun satır sayısı ne olacaktır? Tabii toplam satır sayısı 100 * 100 * 100 * 100 olur. Bu değer de 10 ^ 8 = 100000000'dur.
Yani burada Q-Tablosunun 100 milyon satırı olacaktır. Pekiyi belli bir durumdaki eylem sayısı CartPole simülatöründe kaç tanedir?
Yanıt 2 tanedir. Motor ya sola çalıştırılır ya da sağa çalıştırılır. Bu durumda Q-Tablosu 100 milyon satırdan ve 2 sütundan oluşacaktır.
Böyle bir NumPy dizisinin float32 dtype özelliği ile yaratılacağını düşünürsek Q-Tablosu için toplam bellek miktarı 800 milyon byte'tır.
Yani 1GB civarındadır. Pekiyi CartPole simülatöründe elimizde 100 milyon elemanlı bir durum bilgisi varsa biz buradaki durumu Q-Tablosundaki satıra nasıl
dönüştürebiliriz? Burada en makul çözüm iki boyutlu matris kullanmak yerine beş boyutlu bir matris kullanmaktır. Öyle ki:
qtable[arabanın_x_konumu, arabanın_hızı, diğerin_açısı, direğin_açısal_hızı, 2]
Burada ilk dört bilgiyi verdiğimizde biz iki elemanlı bir NumPy dizisi elde ederiz:
qresult = qtable[arabanın_x_konumu, arabanın_hızı, diğerin_açısı, direğin_açısal_hızı]
Burada qresult iki elemanlı satır bilgisini belirtmektedir.
- MountainCar simülatöründe gözlem değerleri iki tanedir: Arabanın x eksenindeki konumu ve arabanın o andaki hızı. Bu iki değer de sürekli
değerlerdir. O halde Q-Tablosunu oluşturmak için bu iki değerin ayrık hale getirilmesi gerekir. Arabanın konumu [-1.2, 0.6] aralığında,
hızı ise [-0.07, 0.07] aralığındadır. Yine bu iki özelliği 100 eşit parçaya bölebiliriz. Bu durumda Q-Tablosunun satırları 100 * 100 = 10000
elemandan oluşacaktır. Pekiyi yapılacak eylemlerin sayısı da 3 tane idi: Motoru sola çalıştırmak, sağa çalıştırmak ve motoru durdurmak.
O halde Q-Tablosu örnekte (100, 100, 3)'lük bir NumPy dizisi olabilir:
qtable[arabanın_x_konumu, arabanın_hızı, 3]
Yine biz iki bilgiyi verdiğimizde üç elemanlı bir NumPy dizisi elde ederiz:
qresult = qtable[arabanın_x_konumu, arabanın_hızı]
- Taxi simülatöründe o andaki durum bilgisi "taksinin satır numarası, sütun numarası, yolcunun o anda nerede olduğu ve
yolcunun nerede bırakılacağı yer". Anımsanacağı gibi zaten bu dört bilgi Taxi simülatöründe tek bir değer biçiminde ifade
edilmekteydi. Buradaki durum bilgisi ayrıktır ve toplam 500 farklı seçenekten biridir. Etmenin yapacağı eylem sayısı da toplam 6 taneydi:
"Arabayı sola, sağa, yukarı, aşağıya götür; yolcu al ve yolcu bırak". O halde burada Q-Tablosu (500, 6)'lık bir NumPy dizisidir.
- 80x8'lik Frozen Lake simülatöründe kişi 8x8'lik matrisel alanda herhangi bir hücrede bulunabiliyordu. Bu matrisel hücre "kişinin
bulunduğu satır numarası * 8 + kişinin bulunduğu sütun numarası" biçiminde sayısal bir değere dönüştürülüyordu. Yani toplamda bu simülatörde
durum bilgisi 64 taneydi. Bu simülatörde etmenin yapacağı eylemler "sağa gitmek, sola gitmek, yukarı gitmek ya da aşağı gitmek" biçiminde
toplam 4 taneydi. O halde bu simülatördeki Q-Tablosu (64, 4)'lük bir NumPy dizisi olmalıdır.
- MsPacman oyununda ekrandaki resim 210X160 pixel çözünürlüğündedir. Her pixel RGB olarak 256x256x250'lık bir kombinasyondan olutuğuna
göre toplam olası pixel kombinasyonlarının sayısı 210 * 16* * 256 * 256 * 256 biçimindedir. Bu değer 563714457600 (beş yüz milyar civarı)
farklı kombinasyon anlamına gelir. Yani Q-Tablosu çok büyüktür. Etmenin olası eylemlerinin sayısı da 9 tanedir. Tabii bu kadar büyük Q-Tablosunun
oluşturulması ve doldurulması çok zordur. Onun yerine "Deep Q-Learning" algoritması tercih edilir.
- Breakout (tuğla kırmaca) oyunu da MsPacman oyununda olduğu gibi bize 210x160'lık bir resim vermektedir. Olası durumların sayısı
MsPacman'de olduğu gibidir. Burada toplam hareketlerin sayısı 4 tanedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şimdi Q-Tablosunun doldurulmasında kullanılan formül üzerinde duralım. Formül şyledir:
Q(St, At) = Q(St, At) + alpha * (Rt + gamma * max(St+1, a) - Q(St, At))
Buradaki terimlerin anlamlaı da şöyledir:
Q(St, At): Belli bir t durumunda (yani Q tablosunun belli bir satırında) A eylemi yapılması durumu. Bu Q-Tablosunda St satırı ve
A sütununun kesiştiği hücreyi belirtir. Tablonun bu değeri güncellenecektir. Yani etmen belli bir durumdayken belli bir eylemi
yaptığında Q tablosunun o duruma ilişkin satırın o eyleme ilişkin sütunu güncellenmektedir.
alpha: [0, 1] aralığında "öğrenme hızını (learning rate)" temsil eden bir değerdir. Bu değer bir hyper parametredir. Uygulamacı tarafından
belirlenmektedir.
gamma: [0, 1] aralığında "indirim faktörü denen bir değerdir. Bu değer bir hyper parametredir. Uygulamacı tarafından
belirlenmektedir.
Rt: Etmen St durumundayken At eylemini yaptığında elde edeceği ödüldür.
max(St+1, a): Etmen St durumundayken At eylemini yaptığında St+1 durumuna geçecektir. Bu Q-Tablosunda St+1'e durumuna ilişkin bir
satırı temsil etmektedir. İşte bu satırdaki en büyük değer max(St+1, a) ile temsil edilmektedir. Burada etmen "bu eylem yapılırsa bir sonraki
adımdaki en iyi eylem hangisi" diye bakmaktadır. Şüphesiz ilgili eylem yapıdlığındaki en iyi eylem de aslında benzer mantıkla oluşturulmuştur.
Formüldeki alpha ve gamma sabitlerini görmezden gelelim. Pekiyi bu formal bize ne anlatmaktadır? Formüldeki max(St+1, a) - Q(St, At)
çıkartma işlemi mevcut durum ile sonraki durum arasındaki iyileşme miktarını belirtmektedir. Etmen bir durumda bir eylem yaptığında
mevcut durumunu iyileştirmek ister. Yani örneğin iyi bir durumdan yüksek değerli ancak daha kötü bir duruma geçmek bir iyileştirme
anlamına gelmez. Aradaki fark iyileştirmeyi belirtmektedir.
Q-Tablosu baştan sıfırlarla doludur. Pekiyi tablo nasıl doldurulacaktır? Baştan her şey 0 olduğuna göre durumu sıfırlardan kurtaracak
şey ödül ya da cezadır.
Şimdi tablonun güncellenmesi üzerinde çalışma yapmak için aşağıdaki gibi bir oyun düşünelim:
Gxx
xxx
xxx
Burada amaç etmenin (0, 0) noktasındaki hedefe varmayı öğrenmesi olsun. Hedefe varan eyleme 1 puan ödül verelim. Varmayan eyleme de
0 puan verelim. Q Tablosu aşağıdaki gibi olacaktır (Left, Right, Up, Down):
L R U D
S0 0 0 0 0
S1 0 0 0 0
S2 0 0 0 0
S3 0 0 0 0
S4 0 0 0 0
S5 0 0 0 0
S6 0 0 0 0
S7 0 0 0 0
S8 0 0 0 0
Şimdi etmenin (0, 1) pozsiyonunda olduğunu düşünelim ve Sola hareket etmek istediğini varsayalım:
GAx
xxx
xxx
Etmenin durumu şu anda S1'dir. O halde tabloda güncellenecek hücre (S1, L ) hücresidir. Formülde alpha = 0.90, gamma = 1 alalım.
Formülde ilgili değerleri yerine koyalım:
Q(S1, L) = Q(S1, L) + 0.90 * (1 + 1 * 0 - 0)
Q(s1, L) = 0.90
Şimdi Q tablosunda bir hücreyi güncellemiş olduk:
L R U D
S0 0 0 0 0
S1 0.9 0 0 0
S2 0 0 0 0
S3 0 0 0 0
S4 0 0 0 0
S5 0 0 0 0
S6 0 0 0 0
S7 0 0 0 0
S8 0 0 0 0
Şimdi etmenin (1, 1) durumunda olduğunu ve yukarı gitmek istediğini düşünelim. Etmen S4 durumundadır ve Yukarıya (Up) gitmek
istemektedir.
Gxx
xAx
xxx
O halde Q tablosu şöyle güncellenecektir:
Q(S4, U) = Q(S4, U) + 0.90 * (0 + 1 * 0.90 - 0)
S(S4, U) = 0.81
Şimdi Q-Tablosu şu duruma gelecektir:
L R U D
S0 0 0 0 0
S1 0.9 0 0 0
S2 0 0 0 0
S3 0 0 0 0
S4 0 0 0.81 0
S5 0 0 0 0
S6 0 0 0 0
S7 0 0 0 0
S8 0 0 0 0
Şimdi etmenin (2, 1) hücresinde olduğunu ve yukarı gitmek istediğini düşünelim:
Gxx
xxx
xAx
Konum olarak (2, 1) S7'dir. O halde güncellenecek hücre Q(S7, U) hücresidir. Q formülünde değerleri yerine koyalım:
Q(S7, U) = Q(S7, U) + 0.90 * (0 + 1 * 0.81 - 0)
Q(S7, U) = 0.729
Q Tablosu şu hele gelecektir:
L R U D
S0 0 0 0 0
S1 0.9 0 0 0
S2 0 0 0 0
S3 0 0 0 0
S4 0 0 0.81 0
S5 0 0 0 0
S6 0 0 0 0
S7 0 0 0.729 0
S8 0 0 0 0
Burada görüldüğü gibi tablo yavaş eğitim sırasında doldurulmaktadır. Yani eğitim aslında Q-Tablosunun doldurulması anlamına gelmektedir.
Pekiyi şimdi etmenin S7'de olduğunu düşünelim ve etmenin hedefe varmak istediğini varsayalım. Bu durumda S7 satırındaki en yüksek değere
bakılır. Bu değer 0.729'dur. Burada etmen yukarı hareket eder. etmen yukarı hareket edince S4 durumu oluşur. S4 durumundaki en
iyi eylem yine yukarı gitmektir. Bu duurmda S1 durumuna geçilir. S1 durumundaki en iyi eylem sola gitmektir. Hedefe varılır.
Yani tablo dolduğunda etmen nerede olursa olsun hedefe varmak için en iyi eylemi artık öğrenmiştir. Pekiyi tablo nasıl doldurulacaktır?
İşte işin başında tablo sıfırlarla dolu olduğuna göre aşağıdaki Q formül ü ancak bir ödül ya da ceza durumunda 0 dışı bir değer verebilir:
Q(St, At) = Q(St, At) + alpha * (Rt + gamma * max(St+1, a) - Q(St, At))
O halde ödül ya da ceza olmazsa bu öğrenme gerçekleşemez.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Q_learning algoritmasında en önemli noktakardan biri ilk baştan itibaren eğitimin nasıl yapılacağının belirlenmesidir.
Biz bilmediğimiz bir bölgeyi nasıl öğreniriz? Önce rastgele sokaklara dalarız. Biraz oraları öğreniriz. Ondan sonra öğrenilmiş
yerleri de kullanarak biraz bildiğimiz yerlerden hareket ederek bilmediğimiz rastgele sokaklara gireriz. Gitgide bildiğimiz alanı
genişletiriz. Sürekli bildiğimiz yerlerde dolaşırsak etrafı iyi öğrenemeyiz. Sürekli rastgele yerlerde dolaşırsak sentezleme zorlaşır.
O halde biraz rastegele biraz da öğrenilmiş yerleri kullanma stratejisini uygularız. İşte aynı durum pekiştirmeli öğrenmede de
benzer biçimde uygulanmaktadır. Buna pekiştirmeli öğrenmede "keşif/işletme stratejisi (exploration/explotation strategy)"
denilmektedir.
Eğer bir bölgede hiçbir yeri bilmiyorsak önce rastgele yerlerde daha fazla dolaşırız. Sonra yavaş yavaş biraz bildiğimiz yerlerde
biraz da rastgele yerlerde dolaşmaya çalışırız. Burada bazı keşif/işletme stratejileri üzerinde duracağız.
Epsilon greedy denilen keşif/işletme stratejisinde bir epsilon değeri alınır. Sonra o epsilon değeri olasılığında keşif yapılır,
1 - epsilon olasılığında ise işletme yapılır. Bunu Python'da şöyle ifade edebiliriz:
if np.random.uniform(0, 1) < EPSILON:
# keşif
else:
# işletme
Burada EPSILON değerinin 0.20 olduğunu varsayalım. np.random.uniform fonksiyonu [0, 1] aralığında rastgele bir sayı üretmektedir.
Bu sayı 0.2'den küçükse rastgele bir hareket yapılacak, büyükse zaten öğrenilmiş en iyi hareket yapılacaktır. Başka bir deyişle
etmen hangi konumdaysa %20 olasılıkla rastegele bir eylem yapacaktır. Örneğin birt satranç programını eğitecek olalım. Belli bir
pozisyonda %20 olasılıkla etmen rastgele bir hamle oynacaktır. Ancak %80 olasılıkla daha önce öğrendiği en iyi hamleyi
oynayacaktır. Tabii eğitim sırasında çok defalar bu işlemlerin yapılması gerekir. Örneğin:
import numpy as np
EPOCHS = 100
EPSILON = 0.20
for i in range(EPOCHS):
if np.random.uniform() < EPSILON:
print('Exploration')
else:
print('Explotation')
Başlangıçta etmen bir şey bilmedieğine göre "işletme (explotation)" uygulamanın bir anlamı yoktur. O halde başlangıçta daha fazla
keşif yapıp daha az işletme yapabiliriz. Sonra zamanla keşif olasılığını düşürüp işletme olasılığını yükseltebiliriz. Yani işin
başında etmen bir şey bilmediğine göre daha fazla keşif yapmalıdır. İşte yüksek bir keşif oranının giderek düşürülmesine
İngilizce "epsilon decay" denilmektedir.
Epsilon decay işlemi için en basit yöntemlerden biri keşfetme olasılığını her yinelemede oransal olarak düşürmektir. Ancak
eğitim sırasında keşfin de çok azalması iyi değildir. Keşif için minimum bir oran tutulabilir. Örneğin:
import numpy as np
EPOCHS = 200
EPSILON = 0.20
EPSILON_MIN = 0.20
for i in range(EPOCHS):
epsilon = max(1 - i / EPOCHS, EPSILON_MIN)
if np.random.uniform() < epsilon:
print('keşif')
else:
print('işletme')
Burada keşif oranı başlangıçta çok yüksek tutulup yavaş yavaş düşürülmüştür. Ancak %20'den daha fazla düşürülmemiştir.
Epsilon düşürme işlemi döngü değişkeninin belli bir değerle çarparak da sağlanabilir. Örneğin:
import numpy as np
EPOCHS = 200
DECAY_RATE = 0.99
EPSILON_MIN = 0.20
epsilon = 1
for i in range(0, EPOCHS):
epsilon = epsilon * DECAY_RATE
epsilon = max(epsilon, EPSILON_MIN)
if np.random.uniform() < epsilon:
print('keşif')
else:
print('işletme')
Burada 1 değeri her defasında DECAY_RATE (0.99) ile çarpılarak düşürülmüştür. Ancak yine %20 gibi bir orana gelindiğinde artık
keşfin daha da düşürülmesi engellenmiştir.
Epsilon değirini düşürmek için üstel fonksiyonlar da yaygın kullanılmaktadır. Örneğin e ** (-M * i) gibi bir ifade
yüksek bir değerden gittikçe düşüm yapabilmektedir. Buradaki M sabit bir deeğerdir. Örneğin M değeri 0.01 gibi alınabilir:
import numpy as np
EPOCHS = 200
M = 0.01
EPSILON_MIN = 0.20
for i in range(0, EPOCHS):
epsilon = np.exp(-M * i)
epsilon = max(epsilon, EPSILON_MIN)
if np.random.uniform() < epsilon:
print('keşif')
else:
print('işletme')
Yine epsilon değerini azaltmak için ters karekök (inverse square root) yöntemi de kullanılabilmektedir. Bu yöntemde
1 / sqrt(1 + i * DECAY_RATE)
biçiminde azaltım uygulanır. Örneğin:
import numpy as np
EPOCHS = 300
DECAY_RATE = 0.08
EPSILON_MIN = 0.20
epsilon = 1
for i in range(EPOCHS):
epsilon = 1 / np.sqrt(1 + i * DECAY_RATE)
epsilon = max(epsilon, EPSILON_MIN)
print(epsilon)
-
#----------------------------------------------------------------------------------------------------------------------------
Şimdi artık Q-Learning algoritmasını uygulayabilmek için pek çok şeyi öğrendik. Gerçekleştirimi Python'da nasıl yapabiliriz?
Eğitimin bir fonksiyon tarafından yapılması, o fonksiyonun Q-Tablosunu oluşturup bize vermesi uygun olur. Böyle bir train
fonksiyonunun parametrik yapısı nasıl olmalıdır? Tabii Q-Learning algoritmasındaki hyper parametrelerin fonksiyona yansıtılması
gerekir. Fonksiyonun Gym çevre nesnesini (environment) alması da uygundur. Bunların dışında eğitimin kaç epoch süreceği,
bir epoch'ta eğer done koşulu sağlanmazsa maksimum kaç yineleme yapılacağı fonksiyona parametre olarak verilebilir. Örneğin:
def train(env, alpha = 0.90, gamma = 1, epochs = 20000, max_iter = 1000):
pass
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import gym
EPOCHS_DEF = 20000
MAX_ITER_DEF = 1000
ALPHA_DEF = 0.1
GAMMA_DEF = 0.6
EPSILON = 0.20
def train(env, alpha = ALPHA_DEF, gamma = GAMMA_DEF, epochs = EPOCHS_DEF, max_iter = MAX_ITER_DEF):
qtable = np.zeros((env.observation_space.n, env.action_space.n))
for _ in range(epochs):
obs = env.reset()
for i in range(max_iter):
if np.random.uniform() < EPSILON:
action = env.action_space.sample()
else:
action = np.argmax(qtable[obs])
next_obs, reward, done, _ = env.step(action)
qtable[obs, action] = qtable[obs, action] + alpha * (reward + gamma * np.max(qtable[next_obs]) - qtable[obs, action])
obs = next_obs
if done:
break
return qtable
import time
def execute(env, qtable):
obs = env.reset()
count = 0
while True:
print('\x1b[1J' + env.render(mode='ansi'), end='')
action = np.argmax(qtable[obs])
obs, reward, done, _ = env.step(action)
if done:
break
count += 1
time.sleep(0.5)
return count
env = gym.make('Taxi-v3')
qtable = train(env)
count = execute(env, qtable)
print(count)
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda epsilonunun çarpansal biçimde decay işlemiyle azaltılmasına ilişkin örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import gym
EPOCHS_DEF = 20000
MAX_ITER_DEF = 1000
ALPHA_DEF = 0.1
GAMMA_DEF = 0.6
DECAY_RATE = 0.99
EPSILON_MIN = 0.20
def train(env, alpha = ALPHA_DEF, gamma = GAMMA_DEF, epochs = EPOCHS_DEF, max_iter = MAX_ITER_DEF):
qtable = np.zeros((env.observation_space.n, env.action_space.n))
epsilon = 1
for _ in range(epochs):
obs = env.reset()
for i in range(max_iter):
epsilon = epsilon * DECAY_RATE
epsilon = max(epsilon, EPSILON_MIN)
if np.random.uniform() < epsilon:
action = env.action_space.sample()
else:
action = np.argmax(qtable[obs])
next_obs, reward, done, _ = env.step(action)
qtable[obs, action] = qtable[obs, action] + alpha * (reward + gamma * np.max(qtable[next_obs]) - qtable[obs, action])
obs = next_obs
if done:
break
return qtable
import time
def execute(env, qtable):
obs = env.reset()
count = 0
while True:
print('\x1b[1J' + env.render(mode='ansi'), end='')
action = np.argmax(qtable[obs])
obs, reward, done, _ = env.step(action)
if done:
break
count += 1
time.sleep(0.5)
return count
env = gym.make('Taxi-v3')
qtable = train(env, epochs=10000)
count = execute(env, qtable)
print(count)
#----------------------------------------------------------------------------------------------------------------------------
Frozen-Lake simülatöründe ödül mekanizmasında bir problem vardır. Anımsanacağı gibi bu simülatörde ödül olarak deliğe düşüldüğünde de
hedefe varılamadığında da 0 puan verilmekte hedefe varıldığında ise 1 puan verilmekteydi. Ödül mekanizmasının bu biçimde alınması
eğitimi zorlaştırmaktadır. Çünkü etmen deliğe düşmemeyi bu ödül mekanizmasıyla zor çok zor öğrenmektedir. Ödül mekanizmasının
kötü eylemlerde negatif biçimde azaltılması iyi eylemlerde pozitif biçimde yükseltilmesi önemlidir. Bu nedenle biz aşağıda
Frozen-Lake simülatöründe deliğe düşülmesi durumunda etmene yüksek bir ceza vereceğiz. Pekiyi deliğe düşmeyi nasl anlayacağız?
İşte eğer done olmuşsa ve ödül de sıfırsa deliğie düşülmüş demektir. O halde biz aşağıdaki gibi bir kod parçasıyla ödül-ceza
puanlamasını daha iyi bir boyuta getirebiliriz:
next_obs, reward, done, _ = env.step(action)
if reward == 0:
reward = -20 if done else -1
Artık biizm ödül mekanizmamız şöyşedir:
- Deliğe düşülmesi durumunda -20 puan ceza
- Deliğe düşülmeden hedefe varılmaması durumunda -1 puan ceza
- Hedefe varılması durumunda +1 puan ödül
Aşağıdaki programda Frozen-Lake simülatörü kayganlık özelliği kaldırılarak eğitilmiştir. Keşif/İşletme yöntemi
olarak en basit epsilon greedy yöntemi kullanılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import gym
EPOCHS_DEF = 20000
MAX_ITER_DEF = 1000
ALPHA_DEF = 0.6
GAMMA_DEF = 0.9
EPSILON = 0.2
def train(env, alpha = ALPHA_DEF, gamma = GAMMA_DEF, epochs = EPOCHS_DEF, max_iter = MAX_ITER_DEF):
qtable = np.zeros((env.observation_space.n, env.action_space.n))
for _ in range(epochs):
obs = env.reset()
for i in range(max_iter):
if np.random.uniform() < EPSILON:
action = env.action_space.sample()
else:
action = np.argmax(qtable[obs])
next_obs, reward, done, _ = env.step(action)
if reward == 0:
reward = -20 if done else -1
qtable[obs, action] = qtable[obs, action] + alpha * (reward + gamma * np.max(qtable[next_obs]) - qtable[obs, action])
obs = next_obs
if done:
break
return qtable
import time
def execute(env, qtable):
obs = env.reset()
print('\x1b[1J' + env.render(mode='ansi'), end='')
count = 0
while True:
action = np.argmax(qtable[obs])
obs, reward, done, _ = env.step(action)
print('\x1b[1J' + env.render(mode='ansi'), end='')
if done:
break
count += 1
time.sleep(0.5)
return count
env = gym.make('FrozenLake-v1', map_name='8x8', is_slippery=False)
qtable = train(env, epochs=10000)
count = execute(env, qtable)
print(count)
#----------------------------------------------------------------------------------------------------------------------------
Anımsanacağı gibi Frozen-Lake simülatöründe etmen eğer zemin kaygansa (is_slippery=True ise) gitmek istediği yönüm dik yönlerine
1/3 olasılıkla yanlışlıkla gidebiliyordu. O halde etmenin bu modda çok daha akıllı davranması gerekmektedir. Örneğin:
FFF
HXF
FFF
Etmen X durumunda olsun. Artık etmenin sola girmeti, yukarı ve aşağı gitmesi tehlikelidir. Çünkü deliğe düşme olsaılığı vardır.
O halde buarada en güvenli eylem sağa gitmektir.
Aşağıda simülatörün kayganlık özelliği aktive edilerek aynı örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import gym
EPOCHS_DEF = 50000
MAX_ITER_DEF = 1000
ALPHA_DEF = 0.6
GAMMA_DEF = 0.9
EPSILON = 0.2
def train(env, alpha = ALPHA_DEF, gamma = GAMMA_DEF, epochs = EPOCHS_DEF, max_iter = MAX_ITER_DEF):
qtable = np.zeros((env.observation_space.n, env.action_space.n))
for _ in range(epochs):
obs = env.reset()
for i in range(max_iter):
if np.random.uniform() < EPSILON:
action = env.action_space.sample()
else:
action = np.argmax(qtable[obs])
next_obs, reward, done, _ = env.step(action)
if reward == 0:
reward = -50 if done else -5
qtable[obs, action] = qtable[obs, action] + alpha * (reward + gamma * np.max(qtable[next_obs]) - qtable[obs, action])
obs = next_obs
if done:
break
return qtable
import time
def execute(env, qtable):
obs = env.reset()
print('\x1b[1J' + env.render(mode='ansi'), end='')
count = 0
while True:
action = np.argmax(qtable[obs])
obs, reward, done, _ = env.step(action)
print('\x1b[1J' + env.render(mode='ansi'), end='')
if done:
break
count += 1
time.sleep(0.5)
return count
env = gym.make('FrozenLake-v1', map_name='8x8', is_slippery=True)
qtable = train(env)
count = execute(env, qtable)
print(count)
#----------------------------------------------------------------------------------------------------------------------------
CartPole simülatöründe anımsanacağı gibi bir araba ve arabanın üzerinde bir direk vardı. Amaç bu diğeri dik tutabilmekti.
Simülatörde toplam iki eylem vardı: Rabanın motorunu sola çalıştırmak (0), ya da sağa çalıştırmak (1). Çevreye ilişkin
toplam dört gözlem değeri vardı: Arabanın konumu, arabanın hızı, diğreğin açısı ve direğin açısal hızı. Ancak bu simülatörde gözlem değerleri
sürekli değişkenlerdne oluşuyordu. Biz CartPole örneğini Q-Learning algoritmasıyla çözerken bu sürekli 4 gözlem bilgisini ayrık hale
getireceğiz. Tabii sürekli verileri ayrık hale getirirken ayrık durum sayısının iyi bir biçimde belirlenmesi gerekmektedir.
CartPole örneğinde şöyle bir problem de vardır: Gözlem değerlerinden arabanın minimum ve maksimum hızları, direğin minimum ve
maksimum açısal hızları [-sonsuz, +sonsuz] arasında değer almaktadır. Bu sonsuz değerleri ayrık hale getiremiyiz. Bizim bir biçimde
bu değerleri öncelikle -sonsuz, +sonsuz aralığından çıkartıp daha düşük bir aralığa hapsetmemiz gerekmektedir. ÖZet olarak bizim
CartPole örneğinde şu noktalara dikkat etmemiz gerekmektedir:
- Sürekli verilen kaç parçaya ayrılarak ayrık hale getirileceği
- Eğitimde uygulanacak tekrar (epoch) sayısı
- Uygun bir epsilon decay işlemi
- Modelin alpha ve gamma hyper parametreleri
Sürekli değerlerin ayrık hale hale getişrilmesinde oluşturulacak parça sayısı Q-Tablosunu büyütmektedir. Q-Tablosunun büyümesi ise
daha uzun bir eğitimin gerekeceği anlamına gelmektedir. Bu durumda uygulanacak epsilon decay işlemi de daha önem kazanmaktadır.
Bu noktaların sezgisel olarak belirlenmesi kolay değildir. Bu tür pekiştirmeli öğrenme modellerinde deneme yanılma yöntemlerinin
uygulanması kaçınılmazdır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import gym
EPOCHS_DEF = 150000
ALPHA_DEF = 0.5
GAMMA_DEF = 0.95
EPSILON = 0.2
DISCRETE_STATES = 40
def obs2states(env, obs):
state_intervals = (env.observation_space.high - env.observation_space.low) / DISCRETE_STATES
states = np.round((obs - env.observation_space.low) / state_intervals).astype(int)
return np.clip(states, 0, DISCRETE_STATES - 1)
return states
def train(env, alpha = ALPHA_DEF, gamma = GAMMA_DEF, epochs = EPOCHS_DEF, verbose=1):
env.observation_space.low[1] = -10
env.observation_space.low[3] = -10
env.observation_space.high[1] = 10
env.observation_space.high[3] = 10
nactions = env.observation_space.shape[0]
nobservations = env.action_space.n
qtable = np.zeros((DISCRETE_STATES, ) * nactions + (nobservations, ))
for i in range(epochs):
if verbose == 1 and i % 1000 == 0:
print(f'{i} ', end='')
obs = env.reset()
while True:
x, v, angle, vangle = obs2states(env, obs)
if np.random.uniform() < EPSILON:
action = env.action_space.sample()
else:
states = obs2states(env, obs)
x, v, angle, vangle = states
action = np.argmax(qtable[x, v, angle, vangle])
next_obs, reward, done, _ = env.step(action)
next_x, next_v, next_angle, next_vangle = obs2states(env, next_obs)
qtable[x, v, angle, vangle, action] = qtable[next_x, next_v, next_angle, next_vangle, action] + alpha * (reward + gamma * np.max(qtable[next_x, next_v, next_angle, next_vangle]) - qtable[next_x, next_v, next_angle, next_vangle, action])
obs = next_obs
if done:
break
return qtable
import time
def execute(env, qtable):
obs = env.reset()
env.render()
count = 0
while True:
x, v, angle, vangle = obs2states(env, obs)
action = np.argmax(qtable[x, v, angle, vangle])
obs, reward, done, _ = env.step(action)
env.render()
if done:
break
count += 1
time.sleep(0.1)
return count
env = gym.make('CartPole-v1')
qtable = train(env)
count = execute(env, qtable)
print(count)
env.close()
#----------------------------------------------------------------------------------------------------------------------------
Anımsanacağı gibi MountainCar simülatöründe bir araba bir tepeyi aşmaya çalışıyordu. Burada üç eylem söz konusuydu: Motorun çağa
çalıştırılması, motorun sola çalıştırılması ve motorun durdurulması. Bu similatörde iki gözlem değeri vardı: Arabanın konumu ve
arabanın hızı. Ödül mekanizması ceza puanı üzerine oturtulmuştu. Yani hedefe varamayan her eyleme -1 puan veriliyordu.
Bu örnekte de sürekli verilerin ayrık hale getirilmesi gerekmektedir. Aşağıda MountainCar örneği için bir çözüm verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import gym
EPOCHS_DEF = 20000
ALPHA_DEF = 0.60
GAMMA_DEF = 0.90
EPSILON = 0.2
DISCRETE_STATES = 80
def obs2states(env, obs):
state_intervals = (env.observation_space.high - env.observation_space.low) / DISCRETE_STATES
states = np.round((obs - env.observation_space.low) / state_intervals).astype(int)
return np.clip(states, 0, DISCRETE_STATES - 1)
return states
def train(env, alpha = ALPHA_DEF, gamma = GAMMA_DEF, epochs = EPOCHS_DEF, verbose=1):
nactions = env.observation_space.shape[0]
nobservations = env.action_space.n
qtable = np.zeros((DISCRETE_STATES, ) * nactions + (nobservations, ))
for i in range(epochs):
if verbose == 1 and i % 1000 == 0:
print(f'{i} ', end='')
obs = env.reset()
while True:
x, v = obs2states(env, obs)
if np.random.uniform() < EPSILON:
action = env.action_space.sample()
else:
states = obs2states(env, obs)
x, v = states
action = np.argmax(qtable[x, v])
next_obs, reward, done, _ = env.step(action)
next_x, next_v = obs2states(env, next_obs)
qtable[x, v, action] = qtable[next_x, next_v, action] + alpha * (reward + gamma * np.max(qtable[next_x, next_v]) - qtable[next_x, next_v, action])
obs = next_obs
if done:
break
return qtable
import time
def execute(env, qtable):
obs = env.reset()
env.render()
count = 0
while True:
x, v = obs2states(env, obs)
action = np.argmax(qtable[x, v])
obs, reward, done, _ = env.step(action)
env.render()
if done:
break
count += 1
time.sleep(0.1)
return count
env = gym.make('MountainCar-v0')
qtable = train(env)
count = execute(env, qtable)
print(count)
env.close()
#----------------------------------------------------------------------------------------------------------------------------
Q-Learning algoritmasının en önemli handikapları şunlardır:
- Q-Tablosunun çok büyük olduğu durumda tablonun doldurulması için gereken eğitim uzun bir zaman almaktadır.
- Sürekli gözlem değerlerinin bulunduğu durumda bunların ayrık hale getirilmesi zahmetlidir ve bu işlem çok büyük
bir Q-Tablosunun oluşmasına yol açmaktadır.
- Yönetmin bellek gereksinimi Q-Tablolarından dolayı fazladır.
- Gözlem değerlerinin çok fazla olması durumunda yöntemin performansı düşmektedir.
İşte Q-Learning algoritmasının bu dezavantajlarını ortadan kaldırmak için "deep Q-Learning" denilen yöntem kullanılmaktadır.
Deep Q-Learning yöntemi nispeten yeni bir yöntemdir. Yöntem hakkında temel çalışmalar yaklaşık 10 sene önce başlamıştır. Kısa
zaman içerisinde hızlı bir ilerleme sağlanmıştır. Deep Q-Learning yönteminin ana noktaları şunlardır:
- Q-Tablosu sütunlarında eylemlerin satırlarında durumların bulundupu bir tabloydu. Biz de belli bir durumdaki satırın en yüksek
Q değerine sahip sütunundaki eylemi tercih ediyorduk. İşte bir satırın en iyi Q değerinin bulunması için geniş bir Q-Tablosu oluşturmak yerine
bunu bir kestirim problemi haline getirip bu kestirim sinir ağına yaptırılmaktadır. Bu durumda oluşturulacak sinir ağının girdileri
gözlem bilgilerinden oluşacak çıktıları ise Q-Tablosundaki eylemlerin Q değerlerinden oluşacaktır. Başka bir deyişle bir sinir ağına
satır bilgisini verdiğimizde ağ bize sütun bilgisini verecektir.
- Durumlardan Q değerlerinin elde edilmesinin sinir ağına devredilmediyse biz sürekli olguları ayrık hale getirme zorunluluğundan
kurtulmuş oluruz. Çünkü biz sinir ağı ile lojistik olmayan bir regresyon problemini çözmüş olmaktayız.
- Durumlardan Q değerlerinin sinir ağı ile kesitirilmesi sayesinde yöntem daha ölçeklenebilir (scalable) hale gelmektedir.
- Yöntemin sinir ağının oluşturulması dışındaki diğer kısımları daha önce yaptığımız gibi yürütülmektedir.
- Yöntemde sinir ağı üzerinde çalışılan probleme göre çeşitli biçimlerde oluşturulabilmektedir. Örneğin CartPole örneği için
sinir ağı şöyle oluşturulabilir:
model = Sequential(name='Cartpole-DeepLearning')
model.add(Dense(64, activation='relu', input_dim=env.observation_space.shape[0], name='Dense-1'))
model.add(Dense(64, activation='relu', name='Dense-2'))
model.add(Dense(env.action_space.n, activation='linear', name='Output'))
model.compile(optimizer='adam', loss='mse', metrics=['mae'])
Burada ağda bir girdi katmanı iki saklı katman ve bir de çıktı katmanı bulunmaktadır. Girdi katmanındaki möron sayısının
env.observation_space.shape[0] kadar olduğuna dikkat ediniz. Yani girdi katmanındaki nöron sayısı durum bilgisini belirten
gözlem uzayı kadardır. (Yani örneğim CatrPole similatörü için 4, MountainCar simülatörü için 2). Modelin çıktı katanında
toplam env.action_space.n tane nöronun bulunduğuna dikkat ediniz. Yani model bize her eylem için Q değerlerini verecektir.
Tabii buradaki sinir ağı mimarisi problemin biçimine göre değişecektir. Örneğin bir Atari oyunununda görüntü işleme söz konusu olduğuna
göre ona göre evrişimli ve çok katmanlı bir ağ kullanılmalıdır.
- Yönetmin en önemli noktalarından biri sinir ağının nasıl eğitileceğidir. Biz bir durum bilgisinden hareketle Q formülünü
uygulayarak bir değer elde ediyorduk. Bu değeri de Q tablosunun ilgili satır ve sütununa kaydediyorduk. İşte eğitim yapılırken
ismine "replay buffer" denilen bir veri yapısı kullanılır. Etmene eylem yaptırılıp bunun sonuçları ve ödülleri bu replay buffer
içerisine kaydedilir. Bu replay buffer veri yapısının elemanları aşağıdaki gibi demetlerden oluşmaktadır:
(state, action, reward, next_state)
Burada o anda etmenin için bulunduğu durum demetin "state" elemanı ile temsil edilmiştir. Etmenin o durumda yaptığı eylem "action"
ile temsil edilmiştir. Demetin "reward" ile temsil edilen üçüncü elemanı etmenin bu eylemi yaptığındaki elde ettiği ödülü
belirtmektedir. Nihayet demetin son "next_state" elemanı eylemden sonra oluşacak yeni durumu belirtir. Bizim Q formülünü uygulayabilmemiz için
bu bilgilere gereksinimiz vardır. Buradaki relay buffer son n tane değeri tutabilecek bir veri yapısı biçiminde tasarlanmalıdır.
Bunun için Python standart kütüphanesindeki collections modülünde bulunan deque (double-ended-queue) sınıfından faydalanılabilir. Yönetmde
replay buffer oluşturulduktan sonra bunun içerisinden rastgele k tane eleman seçilir. Sonra bu elemanlar ağın eğitiminde kullanılır.
Deep Q-Learning yönteminin tipik gerçekleştirimi maddeler haline şöyledir:
1) Probleme uygun bir sinir ağı modeli oluşturulur.
2) Bir replay buffer oluşturulur. Bu buffer son n tane "durum, eylem, ödül ve sonraki durumdan" oluşan bir veri yapısıdır. Yani bu veri yapısı
n tane aşağıdaki gibi demetlerden oluşmaktadır:
(state, action, reward, next_state)
Bu replay buffer "son n tane elemanı tutacak biçimde" organize edilir. Bunun için yukarıda da belirttiğimiz gibi deque sınıfı
kullanılabilir.
3) Bir epoch döngüsü oluşturulur. Bu epoch döngüsü içerisinde etmene eylemler yaptırılır. Bu eylemlerden elde edilen bilgiler
yukarıda belirtildiği gibi replay buffer içerisinde saklanır.
4) Bu replay buffer'dan "BATCH_SIZE < n" kadar rastgele eleman seçilir. Bu elemanlar sinir ağını eğitmekte kullanılır.
Burada eğitim sırasında gereksinim duyulacak y değerleri (yani Q değerleri) Q formülünden elde edilir. Ancak Q formülü uygulanırken
sonraki durumun en yüksek Q değerine gereksinim duyulmaktadır. İşte bu değer de sinir ağından kestirim yoluyla elde edilebilmektedir.
Aşağıda Carpole simülatrörü için bir Deep Q Learning (DQN) örneği verilmiştir. Bu tür DQN uygulamalarında eğitim oldukça uzun zaman almaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import random
import time
import collections
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
DEF_EPOCHS = 1000
DEF_BATCH_SIZE = 32
DEF_BUFFER_SIZE = 200
EPSILON = 0.20
EPSILON_INIT = 0.95
EPSILON_MIN = 0.20
DECAY_RATE = 0.95
ALPHA = 0.01
GAMMA = 0.95
def train(env, epochs = DEF_EPOCHS, batch_size=DEF_BATCH_SIZE, buffer_size = DEF_BUFFER_SIZE, verbose=1):
model = Sequential(name='Cartpole-DeepLearning')
model.add(Dense(64, activation='relu', input_dim=env.observation_space.shape[0], name='Dense-1'))
model.add(Dense(64, activation='relu', name='Dense-2'))
model.add(Dense(env.action_space.n, activation='linear', name='Output'))
model.summary()
model.compile(optimizer='adam', loss='mse', metrics=['mae'])
deq = collections.deque(maxlen=buffer_size)
epsilon = EPSILON_INIT
for i in range(epochs):
if verbose == 1:
print(f'{i} ')
obs = env.reset()
while True:
if np.random.uniform() < epsilon:
action = env.action_space.sample()
else:
qvalues = model.predict(obs.reshape(1, -1))[0]
action = np.argmax(qvalues)
obs_next, reward, done, _ = env.step(action)
deq.append((obs, action, reward, obs_next))
if len(deq) > batch_size:
sample = random.sample(deq, batch_size)
batch_x = np.zeros((batch_size, env.observation_space.shape[0]))
batch_y = np.zeros((batch_size, env.action_space.n))
for index, (obs_s, action_s, reward_s, obs_next_s) in enumerate(sample):
qvals = model.predict(obs_next_s.reshape(1, -1))
next_max_qval = np.max(qvals[0])
print(next_max_qval )
target_qvals = ALPHA * (reward_s + GAMMA * next_max_qval)
qvals[0, action_s] = target_qvals
batch_x[index] = obs_s
batch_y[index] = qvals
model.fit(batch_x, batch_y, batch_size=batch_size, epochs=1, verbose=0)
obs = obs_next
if done:
break
epsilon *= DECAY_RATE
epsilon = np.max([epsilon, EPSILON_MIN])
return model
def execute(env, model):
obs = env.reset()
while True:
env.render()
action = np.argmax(model.predict(obs.reshape(1, -1))[0])
obs, reward, done, _ = env.step(action)
if done:
break
time.sleep(0.5)
env.close()
import gym
env = gym.make('CartPole-v1')
model = train(env)
execute(env, model)
#----------------------------------------------------------------------------------------------------------------------------
import random
import time
import collections
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
DEF_EPOCHS = 1000
DEF_BATCH_SIZE = 32
DEF_BUFFER_SIZE = 200
EPSILON = 0.20
EPSILON_INIT = 0.95
EPSILON_MIN = 0.20
DECAY_RATE = 0.95
ALPHA = 0.01
GAMMA = 0.95
def train(env, epochs = DEF_EPOCHS, batch_size=DEF_BATCH_SIZE, buffer_size = DEF_BUFFER_SIZE, verbose=1):
model = Sequential(name='Cartpole-DeepLearning')
model.add(Dense(64, activation='relu', input_dim=env.observation_space.shape[0], name='Dense-1'))
model.add(Dense(64, activation='relu', name='Dense-2'))
model.add(Dense(env.action_space.n, activation='linear', name='Output'))
model.summary()
model.compile(optimizer='adam', loss='mse', metrics=['mae'])
deq = collections.deque(maxlen=buffer_size)
epsilon = EPSILON_INIT
for i in range(epochs):
if verbose == 1:
print(f'{i} ')
obs = env.reset()
while True:
if np.random.uniform() < epsilon:
action = env.action_space.sample()
else:
qvalues = model.predict(obs.reshape(1, -1))[0]
action = np.argmax(qvalues)
obs_next, reward, done, _ = env.step(action)
deq.append((obs, action, reward, obs_next))
if len(deq) > batch_size:
sample = random.sample(deq, batch_size)
batch_x = np.zeros((batch_size, env.observation_space.shape[0]))
batch_y = np.zeros((batch_size, env.action_space.n))
for index, (obs_s, action_s, reward_s, obs_next_s) in enumerate(sample):
qvals = model.predict(obs_next_s.reshape(1, -1))
next_max_qval = np.max(qvals[0])
print(next_max_qval )
target_qvals = ALPHA * (reward_s + GAMMA * next_max_qval)
qvals[0, action_s] = target_qvals
batch_x[index] = obs_s
batch_y[index] = qvals
model.fit(batch_x, batch_y, batch_size=batch_size, epochs=1, verbose=0)
obs = obs_next
if done:
break
epsilon *= DECAY_RATE
epsilon = np.max([epsilon, EPSILON_MIN])
return model
def execute(env, model):
obs = env.reset()
while True:
env.render()
action = np.argmax(model.predict(obs.reshape(1, -1))[0])
obs, reward, done, _ = env.step(action)
if done:
break
time.sleep(0.5)
env.close()
import gym
env = gym.make('CartPole-v1')
model = train(env)
execute(env, model)
#----------------------------------------------------------------------------------------------------------------------------
Bizim yukarıda uyguladığımız "deep reinforcement learning" yöntemine "değer tabanlı (value based)" yöntemler denilmektedir.
Genellikle bu yöntemler DQN (Deep Q Learning) olarak bilinmektedir. Biz bu yöntemlerde en iyi q değerlerini tahmin edecek
bir sinir ağı oluşturmaya çalıştık. Halbuki son 10 yıldır bu yöntemin çeşitli problemlerini ortadan kaldırmak için "policy gradient"
denilen yeni yöntemler gelişirilmiştir. Bu policy gradient yöntemler çeşitli alt gruplara ayrılmaktadır. Biz kursumuzda bu
yöntemlerin algoritmik yapıları üzerinde durmayacağız. Ancak bazı automated pekiştirmeli öğrenme kütüphaneleri üzerinde
bu yöntemleri uygulayacağız.
DQN yöntemleriyle son 10 senedir geliştirilmiş olan policy gradient yöntemler arasındaki temel farlılıklar şınlardır:
- DQN yöntemi en iyi Q değerlerinin bulunmaısnı hedeflemektedir. Ancal Policy Gradient yöntemler doğrudan en iyi eylemin belirlenmesini
hedeflemektedir Yani DQN yönteminde Q değerlerinden hareketle en iyi eylem tespit edilmeye çalışılırken Policy Gradient
yöntemlerde doğrudan en iyi eylkemklerin tepsit edilmeye çalışmaktadır.
- DQN yöntemi ayrık eylemlerin söz konusu olduğu durumlarda kullanılırken Policy Gradient yöntemler süreki eylemlerin
söz konusu olduğu durumlarda tercih edilmektedir.
Son 10 yıldır geliştirilmiş olan Policy Gradient yöntemlerden önemli olanları şunlardır:
- Vanilla Policy Gradient (VPG)
- Trust Region Policy Optimization (TRPO)
- Proximal Policy Optimization (PPO)
- Actor-Critic Methods
- Asynchronous Actor-Critic (A2C)
- Asynchronous Advantage Actor-Critic (A3C)
- Natural Policy Gradient (NPG)
- Trust-PCL
Tabii bu yöntemlerin hepsi yapay sinir ağı içermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yüksek seviyeli pekiştirmeli öğrenme kütüphanelerinin en önemlilerinden biri OpenAI kurumunun (ChatGPT'yi geliştiren kurum)
oluşturduğu "Stable Baselines" isimli kütüpahnedir. OpenAI bu kütüphaneyi geliştirmiş olmakla birlikte artık yeni sürümlerini
geliştirmeyi bırakmıştır. Stable Baselines kütüphanesinin en yeni versiyonu 3 versiyonudur. Kütüphane 3'lü versiyonlara kadar
Tensorflow kütüphanesi kullanılarak geliştirilmiştir. 3 versiyonuyla birlikte taban kütüphane olarak PyTorch kütüphanesi kullanılmaya başlanmıştır.
Kütüphanenin 3 versiyonu aşağıdaki gibi kurulabilir:
pip install stable-baselines3
Kütüphaneyi kullanırken bazı parametreler bazı başka kütüphanelerin kurulumunu gerektirebilmektedir. Bu yardımcı kütüphanelerin
kurulması aşağıdakigibi yapılabilir:
pip install stable-baselines3[extra]
Kütüphanenin 3 versiyonun dokümantasyonuna aşağıdaki bağlantıdan erişebilirsiniz:
https://stable-baselines3.readthedocs.io/en/master/
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Stable Baselines kütüphanesi tüm problemlerin gym simülatörleri gibi ifade edildiği varsayımıyla gerçekleştirilmiştir.
Yani bizim Stable baselines kütüphanesini "gym" ile birlikte kullanmamız gerekmektedir. Tabii kendimize özgü pekiştirmeli öğrenme
problemlerini çözmek için bizim problememizi "gym" ortamı gibi oluşturmamız gerekmektedir. Başka bir deyişle uygulamacı kendi
problemi için "custom environment" oluşturması gerekmektedir.
Programcı bir gym ortamı oluşturduktan sonra kullancağı pekiştirmeli öğrenme algoritmasını belirlemesi gerekir. Bu algoritmaların
çoğu "Policy Gradient" denilen son 10 senedir geliştirilen algoritmalardır. Kullanılacak algoritma isimsel olarak da belitirlebilir,
sınıf nesnesi olarak da belirtilebilir. Örneğin aşağıdaki algoritmalara ilişkin sınıflar hazır biçimde bulunmaktadır:
A2C
DDPG
DQN
HER
PPO
SAC
TD3
Bu algoritmaların hepsi gene olmakla birlikte bazı algoritmalar bazı problemler için daha uygun olmaktadır. Programcı bir algoritma seçip
o algoritmaya ilişkin bir sınıf nesnesi yaratır. Buna model nesnesi diyebiliriz. Eğitim işlemi bu model nesnelerinin learn isimli metotlarıyla
yapılmaktadır. Eğitim bittikten sonra model sınıfının predict metodu ile durum bilgisi metoda verilerek o durumda uygulanacak eylem
metottan elde edilir. Model sınıflarının save metotları eğitimden oluşan bilgiyi diske save etmek için load isimli metotları da bunları
yüklemek için kullanılmaktadır. Özetle kütüphanenin kullanılması için sırasıyla şu işlemler yapılmalıdır:
1) Öncelikle gym similatör nesnesi (gym environmet) oluşturulur.
2) Algoritma belirlenir ve algoritması nesnesi yaratılır. Algoritma nesnelerine model nesmneleri de diyebiliriz.
3) Eğitim model nesnelerinin learn metoduyle yapılmaktadır.
4) Artık belli bir durumda hangi eylemin yapılacağını belirlemek için model sınıflarının predict metotları kullanılır.
5) Eğitim bilgilerinin save edilmesi için model sınıflarının save metodu geri yüklemek için ise load metotları kullanılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şimdi CartPole similatörünü Stable Baselines kütüphanesi ile çözmeye çalışalım. İlk aşamada bizim gym similatör nesnesini
yaratmamız gerekir. Örneğin:
import gym
env = gym.make('CartPole-v0')
Bundan sonra kullanılacak algoritma seçilir. Burada DQN algoirmasını seçecek olalım. DQN yukarıda analltığımız "value based"
Deep Q Learning algoritmasını uygulamaktadır. DQN sınıfının __init__ metodunun pek çok parametresi vardır:
class stable_baselines3.dqn.DQN(policy, env, learning_rate=0.0001, buffer_size=1000000, learning_starts=50000,
batch_size=32, tau=1.0, gamma=0.99, train_freq=4, gradient_steps=1, replay_buffer_class=None,
replay_buffer_kwargs=None, optimize_memory_usage=False, target_update_interval=10000,
exploration_fraction=0.1, exploration_initial_eps=1.0, exploration_final_eps=0.05,
max_grad_norm=10, stats_window_size=100, tensorboard_log=None,
policy_kwargs=None, verbose=0, seed=None, device='auto', _init_setup_model=True)
Burada metodun policy parametresi kullanılacak sinir ağı mimarisini belirtmektedir. Bu mimari basit regresyon modelleri için "MlpPolicy"
olarak resimsel işlemler için (örneğin Atari oyunlarında olduğu gibi) "CnnPolicy", Çok girişli işlemleri için MultiInputPolicy biçiminde
olabilmektedir. Metodun ikinci parametresi gym simülatörünü almaktadır. Diğer parametreler "Deep Reinforcemt Q Learning" algoritmasında kullanılan
parametrelerdir. Tabii bu parametreler default değerlerle geçilebilir. Örneğin:
from stable_baselines3.dqn import DQN
model = DQN('MlpPolicy', env)
Artık eğitime aşamasına geçebiliriz. Yuklarıda da belirtidliği gibi eğitim model sınıflarının learn metotlarıyla yapılmaktadır. DQN sınıfının
learn metodunun parametrik yapısı şöyledir:
learn(total_timesteps, callback=None, log_interval=4, tb_log_name='DQN', reset_num_timesteps=True, progress_bar=False
Metodun birinci parametresi similatörün eğitim sırasında kaç kere step yapılacağını belirtmektedir. Bu parametre ne kadar
büyük geçilirse o kadar iyi bir sonuç elde edilir ancak eğitim zamanı da uzamaktadır. Örneğin:
model.learn(1000000)
Artık eğitim tamamlanmıştır. Biz kestirimlerde bulunup similatöre işlemler yaptırabiliriz. predict metodunun parametrik yapısı şöyledir:
predict(observation, state=None, episode_start=None, deterministic=False
Metot bize uygulanacak eylemi ve oluşacak yeni durumu vermektedir. Biz genellikle yalnızca eylem ile ilgileniriz.
Aşağıda CrtPole simülatörünün DQN ile çözümüne ilişkin bir örnek verilmiştir. Genel olarak DQN yöntemi bu tür oproblemlerde
Policy Gradient yöntemlerine göre daha düşük başarı sağlamaktadır. DQN yönteminde eğitim adımlarının yüksek tutulması ile
iyileşme sağlanmaktadır. Aşağıdaki örnekte CartPole için 500000 adım düşük bir eğitim sağlamaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import gym
env = gym.make('CartPole-v1')
from stable_baselines3.dqn import DQN
model = DQN('MlpPolicy', env)
model.learn(500000)
obs = env.reset()
import time
count = 0
while True:
action, _ = model.predict(obs)
obs, reward, done, _ = env.step(action)
env.render()
time.sleep(0.5)
count += 1
if done:
break
env.close()
print(f'Total count: {count}')
#----------------------------------------------------------------------------------------------------------------------------
Stable Baselines kütüphanesindeki A2C (Asycnchronous Actor Critic) algoritması pek çok durumda DQN'den daha iyi sonuç verme eğiliminbdedir.
Örneğin CartPole simülatöründe A2C açık bir biçimde DQN algoritmasında iyi bir sonuç vermektedir. A2C sınıfının __init__ metodunun
parametrik yapısı şöyledir:
classstable_baselines3.a2c.A2C(policy, env, learning_rate=0.0007, n_steps=5, gamma=0.99, gae_lambda=1.0, ent_coef=0.0,
vf_coef=0.5, max_grad_norm=0.5, rms_prop_eps=1e-05, use_rms_prop=True, use_sde=False, sde_sample_freq=-1,
normalize_advantage=False, stats_window_size=100, tensorboard_log=None, policy_kwargs=None, verbose=0,
seed=None, device='auto', _init_setup_model=True)
Yine metodun ilk parametresi sırasıyla uygulanacak yapay sinir ağının mimarisini ve gym simülatör nesnesini almaktadır. Yine bu parametre
normal uygulamakar için "MlpPolicy" olarak resimsel işlemler için (örneğin Atari oyunlarında olduğu gibi) "CnnPolicy", çok girişli işlemleri
için MultiInputPolicy biçiminde olabilmektedir. Diğer parametreler A2C algoritmasında hyper parametrelerdir.
Aşağıdaki örnekte CartPole simülatörü 250000 adım için A2C algoritmasıyla eğitilmiştir. A2C algoritması 250000 adım için
tam başarıyı elde etmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import gym
env = gym.make('CartPole-v1')
from stable_baselines3.a2c import A2C
model = A2C('MlpPolicy', env)
model.learn(250000)
obs = env.reset()
count = 0
while True:
action, _ = model.predict(obs)
obs, reward, done, _ = env.step(action)
env.render()
count += 1
if done:
break
env.close()
print(f'Total count: {count}')
#----------------------------------------------------------------------------------------------------------------------------
PPO (Proximal Policy Optimization) isimli algoritma en yeni algoritmalardan biridir. Bu algoritma da Policy Gradient
denilen sınıftandır. Sınıfının __init__ metodunun parametrik yapısı şöyledir:
classstable_baselines3.ppo.PPO(policy, env, learning_rate=0.0003, n_steps=2048, batch_size=64, n_epochs=10, gamma=0.99,
gae_lambda=0.95, clip_range=0.2, clip_range_vf=None, normalize_advantage=True, ent_coef=0.0, vf_coef=0.5,
max_grad_norm=0.5, use_sde=False, sde_sample_freq=-1, target_kl=None, stats_window_size=100, tensorboard_log=None,
policy_kwargs=None, verbose=0, seed=None, device='auto', _init_setup_model=True)
Yine sınıfın ilk paranetresi aynı anlamdadır. Diğer parametreler algoritmanın hyper parametreleridir.
PPO yöntemi pek çok uygulamada A2C ve ve DQN yöntemlerinden daha iyi sonuç vermektedir. Bu durum CartPole simülatöründe de
ıkça göerülmektedir.
Aşağıda CarPole simülatörüne PPO algoritması uygulanmıştır. 50000 adım bile problemi çözmeye yetmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import gym
env = gym.make('CartPole-v1')
from stable_baselines3.ppo import PPO
model = PPO('MlpPolicy', env)
model.learn(50000)
obs = env.reset()
count = 0
while True:
action, _ = model.predict(obs)
obs, reward, done, _ = env.step(action)
env.render()
count += 1
if done:
break
env.close()
print(f'Total count: {count}')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda MounCar simülatörü PPO algoritmasıyla çözlümeye çalışılmıştır. Ancak PPO algoritmasındaki default epoch 10'dur.
Bu epoch miktarı MountainCar için yetersiz kalmaktadır. Aynı zamanda bu simülatörde learning_rate de büyütülürse hedefe
daha hızlı varılabilmektedir.
Bu tür problemlerde default parametrelerle arzu edilen sonuç elde edilemiyorsa hyper parametrelerle oynak gerekir.
En önemli hyper parametreler şüphesiz n_epochs, learning_rate ve learn metodundaki adım sayısıdır.
#----------------------------------------------------------------------------------------------------------------------------
import gym
env = gym.make('MountainCar-v0')
from stable_baselines3.ppo import PPO
model = PPO('MlpPolicy', env, learning_rate=0.01, n_epochs=50)
model.learn(50000)
obs = env.reset()
count = 0
while True:
action, _ = model.predict(obs)
obs, reward, done, _ = env.step(action)
env.render()
count += 1
if done:
break
env.close()
print(f'Total count: {count}')
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de ayrık durumlardan ve eylemlerden oluşan Taxi simülatörünü Stable Baselines ile çözöeye çalışalım. Ayrık olaylar için
en iyi algoritmalardan biri DQN'dir. Ancak burada da hyper parametrelerin probleme özgü biçimde iyi ayarlnamaıs gerekebilir.
Aşağıda Taxi simülatörü DQN ile çözülmüştür.
#----------------------------------------------------------------------------------------------------------------------------
import gym
from stable_baselines3 import DQN
env = gym.make('Taxi-v3')
model = DQN('MlpPolicy', env, exploration_fraction=0.01, learning_rate=0.001, buffer_size=50000)
model.learn(total_timesteps=1000000)
import time
obs = env.reset()
while True:
action, _ = model.predict(obs)
obs, reward, done, _ = env.step(int(action))
if done:
break
print( '\x1b[1J' + env.render(mode='ansi'))
time.sleep(0.2)
env.close()
model.save('taxi.model')
del model
model = DQN.load('taxi.model')
obs = env.reset()
while True:
action, _ = model.predict(obs)
obs, reward, done, _ = env.step(int(action))
if done:
break
print( '\x1b[1J' + env.render(mode='ansi'))
time.sleep(0.2)
env.close()
#----------------------------------------------------------------------------------------------------------------------------
Aslında Stable Baselines'ın algoritmaları aynı anda bireden fazla simülatör ile paralel bir biçimde eğitim yapabilmektedir.
Yani ilgili Algoritma sınıflarının env parametreleri aslında bir grup simülatör nesnesinden de oluşabilmektedir. Bu amaçla bir grup similatör nesnesi
oluşturmak için make_vec_env isimli bir fonksiyon kullanılmaktadır. Fonksiyonun parametrik yapısı şöyledir:
stable_baselines3.common.env_util.make_vec_env(env_id, n_envs=1, seed=None, start_index=0, monitor_dir=None,
wrapper_class=None, env_kwargs=None, vec_env_cls=None,
vec_env_kwargs=None, monitor_kwargs=None, wrapper_kwargs=None)
Fonksiyonun birinci parametresi simülatör nesnesini ikinci parametresi ise oluşturulacak simülatör nesne sayılarınıo belirtmektedir.
Bu fonksiyonun geri döndürdüğü nesne doğrudan algorima sınıflarında kullanılabilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda da belirttiğimiz gibi Stable Baselines kütüphanesi "gym" simülatör (environment) sistemi üzerine oturtulmuştur.
Uygulamacının kendilerine ilişkin uygulamalarını yapabilmeleri için karşılaştıkları problemleri gym simülatörü gibi
ifade edebilmeleri gerekmektedir. Buna "gym için custom simülatör (environment)" yazımı denilmektedir.. Custom simülatör (environment)
yazımı şu adımlardan geçilerek yapılmaktadır:
1) gym.Env sınıfından bir sınıf türetilir. Örneğin:
from gym import Env
class MyEnv(Env):
pass
2) Programcının bu türemiş sınıf için bazı elemanları yazması gerekmektedir. Programcının minimal olarak oluşturması gereken
elemanlar şunlardır:
- observation_space isimli örnek özniteliği
- action_space isimli örnek özniteliği
- reset metodu
- step metodu
- render metodu (zorunlu değil)
- close metodu (zorunlu değildir)
observation_space örnek özniteliği simülatörümüzün içinde bulunduğu durumun maksimum ve minimum değerlerini vermektedir.
Eğer simülatörün durum bilgileri sürekli ise (MounCar, CartPole simülatörlerinde olduğu gibi) bu observation_space
örnek özniteliği Box denilen bir sınıf türünden olmalıdır. Eğer simülatörümüzün durum bilgileri ayrık ise (Taxi, FrozenLake simülatörlerinde olduğu
gibi= bu durumda bu örnek özniteliği Discrete isimli bir sınıf türünden olmak zorundadır.
Box nesnesi oluşturulurken low ve high parametrelerine durum bilgisinin en düşük ve en yüksek değerleri girilir. Eğer durum bilgisi
birden fazla bilgiden oluşuyorsa ve bu bilgilerinin hepsinin en düşük ve en yüksek değerleri aynı ise bu durumda Box nesnesi
shape belirtilerek kolay bir biçimde oluşturulabilmektedir. Örneğin:
Box(low=-1.0, high=2.0, shape=(3, 4), dtype=np.float32)
Burada toplam 3x4'lük 12 tane durumsal bilginin olduğunu bunların hespnin en düşük değerlerinin -1.0, en yüksek değerlerinin 2.0
olduğunu anlamaktayız. Bu örnekte tüm durumsal bilgilerin low ve high değerleri aynıdır. Ancak bunlar farklı ise bizim
bu low ve high değerlerini bir liste biçiminde tel tek belirtmemiz gerekir. Tabii bu durumda artık shape parametresinin bir anlamı
kalmamaktadır. Örneğin:
Box(low=np.array([-1.0, -2.0]), high=np.array([2.0, 4.0]), dtype=np.float32)
Burada iki tane durum bilgisinin olduğunu bunların en küçük değerlerinin [-1.0, -20.0] ve en yüksek değerlerinin [2.0, 4.0]
olduğunu anlamaktayız.
Eğer durum bilgileri ayrık bilgilerse bu ayrık bilgilere Discrete isimli bir sınıf ile temsil edilmelidir. Discrete sınıfı
yalnızca ayrık durum bilgisini içermektedir. Ayrık durumlar için en düşük ve en yüksek değer kavramının bir anlamı yoktur.
Örneğin:
Discrete(500)
Burada simülatörde ayrık toplam 500 tane durumun olduğu belirtilmektedir. Bu durumlar [0, 499] arası sayısal değerlere sahiptir.
Programcı action_space örnek özniteliğine simülatörün yapabileceği eylemlerin sayısını ve sınırlarını kodlamalıdır. Biz şimdiye kadar
hep ayrın eylemler üzerinde çalışmış olsak da aslında eylemler sürekli de olabilmektedir. Örneğin CartPole simülaötüründe iki eylem
vardır: Motorun sola çalıştırılması, motorun sağa çalıştırılması. Burada eylem uzayı ayrıktır. İşte yine bizim eylemler sürekli ise
onların limitlerini Box nesnesi olarak, ayrık ise Discrete nesnesi olarak yukarıda açıklandığı gibi oluşturmamız gerekir.
Programcı oluşturduğu sınıfte reset metodunu yazmalıdır. Bu reset metodu gözlem nesnesine geri dönmelidir. Programcı belli bir eylemi alıp
bundan dörtlü demet veren step metodunu yazmalıdır. Anımsanacağı gibi step metodunun geri döndürdüğü dörtlü demetin elemanları şunlardır:
obs, reward, done, info = env.step(action)
Programcı eğer görsel bir sunum yapma istiyorsa oluşturduğu sınıfta render isimli metodu da yazmalıdır.
Nihayet close metodunda programcı oluşturduğu sınıf için son birtakım işlemleri yapabilir. Programcı bu metodu yazmazsa taban Env sınıfının
close metodu çağrılacaktır.
Aşağıdaki örnekte basit bir "custom environment" yazılmıştır. Burada 3x3'lük matrisel bir alanda bir etmen hedef (G) noktasına
varmaya çalışmaktadır. Ancak bu matrisel alan içerisinde delikler (X) vardır. Bu custom environment sınıfındaki ödül mekanizması
şöyle ayarlanmıştır:
- Deliğe düşme -10 puan
- Deliğe düşmeden hedefe vararamayan eylem -1 puan
- Hedefe varan eylem 10 puan
Sınıf içerisinde yukarıda belirtilen elemanlar yazılmıştır. render metodu aşağıdaki gibi bir görüntü oluşturmaktadır:
X X G
A . .
. . X
#----------------------------------------------------------------------------------------------------------------------------
"""
Actions:
LEFT = 0
UP = 1
RIGHT = 2
DOWN = 3
"""
import numpy as np
from gym import Env
from gym.spaces import Discrete
GOAL_POS = 2
class GoalPositionError(Exception):
def __str__(self):
return "Agent already at goal position"
class HolePositionError(Exception):
def __str__(self):
return "Agent already at hole position"
class MyEnv(Env):
def __init__(self):
super().__init__()
self.observation_space = Discrete(9)
self.action_space = Discrete(4)
self._holes = [0, 1, 8]
self._pos_dict = {3: (5, 0, 4, 6), 4: (3, 1, 5, 7), 5: (4, 2, 3, 8), 6: (8, 3, 7, 0), 7: (6, 4, 8, 1) }
def reset(self):
none_holes = list(set(range(self.observation_space.n)).difference(self._holes + [GOAL_POS]))
self._agent_pos = np.random.choice(none_holes)
return self._agent_pos
def step(self, action):
if self._agent_pos == GOAL_POS:
raise GoalPositionError()
if self._agent_pos in self._holes:
raise HolePositionError()
newpos = self._pos_dict.get(self._agent_pos)[action]
self._agent_pos = newpos
if newpos in self._holes:
reward = -10
done = True
elif newpos == GOAL_POS:
reward = 10
done = True
else:
reward = -1
done = False
return newpos, reward, done, {}
def render(self, text=False):
s = ''
for row in range(3):
for col in range(3):
pos = row * 3 + col
if pos in self._holes:
if self._agent_pos == pos:
s += 'Q '
else:
s += 'X '
elif pos == GOAL_POS:
if self._agent_pos == pos:
s += 'Q '
else:
s += 'G '
elif pos == self._agent_pos:
s += 'A '
else:
s += '. '
s += '\n'
if text:
return s
else:
print(s)
from stable_baselines3.ppo import PPO
env = MyEnv()
model = PPO('MlpPolicy', env, learning_rate=0.1, n_epochs=100)
model.learn(30000)
import time
obs = env.reset()
print('\x1b[1J' + env.render(text=True), end='')
time.sleep(1)
while True:
action, _ = model.predict(obs)
obs, reward, done, _ = env.step(action)
print('\x1b[1J' + env.render(text=True), end='')
time.sleep(1)
if done:
break
#----------------------------------------------------------------------------------------------------------------------------
Yukarıdaki "custom envirionment" daha genel bir biçimde de oluşturulabilir. Örneğin matrisin genişliğini deliklerin yerlerini
ve başka birtakım değişebilecek öğeleri parametrik hale getirebiliriz. Aşağıdaki örnekte MyEnv isimli simülatör bu biçimde
parametrik hale getirilmiştir. MyEnv sınıfının __init__ metodunun parametrik yapısı şöyledir:
def __init__(self, rowsize = 3, colsize = 3, goal_pos = 2, holes = [0, 1, 8], hole_reward=-10, step_reward=-1, goal_reward=10
Sınıf default durumda 3x3'lük bir matris kullanmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Diğer bir pekiştirmeli öğrenme kütüphanesi "keras-rl" isimli kütüphanedir. Kütüphane aşağıdaki gibi install edilebilir:
pip install keras-rl
Kütüphanesinin dokümantasyonu biraz zayıftır. Kütüphanenin belli bir düzeye gelmesi için de biraz zaman gerekmektedir.
Projenin web sayfasına aşağıdaki bağlantıdan erişilebilir:
https://github.com/keras-rl/keras-rl
Dokğmantasyon için aşağıdaki bağlantıdan da faydalanılabilir:
https://keras-rl.readthedocs.io/en/latest/
Keras-rl kütüphanesinde de hazır birtakım etmen sınıfları bulunmaktadır. Tipik çalışma döngüsü şöyle gerçekleştirilir:
1) Programcı bir sinir ağını kendisi Keras kullarak oluşturur. Örneğin:
import gym
env = gym.make('CartPole-v1')
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.optimizer import Adam
model = Sequential(name='Cartpole-DeepLearning')
model.add(Flatten(input_shape=(1,) + env.observation_space.shape), name='Flatten')
model.add(Dense(64, activation='relu', input_dim=env.observation_space.shape[0], name='Dense-1'))
model.add(Dense(64, activation='relu', name='Dense-2'))
model.add(Dense(env.action_space.n, activation='linear', name='Output'))
model.summary()
2) Bir policy nesnesi yaratılır. Örneğin:
from rl.policy import EpsGreedyQPolicy
policy = EpsGreedyQPolicy(eps=0.2)
Çeşitli policy sınıfları hazır bir biçimde bulunmaktadır. Bu Policy nesneleri isimsel olarak da girilebilmektedir.
3) Daha sonra bir memory nesnesi yaratılır. Çeşitli memory sınıfları oluşturulmuştur. Örneğin:
from rl.memory import SequentialMemory
memory = SequentialMemory(limit=50000, window_length=1)
4) Bundan sonra bir Agent nesnesi oluşturulur. Çeşitli algoritmalar için çeşitli agent sınıfları bulunmaktadır. Örneğin:
from rl.agents.dqn import DQNAgent
agent = DQNAgent(model, nb_actions=env.action_space.n, memory=memory, policy=policy, nb_steps_warmup=10, target_model_update=1e-2)
Görüldüğü gibi agent sınıfı türünden nesne yaratılırken daha önce oluşturmuş olduğumuz "model nesnesi, "memory" nesnesi ve
"policy" nesnesi sınıfın __init__ metodunda parametre olarak verilmektedir.
5) Daha sonra agent nesnesi ile compile ve fit işlemleri yapılır. Örneğin:
agent.compile(Adam(lr=1e-3, metrics=['mae']))
agent.fit(env, nb_steps=10000)
Burada fit işleminde environment nesnesinin de metoda verildiğine dikkat ediniz. Tıpkı Stable Baselines kütüphanesinde
olduğu gibi bu kütüphane için de "custom environment" oluşturulabilmektedir.
6) Artık model eğitilmiştir. Etmen hareket ettirilebilir. Örneğin:
obs = env.reset()
while True:
action, _ = model.predict(obs)
obs, reward, done, _ = env.step(action)
if done:
break
env.render()
env.close()
Keras-rl kürüphanesi Stable Baselines kütüphanesine göre daha zayıf bir dokümantasyona sahiptir. Zaten kütüphane Stable Baselines'tan
sonra geliştirilmeye başlanmıştır. Henüz olgun bir aşamaya gelmemiştir.
Keras-rl kütüphanesi için örnek kodlar aşağıdaki bağlantıdan elde edilebilir:
https://github.com/keras-rl/keras-rl/tree/master/examples
Aşağda Cartplo simülatörünün keras-rl ile çözümü örnek olarak verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import gym
env = gym.make('CartPole-v1')
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.optimizer import Adam
model = Sequential(name='Cartpole-DeepLearning')
model.add(Flatten(input_shape=(1,) + env.observation_space.shape), name='Flatten')
model.add(Dense(64, activation='relu', input_dim=env.observation_space.shape[0], name='Dense-1'))
model.add(Dense(64, activation='relu', name='Dense-2'))
model.add(Dense(env.action_space.n, activation='linear', name='Output'))
model.summary()
from rl.policy import EpsGreedyQPolicy
policy = EpsGreedyQPolicy(eps=0.2)
from rl.memory import SequentialMemory
memory = SequentialMemory(limit=50000, window_length=1)
from rl.agents.dqn import DQNAgent
agent = DQNAgent(model, nb_actions=env.action_space.n, memory=memory, policy=policy, nb_steps_warmup=10, target_model_update=1e-2)
agent.compile(Adam(lr=1e-3, metrics=['mae']))
agent.fit(env, nb_steps=10000)
obs = env.reset()
while True:
action, _ = model.predict(obs)
obs, reward, done, _ = env.step(action)
if done:
break
env.render()
env.close()
#----------------------------------------------------------------------------------------------------------------------------
Sinir ağları için nispeeten aşağı seviyeli olan üç kütüphane (framework de denilebilir) yaygın olarak kullanılmaktadır:
- Tensorflow
- PyTorch
- Theano
Tensorflow Google firması tarafından bu firmanın öncülüğünde açık kaynak kodlu biçimde oluşturulmuştur. PyTorch ise
Facebook firması tarafından bu firmanın öncülüğünde yine açık kaynak kodlu biçimde oluşturulmuştur. Theano kütüphanesi
daha genel amaçlıdır ve birkaç üniversite tarafından geliştirilmiştir. Tensorflow ve PyTorch kütüphaneleri için oldukça fazla
kitap ve kaynak bulunmaktadır.
Tensorflow ve PyTorch kütüphaneleri üzerine oturtulmuş daha yüksek seviyeli kütüphaneler bulunmaktadır. Bunlara "ecosystem" de
denilmektedir. Örneğin Keras kütüphanesi aslında Tensorflow kullnılarak oluşturulmuş yüksek seviyeli bir kütüphanedir.
Her iki kütüphane de ilk çıktığından bu yana gittikçe iyileştirilmiştir. Tensorflow kütüphanesinde 2'li versiyonlarla birlikte
geçmişe doğru uyumu bozan önemli farklılıklar bulunmaktadır. Bu bağlamda PyTorch kütüphanesinin geriye doğru uyumu daha yüksektir.
Biz kurusumuzda önce PyTorh kütüphanesinin genel kullanımı üzerinde duracağız sonra Tensorflow kütüphanesini inceleyeceğiz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
PyTorch kütüphanesinin dokümantasyonuna aşağıdaki bağlantıdan erişilebilir:
https://pytorch.org/docs/stable/index.html
Pytorch kütüphanesi aşağıdaki gibi install edilebilir:
pip install torch
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
PyTorch kütüphanesinde ana öğe "tensör" denilen veri yapısıdır. PyTorch tensörleri aslında NumPy dizilerine benzemektedir.
Yani NumPy dizileri ile Numpy'da yapılanların benzerleri PyTorch tensörleri ile Pytorch'ta yapılabilmektedir. Ancak tensör ,
makine öğrenmesi ve özellikle de sinir ağları için oluşturulmuş olan bir veri yapısıdır. Bu baımdan her ne kadar NumPy'ın ndarray
dizisine benze de farklı yetenekler sunmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
PyTorch kütüphanesinin import ismi "torch" biçimindedir. Tipik olarak kütüphane aşağıdaki gibi import edilmektedir:
import torch
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
PyTorch'da ilk öğrenilecek şey tensör yaratmaktır. Tensörler torch.Tensor isimli bir sınıfla temsil edilmiştir.
Bir tensör yaratmanın pek çok yolu vardır. En basit yolu torch.tensor fonksiyonunu kullanmaktır. Bu fonksiyonun birinci
parametresinde tensörü oluşturan değerler Python listesi biçiminde ya da NumPy dizisi biçiminde verilebilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import torch
t1 = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(type(t1))
import numpy as np
a = np.random.random((10, 10))
t2 = torch.tensor(a)
print(t2)
#----------------------------------------------------------------------------------------------------------------------------
PyTorch'da da tıpkı Tensorflow ve numpy'da olduğu gibi tensörlerin C Programlama Dilindeki temsilleri dtype parametresiyle belirtilebilmektedir.
PyTorch kütüphanesindeki dtype diğerlerinde olduğuı gibidir. Ancak bazı farklılıklar vardır. dtype belirtilmezse
default olarak tensor değerlerine de bakılarak torch.in32 ya da torch.float64 alınmaktadır.
PyTorch'ta dtype türü yazısal olarak belirtilemez. NumPy'daki dtype sınıfları da bunun kullanılamamaktadır. dtype türleri
torch modülünün içerisindeki sınıflarla temsil edilmiş durumdadır. Örneğin torch.float32, torch.in32 gibi.
Eğer bir tensör NumPy dizisiniden oluşturuluyorsa ve tensor fonksiyonunda dtype belirtilmemişse tensör nesnesinin dtype türü
NumPy nesnesinden alınmaktadır. Tabii biz dtype türünü kendimiz vererek bu türün NumPy dizisinden alınmasını englleyebiliriz.
#----------------------------------------------------------------------------------------------------------------------------
import torch
t = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=torch.float32)
print(t)
a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype='float32')
t = torch.tensor(a)
print(t)
#----------------------------------------------------------------------------------------------------------------------------
torch.empty fonksiyonu ilkdeğer verilmemiş belli bir boyutta tensör oluşturmakta kullanılır. Yani oluşturulan tensörün
elemanlarında rastgele değerler vardır.
#----------------------------------------------------------------------------------------------------------------------------
import torch
t = torch.empty((10, 10), dtype=torch.int32)
print(t)
#----------------------------------------------------------------------------------------------------------------------------
Tıpkı NumPy kütüphanesinde olduğu gibi torch.zeros içi sıfırlarla dolu, torch.ones içi 1'lerle dolu tensörler oluşturmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import torch
t1 = torch.zeros((10, 10), dtype=torch.int32)
print(t1)
t2 = torch.ones((10, 10), dtype=torch.int32)
print(t2)
#----------------------------------------------------------------------------------------------------------------------------
torch.full fonksiyonu tıpkı numpy.full fonksiyonu gibi tensör nesnesini belli bir değerle yaratmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
t = torch.full((5, 5), 10, dtype=torch.float32)
print(t)
#----------------------------------------------------------------------------------------------------------------------------
torch.rand fonksiyonu 0 ile 1 arasında rastgele değerlerden, torch.randn fonksiyonu standart normal dağılma göre rastgele değerlerden ve
torch.randint fonksiyonu belli aralıkta rastgele tamsayı değerlerden tensörler oluşturmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import torch
t1 = torch.rand((10, 10), dtype=torch.float32)
print(t1)
t2 = torch.randn((10, 10), dtype=torch.float32)
print(t2)
t3 = torch.randint(10, 20, (10, 10), dtype=torch.int32)
print(t3)
#----------------------------------------------------------------------------------------------------------------------------
torch.eye fonksiyonu birim matric biçiminde tensör oluşturmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
import torch
t = torch.eye(10, dtype=torch.float32)
print(t)
#----------------------------------------------------------------------------------------------------------------------------
Pytorch tensörleri torch.Tensor isimli bir sınıf türündendir. Bu sınıfın pek çok faydalı metodu ve özniteliği vardır. shape isimli örnek özniteliği
bize tensörün boyutlarını torch.Size isimli bir sınıf türünden vermektedir. torch.Size sınıfı köşeli parantez operatörünü destekler. Dolayısıyla biz
tensörün boyutlarını böyle elde edebiliriz.
#----------------------------------------------------------------------------------------------------------------------------
import torch
t = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float32)
print(type(t.shape))
print(t.shape)
print(t.shape[0], t.shape[1])
print(len(t.shape))
#----------------------------------------------------------------------------------------------------------------------------
Tensor sınıfının dtype örnek özniteliği yine bize nesnenin dtype bilgisini verir. Bir tensör yaratılırken onun nerede işleneceği
de belirtilebilmektedir. Buna tensörün "device" bilgisi denilmektedir. Device bilgisi default olarak "cpu" biçimindedir.
Tensör yaratan fonksiyonların hepsinde bu device parametresi bulunmaktadır. Bu device parametresi "gpu" yapılırsa tensör grafik işlemci üzerinde
işleme sokulmaktadır. Ancak tensörün grafik işlemci üzerinde işleme sokulabilmesi için gpu kütüphanelerinin (örneğin Windows için cuda)
yüklenmiş olması gerekmektedir. Tensor sınıfının "device" örnek özniteliği ile biz bu bilgiyi geri alabiliriz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bir tensörün içerisindekileri torch.Tensor sınıfının numpy metodu ile NumPy dizisine dönüştürebiliriz.
#----------------------------------------------------------------------------------------------------------------------------
import torch
t = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.float32)
a = t.numpy()
print(type(a))
print(a)
#----------------------------------------------------------------------------------------------------------------------------
torch.tensor fonksiyonu ile biz zaten bir NumPy dizisinden de Tensor nesnesi elde edebiliyorduk. Ancak istenirse torch.from_numpy fonksiyonuyla da
benzer işlem yapılabilir. Bu fonksiyonun tek bir parametresi vardır o da numpy dizisidir.
#----------------------------------------------------------------------------------------------------------------------------
import torch
import numpy as np
a = np.random.random((5, 5))
t = torch.from_numpy(a)
print(t)
#----------------------------------------------------------------------------------------------------------------------------
Bir tensör nesnesinin elemanlarına [] operatörü ile erişebiliriz. Ancak elemanlar bize yine Tensor nesnesi olarak verilmektedir.
Tensör nesnelerinin elemanları da atama yoluyla değiştirilebilmektedir. Benzer biçimde tensör nesneleri de dilimlenebilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import torch
t = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=torch.float32)
print(t)
x = t[1, 1]
print(x)
t[1, 1] = 100
print(t)
y = t[1:2, 0:3]
print(y)
t[1:2, 0:3] = 0
print(t)
#----------------------------------------------------------------------------------------------------------------------------
Tıpkı NumPy dizilerinde olduğu gibi Tensör nesneleri üzerinde aritmetik işlemler yapılabilmektedir. Örneğin iki tensör
nesnesi toplanabilir, çıkartılabilir. Bir tensör nesnesi bir skalerle işleme sokulabilir. Bu kullanım NumPy
kütüphanesindekine oldukça benzemektedir.
#----------------------------------------------------------------------------------------------------------------------------
import torch
t1 = torch.tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=torch.float32)
t2 = torch.tensor([[9, 8, 7], [6, 5, 4], [3, 2, 1]], dtype=torch.float32)
result = t1 + t2
print(result)
result = t1 * 2 + t2
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Bir tensör içerisinde tek bir eleman varsa (bu eleman çok boyutlu bir tensörün içerisinde de olabilir) o eleman Tensor
sınıfının item metoduyla elde edilebilir. item metodu birden fazla elemana sahip tensörlere uygulanamaz. Bu durumda
eleman önce köşeli parantez operatöryle alınıp sonra item metodu uygulanmalıdır. [] operatörünün her zaman tensör nesnesi
verdiğini anımsayınız.
#----------------------------------------------------------------------------------------------------------------------------
import torch
t = torch.tensor([[[1]]], dtype=torch.float32)
val = t.item()
print(val)
t = torch.rand((5, 5))
val = t[3, 2].item()
print(val)
#----------------------------------------------------------------------------------------------------------------------------
torch.Tensor sınıfının pek çok matematiksel işlem yapan metodu vardır. Bu metotlar tensörün her elemanı üzerinde işlem yapıp yine bir
tensör nesnesi vermektedir. Aslında bu metotların pek çoğunun birer global karşılıkları da vardır. Başka bir deyişle t bir
tensör nesnesi olmak üzere foo da bir matematiksel metot olmak üzere:
result = t.foo(...)
işlemlerinin çoğu:
result = torch.foo(t, ...)
biçiminde de yapılabilmektedir.
NumPy kütüphanesindeki axis kavramı PyTorch kütüphanesinde benzer biçimde dim kavramıyla oluşturulmuştur. Bu iki kavramın kullanım mantığı aynıdır.
Bazı matemetiksel işlem yapan metotlar (ya da fonksiyonlar) şöyledir:
- max
- min
- argmax
- argmin
- sin, cos, tan, asin, acons, atan
- log, log10, exp
- sqrt
- pow
...
Bunların çoğu dim parametresi almaktadır. Bu parametre ihmal edildiğinde tıpkı NumPy'da olduğu gibi tüm değerler üzerinde işlem yapılır.
dim=0 sütun temelinde işlemler için, dim=1 satıt temelinde işlemler için kullanılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Pytorch temel olarak bir yapay sinir ağı kütüphanesidir. Kütüphane içerisinde aşağı seviyeli bir biçimde yapay sinir ağı modelleri
oluşturulabilmektedir. Aynı zamanda yüksek seviyeli birtakım öğeler de bulunmaktadır. Hatta çeşitli konulara ilişkin hazır pek çok sınıf PyTorch projesine
eklenti oluşturan yan projelerde bulunmaktadır. Örneğin bir resim sınıflandırma işlemi için model tamamen sıfırdan oluşturulabileceği gibi zaten
hazır larak bulunan çeşitli modellerden biri seçilip az bir çabayla da işlemler yürütülebilmektedir.
PyTorch bir kütüphane olarak Tensorflow'a göre daha "nesne yönelimli" tasarlanmıştır. Programcı çeştili modeller için var olan sınıflardan türetmeler
yaparak işlemlerini yürütebilmektedir. Bu bağlamda bazı işlemler sınıfsal düzeyde gerçekleştirilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda da PyTorch'ta yüksek seviyeli özel konulara ilişkin çeitli kütüphanelerin bulunduğunu bunlara "PyTorch ecosystem"
dendiğni söylemiştik. Ecosystem içerisindeki yüksek seviyeli önemli kütüphaneler şunlardır:
TorchVision: Tamamen görüntü işleme ile ilgili işlemler oluşturulmuş kütüphanedir. Örneğn resimlerin sınıflandırılması için
bu torchvision paketinden faydalanılabilir.
TorchText: Metinsel işlemler için kullanılan yüksek seviyeli kütüohanedir. Örneğin metinlerin sınıflandırılması için bu paketteki
öğelerden faydalanılablir.
TorchAudio: Voice processing işlemleri için bulundurulmuş yüksek seviyeli kütüpahenedi.r
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
PyTorch kütüphanesinde de hazır pek çok veri kümesi bulunmaktadır. Resimsel sınıflandırmaya ilişkin yüksek seviyeli birtakım sınıflar ve hazır veriler
torchvision paketindedir. Yazısal sınıflandırmaya ilişkin hazır birtakım sınıflar ve veriler ise torchtext paketinde bulunmaktadır. Benzer biçimde işitsel
öğeler için torchaudio, hareketli görüntüsel öğeler için torchvideo gibi yüksek düzeyli paketler ana kütüphaneye entegre edilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
PyTorch modelinde önemli kavramladan biri "dataset" kavramıdır. Dataset veri kümesini temsil eden bazı metotları bulunan bir arayüz sınıftır.
PyTorch içerisindeki hazır birtakım veri kümeleri yüklendiğinde aslında bu veri kümeleri Dataset arayüzünü destekleyen sınıflar biçiminde bize verilmektedir.
Tabii programcı kendi verileri için arayüz Dataset sınıfını kendisi oluşturmak durumundadır. Bir Dataset sınıfı torch.utils.data.Dataset isimli sınıftan türetilerek
oluşturulmaktadır. Programcı kendi dataset sınıfını oluşturacaksa bu torch.utils.data.Dataset sınıfından sınıf türetip __getitem__ metodunu
yazmalıdır. Ya da bu işlemi kendi içerisinde yapan hazır başka dataset sınıflarını da kullanabilir. PyTorch kütüphanesinde çeşitli
amaçlara denk düşen Dataset sınıfından türetilmiş özel dataset sınıfları da bulunmaktadır. Örneğin TensorDataset isimli hazır
dataset sınıfı kendi verilerimizi dataset kavramı biçiminde ifade etmek için pratik bir sınıftır. Bu sınıf bizden x ve y
verilerini alarak bize protokole uygun bir dayaset nesnesi vermektedir. Örneğin:
from torch.utils.data import TensorDataset
dataset = TensorDataset(training_dataset_x, training_dataset_y)
Artık dataset nesnesi üzerinde [...] operatörü ile elemanlar elde edilebilir ve dilimlemeler yapılabilir. Ancak bu [...] operatörü
bize iki elemanlı bir demet verecektir. Demetin birinci indeklere ilişkin x verilerinden ikinxi elemanı da y verilerinden oluşmaktadır.
Aslında TensorDataset nenesini yaratırken biz istediğimiz kadar argüman girebiliriz. Bu durumda [...] operatörü bize o uzunlukta bir demet verir.
Örneğin:
dataset = TensorDataset(x, y, z)
Tabii tipik olarak biz veri kümesi için x ve y değerlerini TensorDataset sınıfına veririz.
TensorDataset sınıfı biz ona hangi türden nesne verirsek bize [...] operatörü ile aynı türden neslerden oluşan demet vermektedir.
Örneğin biz bu sınıfa NumPy dizilerini versek bu sınıf bize NumPy dizilerini verir. Ancak PyTorch'ta çalışma tensörkerke yapılmaktadır.
Dolayısıyla bizim TensorDataset sınıfına x ve y değerlerindne oluşan Tensor nesnelerini vermemiz uygun olur.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Programcının PyTorch'da bir veri kümesi ile çalışmak için ilk yapacağı şey kendi veri kümesini bir Dataset nesnesi olarak ifade etmektir.
Yukarıda da belirtildiği gibi bu işlem için programcı Dataset sınıfından türetme yaparak kendi Dataset sınıfını oluşturabilir.
Ya da TensorDataset gibi hazır bir sınıftan da faydalanabilir.
Aşağıda "Boston Housing Price" verilerinden hareketle oradaki verilerden PyTorch kütüphanesine uygun Dataset nesnesi elde edilmektedir.
Burada hazır TensorDataset sınıfından faydalanılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('housing.csv', dtype='float32')
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.20)
import torch
training_tensor_x = torch.tensor(training_dataset_x)
training_tensor_y = torch.tensor(training_dataset_y)
test_tensor_x = torch.from_numpy(test_dataset_x)
test_tensor_y = torch.from_numpy(test_dataset_y)
from torch.utils.data import TensorDataset
training_dataset = TensorDataset(training_tensor_x, training_tensor_y)
test_dataset = TensorDataset(test_tensor_x, test_tensor_y)
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda da belirttiğimiz gibi aslında PyTorch içerisinde popüler pek çok veri kümesi değişik paketler içerisinde bulunmaktadır. Bu veri kümeleri
zaten bize PyTorch kütüphanesinin istediği Dataset nesnesi biçiminde verilmektedir. Örneğin resimsel verilerin sınıflandırılması için yüksek seviyeli sınıflar
ve veri kümeleri barındıran torchvision içerisindeki datasets modülünde pek çok popüler veri kümesine ilişkin hazır Dataset sınıfları bulunmaktadır.
Örneğin torchvision.datasets modülündeki CIFAR10 isimli sınıf daha önce üzerinde çalışmış olduğuğumuz Cifar10 verilerini PyTorch kütüphanesinin gereksinim duyduğu
Dataset sınıfı biçiminde bize vermektedir. Bu nesne yaratılırken verilerin Internet'ten yerel makineye indirileceği makinemizdeki dizinin ismi root
parametresiyle girilmektedir. train parametresi True ise eğitim veri kümesi için False ise test veri kümesi için Dataset nesneleri oluşturulur.
download parametresi True geçilirse veriler Internet'ten indirilir. False geçilirse indirilmiş veriler doğurdan kullanılır. Tabii download=True
durumunda veriler zaten indirilmişse bir daha indirilmez.
Bu veri kümelerinin data isimli örnek öznitelikleri x verilerinin hepsini bize numpy dizisi olarak vermektedir. Benzer biçimde bu veri
kümelerinin targets örnek öznitelikleri de y değerlerini bize NumPy dizisi olarak vermektedir.
Bu biçimde hazır olarak elde ettiğimiz dataset nesnelerinin [...] operatör metodu bulunmaktadır. Ancak resimsel verilerde bu operatör metodu bize
default durumda x verilerini PIL.Image (Python Image Library'deki Image sınıfı) nesnesi olarak verir.
#----------------------------------------------------------------------------------------------------------------------------
from torchvision.datasets import CIFAR10
training_dataset = CIFAR10(root='cifar100-data', train=True, download=True)
test_dataset = CIFAR10(root='cifar10-data', train=False, download=True)
x, y = training_dataset[0]
print(type(x), type(y)) # <class 'PIL.Image.Image'> <class 'int'>
#----------------------------------------------------------------------------------------------------------------------------
torchvision içerisindeki hazır veri kümelerini kullanırken bunların bazı önişlemlere sokulması gerekebilmektedir. Bunun için CIFAR10 gibi
hazır dataset veren sınıflarda transform ve target_transform isimli parametreler bulunmaktadır. Bu parametrelere __call__ metoduna sahip sınıf
nesneleri girilir. Bu sınıflar da verileri indirdikten sonra parametrelerle belirtilen nesne ile __call__ metotlarını çağırırlar. Bu metotların geri dönüş
değerlerini bize nihai değer olarak verirler. Böylece veriler indirilirken aynı zamanda veriler üzerinde bazı önişlemeler de yapılmış olur. transform parametresi
x verileri için target_transform parametresi y verileri için belirtilen nesnelerin __call__ metotlarını çağırmaktadır. Örneğin biz buradaki resmi PIL.Image
sınıfından bir Tensor nesnesine dönüştüren bir "trasnformer" sınıf yazabiliriz:
import torch
from torchvision.datasets import CIFAR10
import numpy as np
class MyTransformer:
def __call__(self, image):
return torch.tensor(np.array(image))
training_dataset = CIFAR10(root='cifar10-data', train=True, download=True, transform=MyTransformer())
test_dataset = CIFAR10(root='cifar10-data', train=False, download=True, transform=MyTransformer())
x, y = training_dataset[0]
print(type(x), type(y))
Aslında örneğin yukarıdaki işlemi yapan yani PIL.Image nesnesindeki verileri Tensor haline getiren torchvision.transforms modülünde ToTensor
isimli bir sınıf vardır. Aynı işlemi bu sınıfla da yapabilirdik:
Bir grup önişlemi peşi sıra yapmak için ayrıca Compose isminde bir dekoratör sınıf da bulundurulmuştur. Programcı Compose sınıfı türünden bir nesne yaratıp bu
sınıfın __init__ metodunda transform nesnelerini bir liste ile sınıfa verir. Sınıfın __call__ metodu bu nesnelerin __call_ metotlarını çağırarak
orada belirtilen önişlemleri sırasıyla yapar. Compose sınıfı aşağıdakine benzer biçimde yazılmıştır:
class Compose:
def __init__(self, args):
self.args = args
def __call__(self, x):
for arg in self.args:
x = arg(x)
return x;
Örneğin:
from torchvision.datasets import CIFAR10
from torchvision.transforms import Compose, ToTensor, Normalize
training_dataset = CIFAR10(root='cifar10-data', train=True, download=True, transform=Compose([ToTensor(), Normalize((0, 0, 0), (1, 1, 1))]))
test_dataset = CIFAR10(root='cifar10-data', train=False, download=True, transform=Compose([ToTensor(), Normalize((0, 0, 0), (1, 1, 1))]))
x, y = training_dataset[0]
print(type(x), type(y))
#----------------------------------------------------------------------------------------------------------------------------
from torchvision.datasets import CIFAR10
from torchvision.transforms import Compose, ToTensor, Normalize
training_dataset = CIFAR10(root='cifar10-data', train=True, download=True, transform=Compose([ToTensor(), Normalize((0, 0, 0), (1, 1, 1))]))
test_dataset = CIFAR10(root='cifar10-data', train=False, download=True, transform=Compose([ToTensor(), Normalize((0, 0, 0), (1, 1, 1))]))
x, y = training_dataset[0]
print(type(x), type(y))
#----------------------------------------------------------------------------------------------------------------------------
Dataset sınıfları veri kümesini temsil etmektedir. Veri kümesi eğitilirken verilerin batch batch elde edilmesi gerekmektedir.
İşte bunun için DataLoader sınıfları kullanılmaktadır. Bir DataLoader nesnesi torch.DataLoader sınıfı ile oluşturulabilir.
DataLoader nesnesi oluşturulurken Dataset nesnesi ve batch_size miktarı parametre olarak girilir. İstenirse shuffle=True parametresiyle
karıştırma da yapılbilir. DataLoader nesneleri dolaşılabilir (iterable) nesnelerdir. Nesne her dolaşıldığında x ve y olarak
batch_size kadar veri tensör nesnesi olarak elde edilmektedir. Dolaşım sırasında son parçada batch_size kadar bilgi kalmayabilir.
Bu durumda son yinelemede kalan miktarda veri elde edilir. Örneğin:
from torch.utils.data import TensorDataset, DataLoader
training_dataset = TensorDataset(training_tensor_x, training_tensor_y)
test_dataset = TensorDataset(test_tensor_x, training_tensor_y)
training_dataloader = DataLoader(training_dataset, batch_size=32, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=32, shuffle=True)
for x, y in training_dataloader:
print(type(x), x.shape)
Aşağıdaki örnekte "Boston Housing Price" veri kümesi için DataLoader nesneleri oluşturulmuştur. Burada önce Boston veri kümesi
CSV dosyasından okunmuş sonra bu verilerden Dataset nesnesi oluşturulmuş ve nihayet bu dataset nesnesinden de DataLoader nesnesi
oluşturulmuştur.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('housing.csv', dtype='float32')
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.20)
import torch
training_tensor_x = torch.tensor(training_dataset_x)
training_tensor_y = torch.tensor(training_dataset_y)
test_tensor_x = torch.from_numpy(test_dataset_x)
test_tensor_y = torch.from_numpy(test_dataset_y)
from torch.utils.data import TensorDataset, DataLoader
training_dataset = TensorDataset(training_tensor_x, training_tensor_y)
test_dataset = TensorDataset(test_tensor_x, training_tensor_y)
training_dataloader = DataLoader(training_dataset, batch_size=32, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=32, shuffle=True)
for x, y in training_dataloader:
print(type(x), x.shape)
#----------------------------------------------------------------------------------------------------------------------------
Artık sıra yapay sinir ağını oluşturmaya gelşmiştir. PyTorch'ta yapay sinir ağı modeli torch.nn modülü içerisinde Module isimli
bir sınıftan türetme yapılarak oluşturulmalıdır. (Module ismi Python için uygun bir isim olmayabilir. Çünkü Python'da "modül" terimi
tamamen başka bir anlama gelmektedir.). Örneğin:
from torch.nn import Module
class MyModule(Module):
pass
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Programcı Module sınıfından türettiği sınıfta iki metodu yazmalıdır: __init__ ve forward. forward metodu aslında bir callback
metot biçimindedir. Eğitim sırasında her batch işleminde dolaylı olarak bu forward metodu çağrılmaktadır.
Yapay sinir ağını temsil eden bu Module sınıfından türettiğimiz sınıfta bizim __init__ metodunda katman nesnelerini yaratıp
sınıfın örnek özniteliklerine atamamız gerekmektedir. Bu katman nesneleri daha sonra framework tarafından biriktirilip bazı işlemlere sokulacaktır.
Örneğin:
from torch.nn import Module
class MyModule(Module):
def __init__(self):
super().__init__()
def forward(self, batch_x):
pass
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Pytorch'ta katman nesneleri için torch.nn modülünde çeşitli sınıflar bulundurulmuştur. Keras'taki Dense katmana en çok benzeyen katman
Linear isimli katmandır. Keras'ın Sequential isimli model nesnesi anımsanacağı gibi otomatik olarak önceki katmanın çıktısını
sonraki katmanın girdine bağlıyordu. Bu nedenle Keras'ta katman nesneleri yaratılırken yalnızca çıktı katmanındaki nöron sayısı
belirtiliyordu. Ancak PyTorch'ta katman nesnelerinde hem girdi nöron sayısı hem de çıktı nöron sayısı belirtilmektedir.
Anımsanacağı gibi nöronun girdileri girdi sayısı sayısı kadar w değerleriyle çarpılıp toplanıyordu (dot product). Katmanda n tane girdi m tane
çıktı varsa bu n tane girdi m tane nörona bağlı olduğu için ve bu nöronların her birindeki w değerleri farklı olduğu için eğitilecek
parametrelerin sayısı n * m + m kadar oluyordu (buradaki m bias değerinden gelmeketedir.) Keras'ın Dense katmanında bu her nörondaki dot product değeri
en sonunda bir aktivasyon fonksiyonuna sokuluyordu. Oysa PyTorch'ta Linear isimli katman yalnızca dot product işlemini yapmaktadır.
Bu dot product değerini aktivasyon fonksiyonuna sokmamaktadır. Zaten bu nedenle katmanın ismine Linear denmiştir. Başka bir deyişle
nöronda elde edilen dot product değerinin aktivasyon fonksiyonuna sokulmaması aslında Linear aktivasyon fonksiyonuna sokulması ile
aynı anlamdadır. PyTorch'ta aktivasyon fonksiyonları farklı bir katman gibi düşünülmüştür. Yani PyTorch'taki aktivasyon katmanlarını
biz dot product yapmadan nöronun girdisini aktivasyon fonksiyonuna sokarak çıktıtya veren bir katman olarak düşünebiliriz. Aktivasyon katmanları eğitilebilir
parametreye sahip olmadığı için içi sınıfın örnek özniteliklerinde saklanmak zorunda değildir. Eğer bazı katmanlar aynı aktivasyon fonksiyonuna
sahipse her katman için ayrı bir aktivasyon katman nesnesinin de oluşturulmasına da gerek yoktur.
Türettiğimiz Module sınıfının __init__ metodunda katman nesnelerini birbirine bağlamamaktayız. __init__ metodunda yalnızca katman nesnelerini
oluşturulup sınıfın örnek özniteliklerine atamalıyız. (Yukarıda belirttiğimiz gibi aktivasyon katman nesnelerinin sınıfın örnek özniteliklerine
atanması gerekmez.) Örneğin:
from torch.nn import Module, Linear
class MyModule(Module):
def __init__(self, input_size):
super().__init__()
self.input_size = input_size
self.hidden1 = Linear(input_size, 64)
self.hidden2 = Linear(64, 64)
self.output = Linear(64, 1)
def forward(self, batch_x):
pass
Katman nesnelerinin de dtype bilgisi vardır. Bu dtype bilgisi katman nesnelerinin içerisindeki w değerlerine ilişkindir. Eğer yukara da
yaptığımız gibi katman nesnelerinde dtype belirtmezsek default olarak torch.float32 alınmaktadır. Eğitim sırasında katman nesnelerinin
dtype türünün FataLoader nesnesinden elde edilen tensörlerin dtype türleriyle uyuşması gerekmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Module sınıflarının forward metotları eğitim sırasında her batch işlemi için callback metot biçiminde çağrılmaktadır. forward
metodu self parametresinin yanı sıra batch_x ile temsil edilen bir parametreye de sahiptir. Bu batch_x parametresine
veri kümesindeki bir batch kadar bilgi geçirilmektedir. Yani metot çağrıldığında buradaki batch_x parametresi batch_size kadar satırdan
ve veri kümesindeki özellik sayısı kadar sütundan oluşan bir Tensör görünümünde olacaktır.
Pekiyi forward metodunda biz ne yapmalıyız? İşte bu metotta biz gerçekten yaratmış olduğumuz katman nesnelerini devreye sokarak
bir batch_size kadar bilgiyi yapay sinir ağına vererek çıktıyı elde etmeliyiz. forward metodunu biz son katmanın çıktısı ile
geri döndürmeliyiz. Yani özetle forward metodunun batch_x parametresi bir batch kadar bilgiyi içeren bir tensördür. Bu bir batch'lik
bilgiyi biz yapay sinir ağımızın katmanlarına sokarak çıktı elde etmeliyiz. Bu çıktıyla da forward metodunu geri döndürmeliyiz.
Tıpkı Keras'ın fonksiyonel modelinde olduğu gibi PyTorch kütüphanesinde de ona benzer bir tasarım kullanılmıştır. PyTorch katman sınıflarının
__call__ metotları o katmanın yapması gereken işlemi yapmaktadır. Örneğin Linear sınıfının __call__ metodu dot product işlemi yapar.
ReLU sınıfının __call__ metodu relu aktivasyon fonksiyonun işlemini yapar. O halde biz forward metodonda x parametresi ile aldığımız
tensörü bu katman nesnelerinin __call__ metotlarına sokarak bu metotların geri dönüş değerlerini katman çıktısı olarak alırız.
Önceki katmanın çıkışını sonraki katmanın girişine vererek yapay sinir ağını işletiriz. Örneğin:
from torch.nn import Module, Linear, ReLU
class MyModule(Module):
def __init__(self, input_size):
super().__init__()
self.input_size = input_size
self.hidden1 = Linear(input_size, 64)
self.hidden2 = Linear(64, 64)
self.output = Linear(64, 1)
self.relu = ReLU()
def forward(self, batch_x):
x = self.hidden1(batch_x)
x = self.relu(x)
x = self.hidden2(x)
x = self.relu(x)
x = self.output(x)
return x
Aşağıda Boston Housing Price veri kümesi için Modül sınıfınun oluşturulmasına bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('housing.csv', dtype='float32')
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.20)
import torch
training_tensor_x = torch.tensor(training_dataset_x)
training_tensor_y = torch.tensor(training_dataset_y)
test_tensor_x = torch.from_numpy(test_dataset_x)
test_tensor_y = torch.from_numpy(test_dataset_y)
from torch.utils.data import TensorDataset, DataLoader
training_dataset = TensorDataset(training_tensor_x, training_tensor_y)
test_dataset = TensorDataset(test_tensor_x, training_tensor_y)
training_dataloader = DataLoader(training_dataset, batch_size=32, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=32, shuffle=True)
from torch.nn import Module, Linear, ReLU
class MyModule(Module):
def __init__(self, input_size):
super().__init__()
self.input_size = input_size
self.hidden1 = Linear(input_size, 64)
self.hidden2 = Linear(64, 64)
self.output = Linear(64, 1)
self.relu = ReLU()
def forward(self, batch_x):
x = self.hidden1(batch_x)
x = self.relu(x)
x = self.hidden2(x)
x = self.relu(x)
x = self.output(x)
return x
mm = MyModule(training_tensor_x.shape[1])
#----------------------------------------------------------------------------------------------------------------------------
Module sınıfları da __call__ metotlarına sahiptir. Yani elimizde Module sınıfı türünden bir nesne varsa biz o nesneyle
fonksiyon çağırma operatörünü kullanabiliriz. Bu durumda Module sınıfının __call__ metodu çağrılır. İşte Module sınıflarının __call__ metotları
aslında kendi içerisinde forward metodunu çağırmakta ve bize forward metodunun geri döndürdüğü değeri vermektedir.
Aşağıdaki örnekte Module sınıfı türünden bir nesne yaratılıp bu nesne ile sınıfın __call__ metodu çağrılmıştır. __call_metodu da
forwrad metodunun çağrılmasına yol açmakta ve forward metodunun geri dönüş değeri de __call__ metodunun geri dönüş değeri olarak verilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import torch
from torch.nn import Module, Linear, ReLU, Sigmoid
class MyModule(Module):
def __init__(self):
super().__init__()
self.linear1 = Linear(10, 64)
self.linear2 = Linear(64, 64)
self.linear3 = Linear(64, 1)
self.relu = ReLU()
self.sigmoid = Sigmoid()
def forward(self, x):
x = self.linear1(x)
x = self.relu(x)
x = self.linear2(x)
x = self.relu(x)
x = self.linear3(x)
x = self.sigmoid(x)
return x
x = torch.rand(32, 10)
mm = MyModule()
result = mm(x) # bu forward metodunun çağrılmasına yol açmaktadır ve forward metodunun geri dönüş değeri ile geri dönmektedir.
#----------------------------------------------------------------------------------------------------------------------------
Aslında Module sınıfından türetme yaparak kendi modül sınıfımızı oluşturmak yerine doğrudan torch.nn modülü içerisindeki
Sequential isimli sınıfı da kullanabiliriz. Bu Sequential sınıfının __init__ metodunda biz katman nesnelerini sırasıyla
argüman olarak veririz. Sınıf da bunları nesnenin özniteliklerinde saklayıp forward metodunda bunları birbirine bağlamaktadır.
Örneğin:
from torch.nn import Sequential
sequential = Sequential(
Linear(training_tensor_x.shape[1], 64),
ReLU(),
Linear(64, 64),
ReLU(),
Linear(64, 1))
Biz burada bir modül nesnesi oluşturmuş olduk. Adeta bu Sequential sınıfı yukarıda belirttiğimiz şeyleri bizim için zaten
yapmaktadır. Sequential sınıfında argümanlar demetlerden oluşan bir sözlük (en uygunu OrderedDict kullanmak) olarak da girilebilir.
Bu durumda demetlerin ilk elemanı katman nesnesinin ismini, ikinci elemanı nesnenin kendisini içermelidir. Örneğin:
from torch.nn import Sequential
from collections import OrderedDict
sequential = Sequential(OrderedDict([
('hidden1', Linear(training_tensor_x.shape[1], 64)),
('relu1', ReLU()),
('hidden2', Linear(64, 64)),
('relu2', ReLU()),
('output', Linear(64, 1))
]))
Burada verdiğimiz isimler doğrudan sınıfın örnek özniteliklerinin isimleri olmaktadır. (Eskiden Python'da sözlük nesnelerinin
anahtarları elde edilirken anahtarlar herhangi bir sırada elde edilebiliyordu. Ancak Python 3.7 ile birlikte artık anahtarlar onların sözlüğe
eklenme sırasına göre elde edilmektedir. İşte eski versiyonlarda sözlük anahtarlarının eklenme sırasına göre elde edilebilmesini sağlamak için
standart kütüphanede collections modülü içerisine OrderedDict sınıfı yerleştirilmişti. Burada Sequential sınıfının bu örnek özniteliklerini bizim verdiğimiz
sırada elde etmesi gerekmektedir. Bu nedenle OrderedDict kullanılmıştır. Her ne kadar artık OrderedDict yerine Python 3.7 ve sonrasında
normal sözlükler kullanılabiliyorsa da PyTorch buna halen izin vermemektedir.)
Aşağıda Boston Housing Price veri kümesi için Modül sınıfından türetme yapmak yerine Sequential sınıfının kullanılmasına
örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('housing.csv')
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.20)
import torch
training_tensor_x = torch.tensor(training_dataset_x)
training_tensor_y = torch.tensor(training_dataset_y)
test_tensor_x = torch.from_numpy(test_dataset_x)
test_tensor_y = torch.from_numpy(test_dataset_y)
from torch.utils.data import TensorDataset, DataLoader
training_dataset = TensorDataset(training_tensor_x, training_tensor_y)
test_dataset = TensorDataset(test_tensor_x, test_tensor_y)
training_dataloader = DataLoader(training_dataset, batch_size=32)
test_dataloader = DataLoader(test_dataset, batch_size=32)
from torch.nn import Sequential, Linear, ReLU
from collections import OrderedDict
sequential = Sequential(OrderedDict([
('hidden1', Linear(training_tensor_x.shape[1], 64)),
('relu1', ReLU()),
('hidden2', Linear(64, 64)),
('relu2', ReLU()),
('output', Linear(64, 1))
]))
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi Dataset nesnesi ve Dataloader nesnesini oluşturduk. Yapay sinir ağımızı bir Module sınıfı ile temsil ettik.
Şimdi ne yapacağız? İşte artık ağı eğitmemiz gerekmektedir. Anımsanacağı gibi ağın eğitilmesi için minimize edilmesi gereken bir
fonksiyona ve minimizasyon işlemini yapan bir algoritmaya gereksinim vardır. Anımsayacağınız gibi biz Keras'ta minimize edilecek fonksiyona
"loss" fonksiyonu, bunu minimize etmek için kullanılacak alagoritmaya da "optimizer" diyorduk.
O halde bizim eğitim için bir loss fonksiyonuna bir de optimizasyon algoritmasına ihtiyacımız vadır. PyTorch'ta loss fonksiyonları ve optimizasyon
algortimaları sınıflarla temsil edilmiştir. Loss fonksiyonlarına ilişkin PyTorch sınıflarının ismi XXXLoss biçimindedir. Loss sınıfları
türünden nesneler yaratıldıktan sonra bu nesnelerle loss sınıflarının __call__ metotları çağrıldığında bu metotlar bu loss değerini hesaplamaktadır.
Aşağıdaki örnekte BCELoss (Binary Cross Entorpy Loss) sınıfının nasıl kullanıldığına gösterilmiştir. Burada rastgele x ve y değerleri alınmıştır.
y değerleri ikili kategorik değerlerdir. loss nesnesinin __call__ metodu "Binary Cross Entropy" değerini bize vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
import torch
from torch.nn import BCELoss
loss = BCELoss()
yhat = torch.rand(64)
y = torch.randint(0, 2, (64, ), dtype=torch.float)
result = loss(yhat, y)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Optimizer sınıfları torch.optim modülünde bulunmaktadır. Örneğin bu modüldeki Adam sınıfı Adam optimizasyonu yapmaktadır. Örneğin:
from torch.optimn import Adam
optimizer = Adam(params)
Optimizasyon sınıfları birinci parametre olarak bizden güncellenecek w değerlerini istemektedir. Güncellenecek w değerleri aslında
katman nesnelerinin içerisindedir. Onları katman nesnelerinin içerisinden alıp optimizasyon sınıfına vermek gerekir. Ancak bu işlem kolay değildir.
İşte PyTorch'ta Module sınıfının parameters isimli metodu örnek özniteliklerine atanmış olan katman nesnelerinin içerisindeki w değerlerini
alıp bir tensör biçiminde vermektedir. Tabii parameters metodu katman nesnelerinin içertisindeki w değerlerini kopyalayarak değil bir view
nesnesi biçiminde vermektedir. (Yani burada yapılan değişiklikler gerçek kaynağı etkilemektedir.) O halde optimizasyon sınıflarına
ilişkin nesneler tipik olarak şöyle yaratılmalıdır:
from torch.optimn import Adam
optimizer = Adam(mm.parameters())
Optimizer algoritma sınıflarının __init__ metotlarının çeşitli parametreleri vardır. En tipik parametre lr isimli "learning rate"
belirten parametredir. Bazı sınıflarda bu lr parametresi defauşt değer almıştır. Bazı sınıflarda almamaıştır. Örneğin
SGD (Stochastic Gradient Descent) sınıfında biz lr parametresini girmek zorundayız:
from torch.optim import SGD
optimizer = SGD(sequential.parameters(), lr=0.001)
Aşağıdaki örnekte Boston Housing Price veri kğmesi için MSELoss ve SGD sınıf nesneleri oluşturulmuştur.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
dataset = np.loadtxt('housing.csv', dtype='float32')
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.20)
import torch
training_tensor_x = torch.tensor(training_dataset_x)
training_tensor_y = torch.tensor(training_dataset_y)
test_tensor_x = torch.from_numpy(test_dataset_x)
test_tensor_y = torch.from_numpy(test_dataset_y)
from torch.utils.data import TensorDataset, DataLoader
training_dataset = TensorDataset(training_tensor_x, training_tensor_y)
test_dataset = TensorDataset(test_tensor_x, training_tensor_y)
training_dataloader = DataLoader(training_dataset, batch_size=32, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=32, shuffle=True)
from torch.nn import Linear, ReLU
from torch.nn import Sequential
sequential = Sequential(
Linear(training_tensor_x.shape[1], 64),
ReLU(),
Linear(64, 64),
ReLU(),
Linear(64, 1))
from torch.nn import MSELoss
from torch.optim import SGD
loss = MSELoss()
optimizer = SGD(sequential.parameters(), lr=0.001)
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda eğitim için gereken nesneleri oluşturmuş olduk. Şimmdi artık eğitimin kendisini gerçekletirmemiz gerekir. Eğitim
Keras'taki gibi tek bir metotla yapılmamaktadır. PyTorch bu anlamda daha düşük seviyeli bir kütüphanedir. Eğitimin döngü içerisinde
programcının çabasıyla yapılması gerekmektedir. Eğitim için tipik olarak iç içe iki döngülü bir yapı kullanılır. Dıştaki döngü
epoch döngüsüdür. Yani veri kümesinin kaç defa gözden geçirileceğini belirtir. İçteki döngü ise batch döngüsüdür. İçteki döngüde veri kümesi
batch batch elde edilmeli, bir batch'lik bilgi sinir ağına sokulmalı ve buradan bir sonuç elde edilmelidir. Bu sonuç gerçek sonuçla loss fonksiyonuna
sokulmalı ve buradan da bir loss değer elde edilmelidir. Sonra bu değere dayalı olarak optimizasyon nesnesi yoluyla w değerleri güncellenmelidir.
Tabii iç döngüde veri kümesini batch batch ele almak için DataLoader sınıfından faydalanılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
PyTorch'ta sinir ağının eğitilmesi işlemi tipik olarak şöyle yapılmaktadır:
- İç içe iki döngü oluşturulmalıdır. Döngülerden dıştaki epoch'ları oluşturmalı içteki ise o epoch'taki batch'leri oluşturmalıdr.
Örneğin:
for epoch in range(EPOCHS):
for x, y in dataloader:
...
Burada iç döngüde DataLoader nesnesi dolaşılmıştır. Anımsanacağı gibi DataLoader nesnesi her dolaşıldığında bize bir batch'lik
bilgiyi x ve y olarak vermektedir. Artık bizim iç döngü içerisinde bilgileri batch batch işleme sokmamız gerekir.
- Batch işlemleri yapılırken her zaman olmasa da çoğu zaman elde edilen gradient değerlerin kümülatif toplanmaması için yani
her döngüde sıfırlanması için optimizer nesnesi ile zero_grad metodu çağrılmalıdır:
optimizer.zero_grad()
PyTorch tensörler üzerinde otomatik gradient hesap yapabilmektedir. Optimizer nesnesi de default durumda bu gradient değerleri üst
üste toplar. Bu kümülatif toplam bazı ağ modellerinde gerekiyor olsa da çoğu modelde gerekmemektedir.
Bundan sonra bir batch'lik x verisi sinir ağına sokulup oradan y değerleri elde edilmelidir. Anımssanacağı gibi modül nesnesi
fonksiyon çağırma operatörü ile çağrıldığında aslında sınıfın forward metdou çağrılmaktaydı. Bu fprward metodunun çıktısını
biz elde etmekteydik. Örneğin:
pred_y = mm(x)
- Bir batch'lik x değeri sinir ağına sokulduktan sonra buradan elde edilen y değeri ile gerçek y değeri loss fonksiyonuna
sokularak loss değeri hesaplanmalıdır:
lossval = loss(pred_y.flatten(), y)
Sinir ağının son katmanından nx1 biçiminde iki boyutlu bir y değeri elde edilmektedir. Halbuki loss fonksiyonları
bizden tek boyutlu y değerlerini almak istemektedir. Bu nedenle ağdan elde edilen değer Tensor sınıfının flatten metodu
ile tek boyuta indirgenmiştir.
- Loss değeri elde edildikten sonra buradan optimizasyon için ilerlenecek doğrultunun elde edilmesi gerekmektedir. Başka bir
deyişle bu loss değerinden hareketle her bir özellik için gradient vektörlerinin hesaplanması gerekmektedir. Bu işlem Tensor sınıflarının
backward metotlarıyla yapılmaktadır. Örneğin:
lossval.backward()
Bu metot graf üzerinde geriye giderek gradient hesaplarını yaparak bu gradient değerleri saklamaktadır.
- Artık w değerlerinin güncellenmesi aşamasına gelinmiştir. Bu işlem de optimizer sınıflarının step metoduyla yapılmaktadır. Örneğin:
optimizer.step()
Böylece eğitim kodu aşağıdaki gibi bir çatıya sahip olacaktır:
from torch.nn import MSELoss
from torch.optim import Adam
loss = MSELoss()
optimizer = Adam(mm.parameters(), lr=0.001)
EPOCHS = 1000
for epoch in range(EPOCHS):
for x, y in dataloader:
optimizer.zero_grad() # gradient'lerin kümülatif olmamasını sağlar
pred_y = mm(x)
lossval = loss(pred_y.flatten(), y)
lossval.backward() # gradient vektörleri hesaplar
optimizer.step() # modelin w parametrelerini günceller
Aşağıda Boston Housing Price veri kümesi üzerinde bu işlemler yapılmıştırç.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
EPOCHS = 100
BATCH_SIZE = 32
dataset = np.loadtxt('housing.csv', dtype='float32')
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.20)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
import torch
training_tensor_x = torch.tensor(scaled_training_dataset_x)
training_tensor_y = torch.tensor(training_dataset_y)
test_tensor_x = torch.tensor(test_dataset_x)
test_tensor_y = torch.tensor(scaled_test_dataset_x)
from torch.utils.data import TensorDataset, DataLoader
training_dataset = TensorDataset(training_tensor_x, training_tensor_y)
test_dataset = TensorDataset(test_tensor_x, test_tensor_y)
dataloader = DataLoader(training_dataset, batch_size=BATCH_SIZE, shuffle=True)
from torch.nn import Linear, ReLU
from torch.nn import Module
class MyModule(Module):
def __init__(self, input_size):
super().__init__()
self.input_size = input_size
self.hidden1 = Linear(input_size, 64)
self.hidden2 = Linear(64, 64)
self.output = Linear(64, 1)
self.relu = ReLU()
def forward(self, x):
x = self.hidden1(x)
x = self.relu(x)
x = self.hidden2(x)
x = self.relu(x)
x = self.output(x)
return x
mm = MyModule(training_tensor_x.shape[1])
from torch.nn import MSELoss
from torch.optim import Adam
mse_loss = MSELoss()
optimizer = Adam(mm.parameters(), lr=0.001)
for epoch in range(EPOCHS):
for x, y in dataloader:
optimizer.zero_grad() # gradient'lerin kümülatif olmamasını sağlar
pred_y = mm(x)
lossval = mse_loss(pred_y.flatten(), y)
lossval.backward() # gradient vektörleri hesaplar
optimizer.step() # modelin w parametrelerini günceller
print(epoch, end=' ')
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de elde ettiğimiz modeli test edelim. Modelin test edilmesi oldukça basittir. Test verilerini modül nesnesine sokarak tek
hamlede bütün değerleri tahmin edip metrik bir değer elde edebiliriz. Yukarıdaki örnekte iki metrik değeri elde etmek isteyelim. Bunlar
"mean squared error" ve "mean absolute error" olsun. İşin bu kısmını scikit-learn kullanarak yapabiliriz. Ya da bu kısmını yine PyTorch
ile de yapabiliriz. "Mean Absolute Error" işlemi PyTorch'ta L1Loss isimli sınıfla temsil edilmiştir.
Aşağıdaki örnekte test sonucunda "mean squaared error" ve "mean absolute error" değerleri hem PyTorch sınıfları ile hem de scikit-learn
fonksiyonları ile elde edilmiştir. PyTorch sınıflarının bizden tensör nesneleri istediğine halbuki sckit-learn fonksiyonlarının bizden NumPy
dizisi istediklerine dikkat ediniz.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
EPOCHS = 100
BATCH_SIZE = 32
dataset = np.loadtxt('housing.csv', dtype='float32')
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.20)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
import torch
scaled_training_tensor_x = torch.tensor(scaled_training_dataset_x)
training_tensor_y = torch.tensor(training_dataset_y)
scaled_test_tensor_x = torch.tensor(scaled_test_dataset_x)
test_tensor_y = torch.tensor(test_dataset_y)
from torch.utils.data import TensorDataset, DataLoader
training_dataset = TensorDataset(scaled_training_tensor_x, training_tensor_y)
dataloader = DataLoader(training_dataset, batch_size=BATCH_SIZE, shuffle=True)
from torch.nn import Linear, ReLU
from torch.nn import Module
class MyModule(Module):
def __init__(self, input_size):
super().__init__()
self.input_size = input_size
self.hidden1 = Linear(input_size, 64)
self.hidden2 = Linear(64, 64)
self.output = Linear(64, 1)
self.relu = ReLU()
def forward(self, x):
x = self.hidden1(x)
x = self.relu(x)
x = self.hidden2(x)
x = self.relu(x)
x = self.output(x)
return x
mm = MyModule(scaled_training_tensor_x.shape[1])
from torch.nn import MSELoss, L1Loss
from torch.optim import Adam
mse_loss = MSELoss()
optimizer = Adam(mm.parameters(), lr=0.001)
for epoch in range(EPOCHS):
for x, y in dataloader:
optimizer.zero_grad() # gradient'lerin kümülatif olmamasını sağlar
pred_y = mm(x)
lossval = mse_loss(pred_y.flatten(), y)
lossval.backward() # gradient vektörleri hesaplar
optimizer.step() # modelin w parametrelerini günceller
print(epoch, end=' ')
print()
predict_y = mm(scaled_test_tensor_x)
mae_loss = L1Loss()
mse = mse_loss(test_tensor_y, predict_y.flatten()).item()
print(f'Mean Squared Error: {mse}')
mae = mae_loss(test_tensor_y, predict_y.flatten()).item()
print(f'Mean Absolute Error: {mae}')
from sklearn.metrics import mean_squared_error, mean_absolute_error
mse = mean_squared_error(test_dataset_y, predict_y.detach().numpy())
print(f'Mean Squared Error: {mse}')
mae = mean_absolute_error(test_dataset_y, predict_y.detach().numpy())
print(f'Mean Absolute Error: {mae}')
#----------------------------------------------------------------------------------------------------------------------------
Biz yukarıdaki işlemlerde epoch'lar bittiğinde bir sınama (validation) işlemi yapmadık. Anımsanacağı gibi bu sınama işlemi
Keras'ta fit metodu tarafından otomatik yapılıyordu. PyTorch ve Tensorflow daha düşük seviyeli kütüphanelerdir. Bu işlemlerin
programcı tarafındna manuel bir biçimde yapılması gerekmektedir.
Sınama işlemleri için bizim sınama verilerini kendimizin oluşturması gerekir. Örneğin biz veri kümesini eğitim ve test olmak üzere
ikiye ayırdıktan sonra eğitim veri kümesini de yenidne ikiye ayırıp sınama kümesini oluşturabiliriz:
dataset = np.loadtxt('housing.csv', dtype='float32')
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
temp_dataset_x, test_dataset_x, temp_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.20)
training_dataset_x, validation_dataset_x, training_dataset_y, validation_dataset_y = train_test_split(temp_dataset_x, temp_dataset_y, test_size=0.20)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_validation_dataset_x = mms.transform(validation_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
Pekiyi bir epoch'taki sınama değeri nasıl elde edilmelidir? En normal durum epoch içerisindeki her batch'ten elde edilen
metirk değerin ortlamasının hesaplanmasıdır. Örneğin bir epoch'un 50 tane batch'ten oluştuğunu düşünelim. Bizim 50 batch'ten
tek tek elde ettiğimiz metrik değerlerin ortalamasını hesaplamamız gerekmektedir. Tabii her epoch'un metrik ortalamalarını da
bizim bir listede toplamamaız uygun olur. Çünkü epoch'lara metrik değerlerin grafiğini çizmek isteriz. Tabii Keras tüm bunları aynı
bu biçimde kendisi yapmaktadır. Keras'ta da aslında bir epoch sonucunda verilen metrik değerler o epoch'taki batch'lerden elde
edilen metrik değerlerin ortalamasıdır.
Şimdi sınama işleminin nasıl yapılması gerektiği üzerinde üzerinde duralım. Sınama işleminin eğitim ile birlikte değil epoch bittikten
sonra eğitimden bağımsız olarak yapılması gerekmektedir. Bu işlem PyTorch'ta iki adımda yürütülebilir:
1) Önce tıpkı eğitim verilerinde yapıldığı gibi sınama verileri için de ayrı DataLoader nesnesi oluşturulur.
2) Sonra ppoch döngüsünün içerisinde eğitim döngüsünün altında benzer bir sınama döngüsü oluşturulur. Tabii bu döngünün amacı eğitim yapmak
değil sınanama yapmaktır. Yani sınama verileriyle kestirim yapmaktır. Ancak model üzerinde sınama yapılırken loss fonksiyonları
da çalıştırılacaktır. İşte bu sırada model üzerinde bazı işlemlerin yapılmaması gerekir. PyTorch tarasımcıları modeli temsil eden Module
sınıfı için iki mod oluşturmuşlardır: train ve eval modları. Default durum train modudur. eval modunda modelin bazı kısımları disable edilmektedir.
Böylece modelin bozulması engellenmektedir. eval moduna geçmek için Module sınıfının eval metodu, tekrar train moduna geçmek için Module
sınıfının train metodu kullanılmaktadır. Ayrıca bu geçişi kolaylaştırmak için torch modülünde no_grad isimli bir fonksiyon bulundurulmuştur. Bu fonksiyon bize
bir bağlam yönetim nesnesi vermektedir. Bu nesnenin __enter__ metodu eval metodunu çağırmakta __exit__ metodu ise train metodunu çağırmaktadır.
with torch.no_grad():
pass
Pekiyi train ve eval modlarının anlamı nedir? İşte bazı işlemler eğitim sırasında yapılırken sınama sırasında yapılmamlıdır. Örneğin
modelde dropout işlemleri eğitim sırasında yapılması gereken ancak sınama sırasında yapılmaması gereken işlemlerdir. Ayrıca PyTorch'ta
tensörler için otomatik gradient hesapları yapılmaktadır. Bu hesaplar hem zaman alır hem de eğitim için bazı durumlarda çakışma
oluşturabilmektedir.
Aşağıda Boston Housing Price örneği PyTorch ile sınama işlemi de dahil edilerek gerçekleştirilmiştir. Burada sınama işlemi de
test işlemi de yine batch batch yapılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
EPOCHS = 100
BATCH_SIZE = 32
dataset = np.loadtxt('housing.csv', dtype='float32')
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
temp_dataset_x, test_dataset_x, temp_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.20)
training_dataset_x, validation_dataset_x, training_dataset_y, validation_dataset_y = train_test_split(temp_dataset_x, temp_dataset_y, test_size=0.20)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_validation_dataset_x = mms.transform(validation_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
import torch
scaled_training_tensor_x = torch.tensor(scaled_training_dataset_x)
training_tensor_y = torch.tensor(training_dataset_y)
scaled_validation_tensor_x = torch.tensor(scaled_validation_dataset_x)
validation_tensor_y = torch.tensor(validation_dataset_y)
scaled_test_tensor_x = torch.tensor(scaled_test_dataset_x)
test_tensor_y = torch.tensor(test_dataset_y)
from torch.utils.data import TensorDataset, DataLoader
training_dataset = TensorDataset(scaled_training_tensor_x, training_tensor_y)
training_dl = DataLoader(training_dataset, batch_size=BATCH_SIZE, shuffle=True)
validation_dataset = TensorDataset(scaled_validation_tensor_x, validation_tensor_y)
validation_dl = DataLoader(validation_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_dataset = TensorDataset(scaled_test_tensor_x, test_tensor_y)
test_dl = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=True)
from torch.nn import Linear, ReLU
from torch.nn import Module
class MyModule(Module):
def __init__(self, input_size):
super().__init__()
self.input_size = input_size
self.hidden1 = Linear(input_size, 64)
self.hidden2 = Linear(64, 64)
self.output = Linear(64, 1)
self.relu = ReLU()
def forward(self, x):
x = self.hidden1(x)
x = self.relu(x)
x = self.hidden2(x)
x = self.relu(x)
x = self.output(x)
return x
mm = MyModule(scaled_training_tensor_x.shape[1])
from torch.nn import MSELoss, L1Loss
from torch.optim import Adam
mse_loss = MSELoss()
optimizer = Adam(mm.parameters(), lr=0.001)
training_mse_list = []
validation_mse_list = []
training_count = np.ceil(len(training_dataset_x) / BATCH_SIZE)
validation_count = np.ceil(len(validation_dataset_x) / BATCH_SIZE)
test_count = np.ceil(len(test_dataset_x) / BATCH_SIZE)
for epoch in range(EPOCHS):
mm.train()
total_mse = 0
for x, y in training_dl:
optimizer.zero_grad() # gradient'lerin kümülatif olmamasını sağlar
pred_y = mm(x)
lossval = mse_loss(pred_y.flatten(), y)
total_mse += lossval.item()
lossval.backward() # gradient vektörleri hesaplar
optimizer.step() # modelin w parametrelerini günceller
training_mean_mse = total_mse / training_count
training_mse_list.append(training_mean_mse )
mm.eval()
total_mse = 0
for x, y in validation_dl:
pred_y = mm(x)
lossval = mse_loss(pred_y.flatten(), y)
total_mse += lossval.item()
validation_mean_mse = total_mse / validation_count
validation_mse_list.append(validation_mean_mse)
print(f'#{epoch}: loss: {training_mean_mse}, validation loss: {validation_mean_mse}')
import matplotlib.pyplot as plt
plt.plot(range(EPOCHS), training_mse_list)
plt.plot(range(EPOCHS), validation_mse_list)
plt.legend(['Training MSE', 'Validaition MSE'])
plt.show()
mae_loss = L1Loss()
total_mse = 0
total_mae = 0
for x, y in test_dl:
pred_y = mm(x)
lossval = mse_loss(pred_y.flatten(), y)
total_mse += lossval.item()
maeval = mae_loss(pred_y.flatten(), y)
total_mae += maeval.item()
test_mean_mse = total_mse / test_count
test_mean_mae = total_mae / test_count
print(f'Test Mean Squared Error: {test_mean_mse}')
print(f'Test Mean Aboslute Error: {test_mean_mae}')
"""
predict_y = mm(scaled_test_tensor_x)
mse = mse_loss(test_tensor_y, predict_y.flatten()).item()
print(f'Mean Squared Error: {mse}')
mae = mae_loss(test_tensor_y, predict_y.flatten()).item()
print(f'Mean Absolute Error: {mae}')
"""
#----------------------------------------------------------------------------------------------------------------------------
PyTorch'ta kestirim nasıl yapılmaktadır? Aslında kestirim yapmak oldukça kolaydır. Tek yapılacak şey model nesnesi ile
__call__ metodunu çağırmaktır.
Aşağıdaki örnekte model eğitildikten sonra kestirim işlemi de yapılmıştır:
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
EPOCHS = 100
BATCH_SIZE = 32
dataset = np.loadtxt('housing.csv', dtype='float32')
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
temp_dataset_x, test_dataset_x, temp_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.20)
training_dataset_x, validation_dataset_x, training_dataset_y, validation_dataset_y = train_test_split(temp_dataset_x, temp_dataset_y, test_size=0.20)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_validation_dataset_x = mms.transform(validation_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
import torch
scaled_training_tensor_x = torch.tensor(scaled_training_dataset_x)
training_tensor_y = torch.tensor(training_dataset_y)
scaled_validation_tensor_x = torch.tensor(scaled_validation_dataset_x)
validation_tensor_y = torch.tensor(validation_dataset_y)
scaled_test_tensor_x = torch.tensor(scaled_test_dataset_x)
test_tensor_y = torch.tensor(test_dataset_y)
from torch.utils.data import TensorDataset, DataLoader
training_dataset = TensorDataset(scaled_training_tensor_x, training_tensor_y)
training_dl = DataLoader(training_dataset, batch_size=BATCH_SIZE, shuffle=True)
validation_dataset = TensorDataset(scaled_validation_tensor_x, validation_tensor_y)
validation_dl = DataLoader(validation_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_dataset = TensorDataset(scaled_test_tensor_x, test_tensor_y)
test_dl = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=True)
from torch.nn import Linear, ReLU
from torch.nn import Module
class MyModule(Module):
def __init__(self, input_size):
super().__init__()
self.input_size = input_size
self.hidden1 = Linear(input_size, 64)
self.hidden2 = Linear(64, 64)
self.output = Linear(64, 1)
self.relu = ReLU()
def forward(self, x):
x = self.hidden1(x)
x = self.relu(x)
x = self.hidden2(x)
x = self.relu(x)
x = self.output(x)
return x
mm = MyModule(scaled_training_tensor_x.shape[1])
from torch.nn import MSELoss, L1Loss
from torch.optim import Adam
mse_loss = MSELoss()
optimizer = Adam(mm.parameters(), lr=0.001)
training_mse_list = []
validation_mse_list = []
training_count = np.ceil(len(training_dataset_x) / BATCH_SIZE)
validation_count = np.ceil(len(validation_dataset_x) / BATCH_SIZE)
test_count = np.ceil(len(test_dataset_x) / BATCH_SIZE)
for epoch in range(EPOCHS):
mm.train()
total_mse = 0
for x, y in training_dl:
optimizer.zero_grad() # gradient'lerin kümülatif olmamasını sağlar
pred_y = mm(x)
lossval = mse_loss(pred_y.flatten(), y)
total_mse += lossval.item()
lossval.backward() # gradient vektörleri hesaplar
optimizer.step() # modelin w parametrelerini günceller
training_mean_mse = total_mse / training_count
training_mse_list.append(training_mean_mse )
mm.eval()
total_mse = 0
for x, y in validation_dl:
pred_y = mm(x)
lossval = mse_loss(pred_y.flatten(), y)
total_mse += lossval.item()
validation_mean_mse = total_mse / validation_count
validation_mse_list.append(validation_mean_mse)
print(f'#{epoch}: loss: {training_mean_mse}, validation loss: {validation_mean_mse}')
import matplotlib.pyplot as plt
plt.plot(range(EPOCHS), training_mse_list)
plt.plot(range(EPOCHS), validation_mse_list)
plt.legend(['Training MSE', 'Validaition MSE'])
plt.show()
mae_loss = L1Loss()
total_mse = 0
total_mae = 0
for x, y in test_dl:
pred_y = mm(x)
lossval = mse_loss(pred_y.flatten(), y)
total_mse += lossval.item()
maeval = mae_loss(pred_y.flatten(), y)
total_mae += maeval.item()
test_mean_mse = total_mse / test_count
test_mean_mae = total_mae / test_count
print(f'Test Mean Squared Error: {test_mean_mse}')
print(f'Test Mean Aboslute Error: {test_mean_mae}')
"""
predict_y = mm(scaled_test_tensor_x)
mse = mse_loss(test_tensor_y, predict_y.flatten()).item()
print(f'Mean Squared Error: {mse}')
mae = mae_loss(test_tensor_y, predict_y.flatten()).item()
print(f'Mean Absolute Error: {mae}')
"""
import numpy as np
predict_data = np.array([[0.11747, 12.50, 7.870, 0, 0.5240, 6.0090, 82.90, 6.2267, 5, 311.0, 15.20, 396.90, 13.27]])
scaled_predict_tensor = torch.tensor(mms.transform(predict_data), dtype=torch.float32)
predict_result = mm(scaled_predict_tensor).item()
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
Tabii biz Module sınıfınından sınıf türetmeden de aynı işlemleri hazır Sequential isimli modül sınıfıyla da yapabiliriz.
Aşağıda buna örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
EPOCHS = 100
BATCH_SIZE = 32
dataset = np.loadtxt('housing.csv', dtype='float32')
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
temp_dataset_x, test_dataset_x, temp_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.20)
training_dataset_x, validation_dataset_x, training_dataset_y, validation_dataset_y = train_test_split(temp_dataset_x, temp_dataset_y, test_size=0.20)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_validation_dataset_x = mms.transform(validation_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
import torch
scaled_training_tensor_x = torch.tensor(scaled_training_dataset_x)
training_tensor_y = torch.tensor(training_dataset_y)
scaled_validation_tensor_x = torch.tensor(scaled_validation_dataset_x)
validation_tensor_y = torch.tensor(validation_dataset_y)
scaled_test_tensor_x = torch.tensor(scaled_test_dataset_x)
test_tensor_y = torch.tensor(test_dataset_y)
from torch.utils.data import TensorDataset, DataLoader
training_dataset = TensorDataset(scaled_training_tensor_x, training_tensor_y)
training_dl = DataLoader(training_dataset, batch_size=BATCH_SIZE, shuffle=True)
validation_dataset = TensorDataset(scaled_validation_tensor_x, validation_tensor_y)
validation_dl = DataLoader(validation_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_dataset = TensorDataset(scaled_test_tensor_x, test_tensor_y)
test_dl = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=True)
from torch.nn import Linear, ReLU, Sequential
mm = Sequential(Linear(dataset_x.shape[1], 64), ReLU(), Linear(64, 64), ReLU(), Linear(64, 1))
from torch.nn import MSELoss, L1Loss
from torch.optim import Adam
mse_loss = MSELoss()
optimizer = Adam(mm.parameters(), lr=0.001)
training_mse_list = []
validation_mse_list = []
training_count = np.ceil(len(training_dataset_x) / BATCH_SIZE)
validation_count = np.ceil(len(validation_dataset_x) / BATCH_SIZE)
test_count = np.ceil(len(test_dataset_x) / BATCH_SIZE)
for epoch in range(EPOCHS):
mm.train()
total_mse = 0
for x, y in training_dl:
optimizer.zero_grad() # gradient'lerin kümülatif olmamasını sağlar
pred_y = mm(x)
lossval = mse_loss(pred_y.flatten(), y)
total_mse += lossval.item()
lossval.backward() # gradient vektörleri hesaplar
optimizer.step() # modelin w parametrelerini günceller
training_mean_mse = total_mse / training_count
training_mse_list.append(training_mean_mse )
mm.eval()
total_mse = 0
for x, y in validation_dl:
pred_y = mm(x)
lossval = mse_loss(pred_y.flatten(), y)
total_mse += lossval.item()
validation_mean_mse = total_mse / validation_count
validation_mse_list.append(validation_mean_mse)
print(f'#{epoch}: loss: {training_mean_mse}, validation loss: {validation_mean_mse}')
import matplotlib.pyplot as plt
plt.plot(range(EPOCHS), training_mse_list)
plt.plot(range(EPOCHS), validation_mse_list)
plt.legend(['Training MSE', 'Validaition MSE'])
plt.show()
mae_loss = L1Loss()
total_mse = 0
total_mae = 0
for x, y in test_dl:
pred_y = mm(x)
lossval = mse_loss(pred_y.flatten(), y)
total_mse += lossval.item()
maeval = mae_loss(pred_y.flatten(), y)
total_mae += maeval.item()
test_mean_mse = total_mse / test_count
test_mean_mae = total_mae / test_count
print(f'Test Mean Squared Error: {test_mean_mse}')
print(f'Test Mean Aboslute Error: {test_mean_mae}')
"""
predict_y = mm(scaled_test_tensor_x)
mse = mse_loss(test_tensor_y, predict_y.flatten()).item()
print(f'Mean Squared Error: {mse}')
mae = mae_loss(test_tensor_y, predict_y.flatten()).item()
print(f'Mean Absolute Error: {mae}')
"""
import numpy as np
predict_data = np.array([[0.11747, 12.50, 7.870, 0, 0.5240, 6.0090, 82.90, 6.2267, 5, 311.0, 15.20, 396.90, 13.27]])
scaled_predict_tensor = torch.tensor(mms.transform(predict_data), dtype=torch.float32)
predict_result = mm(scaled_predict_tensor).item()
print(predict_result)
#----------------------------------------------------------------------------------------------------------------------------
Şimdi ikili sınıflandırma işlemi için kestirim yapalım. Aşağıda daha önce üzerinde çalışmış olduğumuz "breast cancer" verileri
üzerinde ikili sınıflandırma örneği verilmiştir. İkili sınıflandırma problemleri için kullanılacak loss fonksiyonun genel
olarak "binary cross entropy" alındığına dikkat ediniz. PyTorch'ta bu loss fonksiyonu BCELoss sınıfı ile temsil edilmiştir.
Accuracy ölçümü basit bir biçimde manuel ya da sckit-learn kullanılarak yapılabilir. Ancak biz burada PyTorch ekosistemi
içerisindeki torchmetrics isimli pakette bulunan Accuracy sınıfınından faydalandık. torchmetrics paketinin ayrıca aşağıdaki
gibi yüklenmesi gerekmektedir:
pip install torchmetrics
#----------------------------------------------------------------------------------------------------------------------------
EPOCHS = 200
BATCH_SIZE = 32
import pandas as pd
dataset_df = pd.read_csv('breast-cancer-data.csv')
dataset_x = dataset_df.iloc[:, 2:-1].to_numpy(dtype='float32')
dataset_y = (dataset_df['diagnosis'] == 'M').to_numpy(dtype='float32')
from sklearn.model_selection import train_test_split
temp_dataset_x, test_dataset_x, temp_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.15)
training_dataset_x, validation_dataset_x, training_dataset_y, validation_dataset_y = train_test_split(temp_dataset_x, temp_dataset_y, test_size=0.15)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(training_dataset_x)
scaled_training_dataset_x = mms.transform(training_dataset_x)
scaled_validation_dataset_x = mms.transform(validation_dataset_x)
scaled_test_dataset_x = mms.transform(test_dataset_x)
import torch
scaled_training_tensor_x = torch.tensor(scaled_training_dataset_x)
training_tensor_y = torch.tensor(training_dataset_y)
scaled_validation_tensor_x = torch.tensor(scaled_validation_dataset_x)
validation_tensor_y = torch.tensor(validation_dataset_y)
scaled_test_tensor_x = torch.tensor(scaled_test_dataset_x)
test_tensor_y = torch.tensor(test_dataset_y)
from torch.utils.data import TensorDataset, DataLoader
training_dataset = TensorDataset(scaled_training_tensor_x, training_tensor_y)
training_dl = DataLoader(training_dataset, batch_size=BATCH_SIZE, shuffle=True)
validation_dataset = TensorDataset(scaled_validation_tensor_x, validation_tensor_y)
validation_dl = DataLoader(validation_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_dataset = TensorDataset(scaled_test_tensor_x, test_tensor_y)
test_dl = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=True)
from torch.nn import Linear, ReLU, Sigmoid, Sequential
model = Sequential(Linear(dataset_x.shape[1], 64), ReLU(), Linear(64, 64), ReLU(), Linear(64, 1), Sigmoid())
from torch.nn import BCELoss, L1Loss
from torch.optim import Adam
from torchmetrics import Accuracy
import numpy as np
bce_loss = BCELoss()
optimizer = Adam(model.parameters(), lr=0.001)
training_bce_list = []
validation_bce_list = []
training_count = np.ceil(len(training_dataset_x) / BATCH_SIZE)
validation_count = np.ceil(len(validation_dataset_x) / BATCH_SIZE)
test_count = np.ceil(len(test_dataset_x) / BATCH_SIZE)
for epoch in range(EPOCHS):
model.train()
total_bce = 0
for x, y in training_dl:
optimizer.zero_grad() # gradient'lerin kümülatif olmamasını sağlar
pred_y = model(x)
lossval = bce_loss(pred_y.flatten(), y)
total_bce += lossval.item()
lossval.backward() # gradient vektörleri hesaplar
optimizer.step() # modelin w parametrelerini günceller
training_mean_bce = total_bce / training_count
training_bce_list.append(training_mean_bce )
model.eval()
total_mse = 0
for x, y in validation_dl:
pred_y = model(x)
lossval = bce_loss(pred_y.flatten(), y)
total_mse += lossval.item()
validation_mean_bce = total_mse / validation_count
validation_bce_list.append(validation_mean_bce)
print(f'#{epoch}: loss: {training_mean_bce}, validation loss: {validation_mean_bce}')
import matplotlib.pyplot as plt
plt.plot(range(EPOCHS), training_bce_list)
plt.plot(range(EPOCHS), validation_bce_list)
plt.legend(['Training MSE', 'Validaition MSE'])
plt.show()
accuracy = L1Loss()
accuracy = Accuracy(task='binary')
total_bce = 0
total_accuracy = 0
for x, y in test_dl:
pred_y = model(x)
lossval = bce_loss(pred_y.flatten(), y)
total_bce += lossval.item()
accuracy.update(pred_y.flatten(), y)
total_accuracy += accuracy.compute()
test_mean_bce = total_bce / test_count
test_mean_accuracy = total_accuracy / len(test_dl)
print(f'Test Mean BCE Loss: {test_mean_bce}')
print(f'Test Accuracy: {test_mean_accuracy}')
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de evrişim içeren bir resim sınıflandırma örneği verelim. CIFAR-10 veri kümesini daha önce birkaç kez kullanmıştık Bu veri
kümesinde her biri 32x32'lik RGB resimler bulunuyordu. Bu resimler aşağıdaki nesnelerden birine ilişkindir:
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
Bu tür veri kümeleri torchvision içerisinde hazır bir biçimde bulunmaktadır. Gerçekten de torchvision.datasets içerisindeki CIFAR10 sınıfı
bu verileri indirerek bize Dataset nesnesi biçiminde veriyordu.
Aşağıdaki örnekte kritik birkaç nokta üzerinde durmak istiyoruz:
1) Bu örnekte evrişim katmanı kullanılmamıştır. Linear katmanlar iki boyutlu tensörlerle çalışmaktadır. Halbuki CIFAR10 sınıfı ile bize
verilen resimler 32X32X3 boyutlarındadır. Bu nedenle biz forward metodunun başında bunu Linear katmanına uygun biçimde iki boyut haline getirdik.
2) Softmax sınıfı yine toplamları 1 olan örneğimizde 10 tane değerleri bie vermektedir. Yani ağımızın çıktısı her batch işleminde
BATCH_SIZE X 10 biçiminde bir tensörü bize vermektedir.
3) loss fonksiyonu olarak CrossEntropyLoss alınmıştır. Bu Keras'taki "categorical_crossentropy" loss fonksiyonuna karşılık gelmektedir. loss
fonksiyonu çağrılırken softmax değerlerini ve label değerlerini parametre olarak almaktadır.
4) Eğitim sırasında validation yapılmamıştır. Test işlemi yine CIFAR10 sınıfının bize verdiği test dataset nesnesi yoluyla yapılmıştır. Test işlemi sırasında
her batch işlemindeki isabet sayıları toplanıp toplam test veri sayısına bölünmüştür.
#----------------------------------------------------------------------------------------------------------------------------
BATCH_SIZE = 32
EPOCHS = 5
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
from torchvision.datasets import CIFAR10
from torchvision.transforms import Compose, Normalize, ToTensor
compose = Compose([ToTensor(), Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
from torchvision.transforms import ToTensor
training_dataset = CIFAR10(root='cifar10x-data', train=True, download=True, transform=compose)
test_dataset = CIFAR10(root='cifar10-data', train=False, download=True, transform=compose)
from torch.utils.data import DataLoader
training_dl = DataLoader(training_dataset, batch_size=BATCH_SIZE)
test_dl = DataLoader(test_dataset, batch_size=BATCH_SIZE)
from torch.nn import Module, Linear, ReLU, Softmax, CrossEntropyLoss
class CifarModule(Module):
def __init__(self):
super().__init__()
self.linear1 = Linear(32 * 32 * 3, 128)
self.linear2 = Linear(128, 128)
self.linear3 = Linear(128, 10)
self.relu = ReLU()
self.softmax = Softmax(dim=1)
def forward(self, x):
x = x.view(x.shape[0], -1) # x = torch.flatten(x, 1)
x = self.linear1(x)
x = self.relu(x)
x = self.linear2(x)
x = self.relu(x)
x = self.linear3(x)
x = self.softmax(x)
return x
from torch.optim import Adam
import torch
cm = CifarModule()
loss = CrossEntropyLoss()
optimizer = Adam(cm.parameters())
epoch_losses = []
for epoch in range(EPOCHS):
losses = []
for x, y in training_dl:
optimizer.zero_grad()
pred_y = cm(x)
lossval = loss(pred_y.type(torch.float32), y)
lossval.backward()
optimizer.step()
losses.append(lossval.item())
mean_epoch_loss = sum(losses) / len(losses)
epoch_losses.append(mean_epoch_loss)
print(f'Epoch #{epoch}, MSELoss: {mean_epoch_loss}')
total = 0
for x, y in test_dl:
pred_y = cm(x)
result = pred_y.argmax(dim=1)
total += torch.sum(result == y)
accuracy = total / len(test_dataset)
#----------------------------------------------------------------------------------------------------------------------------
Aslında biz torchvision içerisindeki CIFAR10 sınıfını kullanmak yerine verilerin Dataset sınıfı haline getirilmesini numpy ve
sklearn kütüphanelerini kullanarak da yapabilirdik. Aşağıda bu işlemler uygulanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
BATCH_SIZE = 32
EPOCHS = 5
import numpy as np
training_dataset = np.loadtxt('cifar10-train.csv', delimiter=',', skiprows=1, dtype=np.uint8)
training_dataset_x = training_dataset[:, :-1]
training_dataset_y = training_dataset[:, -1]
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
training_dataset_x = mms.fit_transform(training_dataset_x).astype('float32')
import torch
training_dataset_x = torch.from_numpy(training_dataset_x)
training_dataset_y = torch.from_numpy(training_dataset_y)
from torch.utils.data import TensorDataset, DataLoader
training_dataset = TensorDataset(training_dataset_x, training_dataset_y)
training_dl = DataLoader(training_dataset, batch_size=BATCH_SIZE)
from torch.nn import Module, Linear, ReLU, Softmax, CrossEntropyLoss
class CifarModule(Module):
def __init__(self):
super().__init__()
self.linear1 = Linear(32 * 32 * 3, 128)
self.linear2 = Linear(128, 128)
self.linear3 = Linear(128, 10)
self.relu = ReLU()
self.softmax = Softmax(dim=1)
def forward(self, x):
x = x.view(x.shape[0], -1) # x = torch.flatten(x, 1)
x = self.linear1(x)
x = self.relu(x)
x = self.linear2(x)
x = self.relu(x)
x = self.linear3(x)
x = self.softmax(x)
return x
from torch.optim import Adam
import torch
cm = CifarModule()
loss = CrossEntropyLoss()
optimizer = Adam(cm.parameters())
epoch_losses = []
for epoch in range(EPOCHS):
losses = []
for x, y in training_dl:
optimizer.zero_grad()
pred_y = cm(x)
lossval = loss(pred_y.type(torch.float32), y)
lossval.backward()
optimizer.step()
losses.append(lossval.item())
mean_epoch_loss = sum(losses) / len(losses)
epoch_losses.append(mean_epoch_loss)
print(f'Epoch #{epoch}, MSELoss: {mean_epoch_loss}')
#----------------------------------------------------------------------------------------------------------------------------
CIFAR1-10 örneğini şimdi de evrişim katmanlarını kullanarak gerçekleştirelim.
Aşağıdaki örnekte iki evrişim katmanı kullanılmıştır. Pytorch'ta evrişim katmanı Conv2D sınıfyla temsil edilmiştir. Bu sınıfın
__init__ metodunun ilk parametresi resimdeki "channel sayısını" belirtir. Eğer resim RGB ise ilk evrişim katmanında bu ilk parametre 3 olarak girilmelidir.
Metodun ikinci parametresi kullanılacak filtre sayısını ve üçüncü parametresi kullanılacak filtrenin boyutlarını (kernel size) belirtmektedir.
Bu üçüncü parametre iki elemanlı bir demet olarak girilebilir ya da tek bir değer olarak girilebilir. Tek değer olarak girilirse en ve boy aynı olmak
üzere bu değerde olur. Metodun dördüncü parametresi yine kaydırma değerini belirten stride parametresidir. Bu parametrenin default değeri 1'dir.
Aşağıdaki örnekte sınama işlemi yapılmamıştır.
#----------------------------------------------------------------------------------------------------------------------------
EPOCHS = 20
BATCH_SIZE = 32
import pickle
import glob
x_lst = []
y_lst = []
for path in glob.glob('cifar-10-batches-py/data_batch_*'):
with open(path, 'rb') as f:
d = pickle.load(f, encoding='bytes')
x_lst.append(d[b'data'])
y_lst.append(d[b'labels'])
import numpy as np
training_dataset_x = np.concatenate(x_lst)
training_dataset_y = np.concatenate(y_lst)
with open('cifar-10-batches-py/test_batch', 'rb') as f:
d = pickle.load(f, encoding='bytes')
test_dataset_x = d[b'data']
test_dataset_y = d[b'labels']
training_dataset_x = training_dataset_x / 255
test_dataset_x = test_dataset_x / 255
training_dataset_x = training_dataset_x.reshape(-1, 3, 32, 32)
from tensorflow.keras.utils import to_categorical
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
import torch
from torch.utils.data import TensorDataset, DataLoader
training_dataset_tensor_x = torch.tensor(training_dataset_x, dtype=torch.float32)
training_dataset_tensor_y = torch.tensor(ohe_training_dataset_y, dtype=torch.float32)
training_dataset = TensorDataset(training_dataset_tensor_x, training_dataset_tensor_y)
training_dl = DataLoader(training_dataset, batch_size=BATCH_SIZE)
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
from torch.nn import Sequential, Conv2d, Linear, ReLU, Flatten, Softmax, Module
class MyModule(Module):
def __init__(self):
super().__init__()
self.layer1 = Conv2d(3, 32, kernel_size=(3, 3), padding=1)
self.layer2 = Conv2d(32, 64, kernel_size=(3, 3), padding=1)
self.layer3 = Flatten()
self.layer4 = Linear(65536, 10)
self.layer5 = Softmax(dim=1)
self.relu = ReLU()
def forward(self, x):
#print(x.shape)
x = self.layer1(x)
#print(x.shape)
#x = self.relu(x)
#print(x.shape)
x = self.layer2(x)
#print(x.shape)
x = self.relu(x)
#print(x.shape)
x = self.layer3(x)
#print(x.shape)
x = self.layer4(x)
#print(x.shape)
x = self.layer5(x)
#print(x.shape)
return x
model = MyModule()
"""
model = Sequential(
Conv2d(3, 32, kernel_size=(3, 3), padding=1),
ReLU(),
self.layer2 = Conv2d(32, 64, kernel_size=(3, 3), padding=1),
ReLU(),
Flatten(),
Linear(84480, 10),
Softmax(dim=1)
)
"""
from torch.optim import Adam
from torch.nn import CrossEntropyLoss
optimizer = Adam(model.parameters(), lr=0.001)
ce_loss = CrossEntropyLoss()
training_ce_list = []
training_count = np.ceil(len(training_dataset_x) / BATCH_SIZE)
for epoch in range(EPOCHS):
model.train()
total_ce = 0
count = 0
for x, y in training_dl:
optimizer.zero_grad() # gradient'lerin kümülatif olmamasını sağlar
pred_y = model(x)
lossval = ce_loss(pred_y.flatten(), y.flatten())
total_ce += lossval.item()
lossval.backward() # gradient vektörleri hesaplar
optimizer.step() # modelin w parametrelerini günceller
count += 1
print(f'\x1b[1JEpoch {epoch + 1}: {int(count/training_count* 100)}%', end='')
training_mean_ce = total_ce / training_count
training_ce_list.append(training_mean_ce)
print(f'#{epoch}: loss: {training_mean_ce}')
import matplotlib.pyplot as plt
import glob
for path in glob.glob('test-images/*.jpg'):
image_data = plt.imread(path)
image_data = image_data / 255
image_data = image_data.reshape(1, 32, 32, 3)
image_data = np.transpose(image_data, [0, 3, 1, 2])
image_tensor_data = torch.tensor(image_data, dtype=torch.float32)
predict_result = model(image_tensor_data)
result = predict_result.argmax()
print(f'{path}: {class_names[result]}')
#----------------------------------------------------------------------------------------------------------------------------
TensorFlow kütüphanesi Google öncülüğünde açık kaynak kodlu biçimde oluşturulmuş bir kütüphanedir. Makine öğrenmesi ile ilgili
pek çok yüksek seviyeli kütüphane TensorFlow kullanılarka yazılmış durumdadır. Tensorflow kütüphanesinin ilk sürümü 2015 yoılında
oluşturulmuştur. Ancak kütüphanenin 2'li verisyonlarıyla birlikte önemli tasarım değişiklikleri göze çarpmaktadır. Kütüphanenin 1'li versiyonları
nispeten uzun süredir kullanılan versiyonlarıdır. 2'li versiyonlar ise nispeten yenidir.
Kütüphanenin 1'li versiyonları metaprogramlama tekniğini kullanıyordu. Yani bu 1'li versiyonlarda önce yapılacak işlemler belirtiliyor.
Böylece bir graf oluşturuluyor sonra bu graf çalıştırılıyordu. Ancak bu biçimdeki çalışma sistemi programcılar için tanıdık değildi ve
bu biçimdeki kullanım zordu. Ancak kütüphanein 2'li versyionlarıyla birlikte bu graf oluşturma modeli "isteğe bağlı" hale getirildi.
Bu yeni çalışma modeli "eager execution" biçiminde isimlendirildi.
Kütüphanenin 1'li versiyonlarında kullanılan graf sistemi optimizasyon bakımından faydalar sağlayabilmektedir. Ancak yukarıda da
belirttiğimiz gibi zor bir kullanıma yol açmaktadır. Kütüphanein 2'li versiyonları model olarak PyTorch'a oldukça benzemektedir.
Eskiden Keras kütüphanesi TenserFlow ile yazılmış yüksek seviyeli bir kütüphane idi. Ancak daha sonra 2'li versiyonlarla Keras TenserFlow
kütüphanesinin kendi bünyesine katıldı. Dolayısıyla eskiden tamamen Kers olmadan TensorFlow ile işlem yapan uygulamacılar artık işin büyük kısmında
Keras kullanmaya başladılar. Tabii Keras neticede TensorFlow kullanılarak yazıldığı için Keras ile TensorFlow kütüphanelerinin bir uyumu
vardır. Programcılar artık yüksek seviyeli pek çok şeyi Keras'ta yapıp bazı durumlarda TensorFlow kütüphanesinin tensör olanaklarından
faydalanmaktadır.
TensorFlow kütüphanesine yeni başlayanlar artık eski graf sistemi yerine bu kütüphanenin 2'li versiyonlarının sunduğu özellikleri öğrenmelidir.
Ancak eski graf sistemi yine de işlemlerin daha hızlı yapılmasını sağlamak amacıyla programcılar tarafından kullanılabilmektedir.
Biz daha önce Keras kullanarak sinir ağlarını oluşturmuştuk. Keras modelini oluşturduğumuzda bunların eğitilmesi ve test işlemlerinde
NumPy dizilerini kullandık. Aslında Keras kütüphanesi kendi içerisinde TensorFlow kullandığı için işlemleri NumPy dizileri ile değil
TensorFlow kütüphanesinin Tensor nesneleriyle yapmaktadır. Dolayısıyla biz Keras'ta çeşitli metotlara (fit gibi, evalutae gibi, predict gibi)
NumPy dizileri verdiğimizde aslında Keras bunları Tensor nesnelerine dönüştürüp işlemlerini yapmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------
TensorFlow kütüphanesinde de tıpkı PyTorch'ta olduğu gibi ana veri yapısı Tensor denilen nesnelerdir. Tensor nesneleri NumPy dizilerine
benzemekle birlikta yapay sinir ağları için özel bir biçimde tasarlanmıştır ve paralel programlamaya olanak sağlamaktadır.
#------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------
Tensorflow kütüphanesinin son versiyonu aşağıdaki gibi kurulabilir:
pip install tensorflow
Kütüphane genellikle tf ismiyle aşağıdaki gibi import edilmektedir:
import tensoflow as tf
#------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------
Tensör yaratmanın en yaygın yollarından biri tf.constant fonksiyonunun kullanılmasıdır. Yaratım yapılırken dtype belirtilebilir.
Eğer dtype belirtilmezse default olarak dtype türü tamsayılar için tf.int32, noktalı sayılar için tf.float32 biçiminde alınmaktadır.
TensorFlow kütüphanesinde dtype türü için NumPy dtype türleri değil TensorFlow içerisindeki dtype türleri
kullanılmaktadır. Bu dtype türleri de yine C Programlama Dilindeki türlerden oluşturulmuştur. Zaten TensorFlow kütüphanesi
büyük ölçüde C/C++ kullanılarka yazılmış durumdadır. Tensorflow dtype türleri şunlardır:
tf.float32
tf.float64
tf.int8
tf.int16
tf.int32
tf.int64
tf.uint8
tf.string
tf.bool
dtype türleri yine istenirse isimsel biçimde de belirtilebilmektedir. Örneğin:
import tensorflow as tf
t = tf.constant([[1, 2, 3], [4, 5, 6]], dtype='float32')
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=tf.int32)
print(t)
#------------------------------------------------------------------------------------------------------------------
Tabii dtype yine numpy kütüphanesinde olduğu gibi isimsel biçimde verilebilir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype='float32')
print(t)
#------------------------------------------------------------------------------------------------------------------
tf.constant fonksiyonunda dolaşılabilir nesneden belli bir boyutta tensör nesnesi de oluşturulabilir. Bunun için
shape parametresi kullanılmaktadır.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant([1, 2, 3, 4, 5, 6, 7, 8], shape=(2, 4), dtype='float32')
print(t)
#------------------------------------------------------------------------------------------------------------------
tf.constant fonksiyonunda girilen listenin ya da demetin boyutu ne olursa olsun biz shape parametresi yoluyla onun
boyutlarını ayarlayabiliriz. Örneğin:
t = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]] , shape=(9, ), dtype='float32')
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]], shape=(9, ), dtype='float32')
print(t)
#------------------------------------------------------------------------------------------------------------------
NumPy'da olduğu gibi skaler değerlerin boyutsal bir özellikleri yoktur. Yani bunlar için shape=() biçimndedir.
Örneğin:
t = tf.constant(10 , dtype='float32')
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant(10, dtype='float32')
print(t)
#------------------------------------------------------------------------------------------------------------------
Tabii biz istersek bir NumPy dizisinden de tensöt yaratabiliriz. Bu durumda tensör nesnesinin dtype türü NumPy dizisinden
alınacaktır. Örneğin:
a = np.array([1, 2, 3, 4, 5], dtype=np.float32)
t = tf.constant(a)
#------------------------------------------------------------------------------------------------------------------
#------------------------------------------------------------------------------------------------------------------
tensör yaratmanın diğer bir yolu da tf.convert_to_tensor fonksiyonunu kullanmaktır. Buradan da yine "constant" bir
tensör elde edilmektedir. Örneğin:
t = tf.convert_to_tensor([1, 2, 3, 4, 5], dtype=tf.float32)
print(t2)
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
import numpy as np
a = np.random.random((5, 5))
t = tf.convert_to_tensor(a)
print(t)
#------------------------------------------------------------------------------------------------------------------
tf.zeros fonksiyonu tıpkı np.zeros fonksiyonu gibi içi 0 ile dolu olan bir tensör oluşturmaktadır. dtype da belirtilebilir.
Ancak default dtype=tf.float32'dir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.zeros((5, 5))
print(t)
#------------------------------------------------------------------------------------------------------------------
tf.ones fonksiyonu da np.ones gibi içi 1'lerden oluşan bir tensör yaratmaktadır. dtype da belirtilebilir.
Ancak default dtype=tf.float32'dir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.ones((5, 5))
print(t)
#------------------------------------------------------------------------------------------------------------------
tf.fill fonksiyonu da np.fill fonksiyonuna benzemektedir. Belli bir değerden bir tensör oluşturmaktadır.
Bu fonksiyonun dtype parametresi yoktur. dtype doldurulacak değerin türünden hareketle belirlenmektedir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.fill((5, 5), 10.)
print(t)
#------------------------------------------------------------------------------------------------------------------
tf.range isim olarak Python'daki range fonksiyonuna benzese de işlev olarak Numpy'daki np.arange fonksiyonuna
benzemektedir. Belli bir aralıkta değerlerden oluşan tensor yaratır.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.range(20)
print(t)
t = tf.range(10, 20, 0.2)
print(t)
#------------------------------------------------------------------------------------------------------------------
tf.linspace fonksiyonu da Numpy'daki np.linspace fonksiyonuna benzemektedir. Ancak dtype parametresi almamaktadır.
default durumda dtype=tf.float64 alınmaktadır.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.linspace(-10, 10, 50)
print(t)
#------------------------------------------------------------------------------------------------------------------
Bir tensör nesnesinin dtype bilgisi dtype isimli örnek özniteliği ile, shape bilgisi ise shape isimli örnek
özniteliği ile elde edilebilir. shape örnek özniteliği bize boyut bilgisini TensorShape isimli bir sınıf türünden
vermektedir. TensorShape bir tensör nesnesi değildir. Biz shape örnek özniteliği ile boyutları aldıktan sonra bu
TensorShpape sınıfının [...] operatörü ile belli bir boyutun uzunluğunu elde edebiliriz.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant([1, 2, 3, 4, 5], dtype=tf.float32)
print(t.dtype)
print(t.shape)
#------------------------------------------------------------------------------------------------------------------
Bir tensörün boyut bilgisi istenirse tf.shape fonksiyonuyla da elde edilebilir. shape örnek özniteliğinden farklı olarak
tf.shape fonksiyonu boyut bilgisini bize Tensor nesnesi olarak vermektedir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant([1, 2, 3, 4, 5], dtype=tf.float32)
shape = tf.shape(t)
print(shape)
#------------------------------------------------------------------------------------------------------------------
tf.rank fonksiyonu tensörün boyut sayısını bize vermektedir. Böyle bir örnek özniteliği versiyonu yoktur.
Boyut sayısı bir tensör nesnesi olarak verilmektedir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant([1, 2, 3, 4, 5], dtype=tf.float32)
rank = tf.rank(t)
print(repr(rank))
#------------------------------------------------------------------------------------------------------------------
skaler değerlerin rank'leri 0'dır.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant(10, dtype=tf.float32)
rank = tf.rank(t)
print(repr(rank))
#------------------------------------------------------------------------------------------------------------------
Bir tensörün boyutsal bilgisini değiştirmek için tf.reshape fonksiyonu kullanılmaktadır. Numpy'daki gibi bir reshape
metodu yoktur.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant([[1, 2, 3, 4], [5, 6, 7, 8]], dtype=tf.float32)
k = tf.reshape(t, shape=(2, 4))
print(t)
print(k)
#------------------------------------------------------------------------------------------------------------------
tf.reshape fonksiyonuna örnek
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.reshape(tf.range(30), (6, 5))
print(t)
#------------------------------------------------------------------------------------------------------------------
Bir tensör nesnesinin elemanlarına köşeli parantez operatörüyle erişilebilir. Bu durumda elde edilen değer yine bir
tensör nesnesi olmaktadır. Tensör nesneleri üzerinde tıpkı Numpy'da olduğu gibi dilimlemeler yapılabilir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.reshape(tf.range(30), (6, 5))
k = t[1, 0]
print(k)
k = t[2:4, 1:3]
print(k)
#------------------------------------------------------------------------------------------------------------------
tf.size fonksiyonu ile tensördeki toplam eleman sayısı bir tensör nesnesi biçiminde elde edilebilir. size fonksiyonun da
örnek özniteliği ya da metot karşılığı yoktur. Bu tensor nesnesinin içerisindeki değer numpy() metodu ile düze bir sayı
olarak elde edilebilir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.reshape(tf.range(30), (6, 5))
size = tf.size(t)
print(size)
print(size.numpy())
#------------------------------------------------------------------------------------------------------------------
Bir tensör nesnesinin içerisindeki değrler numpy metodu ile numpy dizisi biçiminde elde edilebilir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.reshape(tf.range(30), (6, 5))
a = t.numpy()
print(t)
#------------------------------------------------------------------------------------------------------------------
Anımsanacağı gibi Tensorflow'da temel olarak içeriği değiştirilemeyen ve içeriği değiştirilebilen biçimde iki çeşit tensör
nesneleri bulunuyordu. Biz tf.constant fonksiyonuyla ya da tf.convert_to_tensor fonksiyonuyla tensor yarattığımız zaman bu
içeriği değiştirilemeyen tensör oluşturuyordu. İçeriği değiştirilebilen tensörler fonksiyonla değil tf.Variable isimli bir
sınıf ile yaratılmaktadır. İçeriği değiştirilen tensörler tipik olarak sinir ağlarındaki W ve bias değerleri için kullanılmaktadır.
Eğitim sırasında bu tensör nesneleri kendi içlerinde tıpkı PyTorch'ta olduğu gibi gradient değerleri tutup bunların güncellenmesine
olanak sağlamaktadır.
tf.Variable nesnesi tf.Variable sınıfının __init__ metoduyla yaraılabilir. Yine yaratım sırasında tensöre ilkdeğerleri
bir liste biçiminde ya da NumPy dizisi biçiminde verilebilmektedir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.Variable([1, 2, 3, 4, 5], dtype=tf.float32)
print(t)
#------------------------------------------------------------------------------------------------------------------
Variable sınıfıyla yaratılan tensör nesnelerine sınıfın assign isimli metoduyla değer atanabilir. Ancak atanacak değerin
aynı shape özelliğine sahip olması gerekir. Yani tensörün bir kısmı değil hepsi değiştirilmektedir. assign metodu ile
atanacak olanan değer assign metoduna bir Python listesi, NumPy dizisi biçiminde verilebilir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.Variable([[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype=tf.float32)
print(t)
t.assign([[10, 20, 30], [40, 50, 60], [70, 80, 90]])
print(t)
#------------------------------------------------------------------------------------------------------------------
assign metodu ile atanacak olanan değer assign metoduna bir Python listesi, NumPy dizisi biçiminde verilebilir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
import numpy as np
vt = tf.Variable([[1, 2, 3], [4, 5, 6], [7, 8, 9]], shape=(3, 3), dtype=tf.float32)
print(vt)
a = np.random.randint(0, 100, (3, 3))
vt.assign(a)
print(vt)
#------------------------------------------------------------------------------------------------------------------
assign metodu ile atama yapılırken atanacak değer bir tensör nesnesi de olabilir. Ancak atama sırasında dtype
türlerinin uyuşması gerekmektedir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
import numpy as np
vt = tf.Variable([[1, 2, 3], [4, 5, 6], [7, 8, 9]], shape=(3, 3), dtype=tf.float32)
print(vt)
ct = tf.constant(np.random.randint(0, 100, (3, 3)), dtype=tf.float32)
vt.assign(ct)
print(vt)
#------------------------------------------------------------------------------------------------------------------
Variable nesnesi de bir skaler biçiminde olabilir. Biz bu skaler nesneye yine assign metodu ile değer atayabiliriz.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
v = tf.Variable(1)
print(v)
v.assign(2)
print(v)
#------------------------------------------------------------------------------------------------------------------
Variable tensör içerisindeki değeri toplayarak ve çıkartarak atayabiliriz. Bunlar için assign_add ve assign_sum metotları
kullanılmaktadır.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
vt = tf.Variable([1, 2, 3, 4, 5], dtype=tf.float32)
vt.assign_add([10, 20, 30, 40, 50])
print(vt)
#------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte de skaler bir Variable tensör nesnesi üzerinde assign_add ve assign_sub metotları kullanılmıştır.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
v = tf.Variable(1)
print(v)
v.assign_add(10)
print(v)
v.assign_sub(3)
print(v)
#------------------------------------------------------------------------------------------------------------------
Rassal sayılardan oluşan tensörler elde etmek için tf.random modülündeki fonksiyonlar kullanılmaktadır. Örneğin
tf.random.uniform fonksiyonu iki aralık içerisinde belli boyutlarda rastgele sayılardan oluşan tensör yaratmaktadır.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.random.uniform((5, 5), 0, 10)
print(t)
t = tf.random.uniform((5, 5), 0, 10, dtype=tf.int32)
print(t)
#------------------------------------------------------------------------------------------------------------------
tf.random.normal fonksiyonu belli bir ortalama ve standart sapmaya ilişkin belli boyutta rassal değerlerden oluşan
tensör oluşturmaktadır.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.random.normal((5, 5), 0, 3)
print(t)
#------------------------------------------------------------------------------------------------------------------
tf.random.shuffle fonksiyonu bizden bir tensör alır, onu karıştırarak yeni bir tensör verir. Karıştırma işlemi
her zaman ilk eksene göre yapılmaktadır. (Yani bir matris karıştılacaksa satırlar yer değiştirilmektedir.)
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant([1, 2, 3, 4, 5])
print(t)
k = tf.random.shuffle(t)
print(k)
t = tf.random.uniform((5, 5), 0, 10, dtype=tf.int32)
print(t)
k = tf.random.shuffle(t)
print(k)
#------------------------------------------------------------------------------------------------------------------
Tensör sınıflarında temel operatörlere ilişkin operatör metotları yazılmış durumdadır. Dolayısıyla biz iki tensörü
artimetik işlemlere sokabiliriz. Bu durumda tıpkı Numpy'da olduğu gibi karşılıklı elemanlar işleme sokulur.
Ancak işleme sokulacak tensörlerin dtype türlerinin aynı olması gerekmektedir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
a = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = tf.constant([[1, 3, 5], [2, 4, 6], [1, 2, 3]])
c = a + b
print(c)
c = a * b
print(c)
#------------------------------------------------------------------------------------------------------------------
Yine aritmetik işlemlerde Numpy'da olduğu gibi "broadcasting" uygulanmaktadır.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
a = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = tf.constant([1, 3, 5])
c = a + b
print(c)
#------------------------------------------------------------------------------------------------------------------
Matrisel çarpım tıpkı Numpy'da olduğu gibi matmul fonksiyonuyla ya da @ operatörüyle yapılmaktadır.
Tabii tf.matmul fonksiyonunda iki boyutlu matrislerin uygun şekillerde olması gerekmektedir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
a = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = tf.constant([[1], [3], [5]])
c = tf.matmul(a, b)
print(c)
b = tf.constant([[1, 2, 3]])
c = tf.matmul(b, a)
print(c)
#------------------------------------------------------------------------------------------------------------------
Tabii iki tensör tıpkı Numpy'da olduğu gibi karşılaştırma operatörleriyle de işlemlere sokulabilir. Bu durumda
bool bir vektör elde edilir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
a = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = tf.constant([[4, 5, 6], [7, 3, 2], [4, 9, 8]])
c = a > b
print(c)
c = a == b
print(c)
#------------------------------------------------------------------------------------------------------------------
Yine Tensorflow'da da bool indeksleme yapılabilmektedir. Bu sayede biz matris değerlerini filtreleyebiliriz.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
a = tf.constant([8, 12, 43, 23, 19])
result = a > 20
b = a[result]
print(b)
#------------------------------------------------------------------------------------------------------------------
Matematiksel işlemlerin önemli bölümü tf.math modülündeki fonksiyonlarla yapılmaktadır. Bu modüldeki reduce_xxx biçiminde
isimlendirilmiş olan fonksiyonlar axis parametresi almaktadır. Buradaki axis parametresi Numpy'daki axis parametresiyle
aynı anlama sahiptir. Başı reduce öneki ile başlamayan fonksiyonlar axis parametresi almamaktadır. Örneğin reduce_sum,
reduce_mean, reduce_std, reduce_min, reduce_max axis parametresi alab önemli fonksiyonlardır.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
a = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(a)
b = tf.math.reduce_sum(a, axis=0) # np.sum(a, axis=0)
print(b)
b = tf.math.reduce_sum(a, axis=1) # np.sum(a, axis=1)
print(b)
b = tf.reduce_mean(a, axis=0) # no.mean(a, axis=0)
print(b)
#------------------------------------------------------------------------------------------------------------------
tf.math modülündeki bazı fonksiyonlar ilgili operatör metotlarının fonksiyon biçimleridir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
a = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(a)
b = tf.constant([[3, 2, 1], [7, 4, 6], [1, 3, 5]])
print(a)
c = tf.add(a, b) # c = a + b
print(c)
c = tf.multiply(a, b) # c = a * b
print(c)
#------------------------------------------------------------------------------------------------------------------
dot product işlemi için birkaç benzer fonksiyon bulundurulmuştur. tf.tensordat fonksiyonu iki tensör üzerinde dot product
işlemi yapar. Fonksiyondaki axes parametresi tipik olarak 1 biçiminde geçilir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
x = tf.constant([1, 2, 3, 4, 5], dtype=tf.float32)
y = tf.constant([1, 2, 3, 4, 5], dtype=tf.float32)
z = tf.tensordot(x, y, axes=1)
print(z)
#------------------------------------------------------------------------------------------------------------------
tf.math modülünde segment_xxx biçiminde isimlendirilmiş olan fonksiyonlar tensörü segment'teki aynı değerler dikkate alınarak
blümlere ayırıp o bölümleri işleme sokmaktadır. Bölümleme 0'dan başlayarak artırımlı bir bir biçimde yapılmaktadır.
Örneğin:
t = tf.constant([1, 2, 3, 4, 5, 6, 7, 8])
segment = tf.constant([0, 0, 1, 1, 1, 2, 2, 2])
Burada 0'lara karşılık gelen indekslerdeki elemanlar ayrı bir bölümü 1'lere karşılık gelen indeksteki elemanlar ayrı bir bölümü
ve 2'lere karşılık gelen indekslerdeki elemanlar ise ayrı bir bölümü oluşturmaktadır. Dolayısıyla şu bölümler elde edilmiştir:
1 2
3 4 5
6 7 8
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant([1, 2, 3, 4, 5, 6, 7, 8])
segment = tf.constant([0, 0, 1, 1, 1, 2, 2, 2])
result = tf.math.segment_sum(t, segment)
print(result)
#------------------------------------------------------------------------------------------------------------------
segment'li işlemlere örnek
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant([1, 2, 3, 4, 5, 6, 7, 8])
segment = tf.constant([1, 1, 1, 2, 2, 2, 3, 3])
result = tf.math.segment_sum(t, segment)
print(result)
#------------------------------------------------------------------------------------------------------------------
Çok boyutlu diziler üzerinde de segment'li işlemler yapılabilir
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant([[1, 2, 3], [4, 5, 6], [7, 8, 9], [3, 2, 1]])
segment = tf.constant([0, 0, 1, 1])
result = tf.math.segment_sum(t, segment)
print(result)
#------------------------------------------------------------------------------------------------------------------
segment_xxx fonksiyonlarında bölüm belirten numaraların sırayı dizilmiş bir biçimde bulunması gerekir. Ancak eğer bölüm belirten
numaralar sıraya dizilmiş değilse unsorted_segment_xxx fonksiyonları kullanılmalıdır. Ancak bu fonksiyonların ayrıca num_segments
isimli zorunlu bir parametresi de vardır.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant([1, 2, 3, 4, 5, 6, 7, 8])
segment = tf.constant([0, 0, 1, 1, 1, 2, 1, 0])
result = tf.math.unsorted_segment_sum(t, segment, num_segments=3)
print(result)
#------------------------------------------------------------------------------------------------------------------
unsorted_segment_xxx fonksiyonlarınun kullanımına bir örnek
#------------------------------------------------------------------------------------------------------------------
,import tensorflow as tf
t = tf.constant([1, 2, 3, 4, 5, 6, 7, 8])
segment = tf.constant([2, 2, 2, 1, 1, 1, 3, 3])
result = tf.math.unsorted_segment_sum(t, segment, num_segments=5)
print(result)
#------------------------------------------------------------------------------------------------------------------
math modülündeki argmax ve argmin NumPy'daki gibi en büyük ve en küçük elemanların kendilerini değil indeks numaralarını
elde etmektedir.
#------------------------------------------------------------------------------------------------------------------
import tensorflow as tf
t = tf.constant([10, 21, 32, 42, 5, 6, 72, 8])
result = tf.argmax(t)
print(result)
result = tf.argmin(t)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Yapay zeka ve makine öğrenmesi uygulamaları için çeşitli kurumlar tarafından cloud temelli hizmetler sunulmaktadır. Bu cloud
hizmetlerinin en yaygın kullanılanları şunlardır:
- Goodle Cloud Platform (Vertex AI)
- Amazon Web Services (Sage Maker)
- Microsodt Azure
- IBM Watson
Bu servislerin hepsinin ortak birtakım özellikleri ve amaçları vardır:
- Bu platformlar bize CPU ve bellek sağlamaktadır. Dolayısıyla bizim makine öğrenmesi işlemleri için ayrı bir makine tahsis
etmemize gerek kalmaz. Pek çok modelin eğitimi günlerce sürebilmektedir. Bunun için makinenin evde tutulması uygun olamayabilir.
- Bu platformlar "ölçeklenebilir (scalable)" çözümler sunmaktadır. Yani kiralanan birimler büyütülük küçültülebilmektedir.
- Bu platformlar "deployment" için kullanılabilmektedir. Yani burada eğitilen modellerle ilgili işlemler Web API'leriyle
uzaktan yapılabilmektedir. (Örneğin biz makine öğrenmesi uygulamasını buralarda konuşlandırabiliriz. predict işlemlerini
cep telefonumuzdaki uygulamalardan yapabiliriz. Böylece uygulamamız mobil aygıtlardan da web tabanlı olarak kullanılabilir
hale gelmektedir.)
- Bu platformlar kendi içerisinde "Automated ML" araçlarını da bulundurmaktadır. Dolayısıyla aslında konunun teorisini bilmeyen
kişiler de bu Automated ML araçlarını kullanarak işlemlerini yapabilmektedir.
Yukarıdaki platformlar (IBM Watson dışındaki) aslında çok genel amaçlı platformlardır. Yani platformlarda pek çok değişik hizmet de
verilmektedir. Bu platformalara "yapay ze makine öğrenmesi" unsurları son 10 senedir eklenmiş durumdadır. Yani bu platformlardaki
yapay zeka ve makine öğrenmesi kısımşları bu platformların birer alt sistemi gibidir. Bu platformların pek çok ayrıntısı olduğunu
hatta bunlar için sertifikasyon sınavlarının yapıldığını belirtmek istiyoruz.
Tabii yukarıdaki platformlar ticari platformlardır. Yani kullanım için ücret ödenmektedir. Ücret ödemesi "kullanım miktarı ile"
ilişkilidir. Yani ne kadar kullanılırsa o kadar ücret ödenmektedir. (Bu bakımdan modellerin eğitimini unutursanız, bu eğitimler
bu platformun kaynaklarını kullandığı için ücretlendirilecektir. Denemeler yaparken bu tür hesaplamaları durdurduğunuzdan emin
olmalısınız.) Tabii bu platformlarda da birtakım işlemler bedava yapılabilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bütün cloud sistemlerinde makine öğrenmesi işlemleri yapılırken birbirleriyle ilişkili üç etkinlik yürütülür:
Data + Model + Hesaplama
Üzerinde çalışacağımız veriler genellikle bu cloud sistemlerinde onların bu iş için ayrılan bir servisi yoluyla upload
edilir. Model manuel ya da otomatik bir biçimde oluşturulmaktadır. Cloud sistemleri kendi içerisindeki dağıtık bilgisayar
sistemleri yoluyla model üzerinde eğitim, kestirim gibi işlemler yapmamıza olanak vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Google Cloud Platform (kısaca GCP) Amazon AWS ve Microsoft Azure platformlarının doğrudan rekabetçisi konumundadır.
GCP 2008de kurulmuştur. Aslında diğer platformlarda olan servislerin tamamen benzeri GCPde bulunmaktadır.
GCPye erişmek için bir Google hesabının açılmış olması gerekir.
GCPnin ana sayfası şöyeldir:
https://cloud.google.com/
GCP işlemlerini yapabilmek için kontrol panele (konsol ortamına) girmek gerekir. Kontrol panel adresi de şöyledir:
https://console.cloud.google.com
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
GCPde tüm işlemler bir proje eşliğinde yapılmaktadır. Çünkü işletmeler değişik projeler için değişik hizmetler alabilmektedir.
Projenin yaratımı hemen konsole sayfasından yapılabilmektedir. Projeyi yarattıktan sonra aktif hale getirmek (select etmek) gerekir.
Proje aktif hale geldiğinde proje sayfasına geçilmiş olur. Tabii proje yaratmak için bizim Google'a kredi kartımızı vermiş olmamız
gerekir. Yukarıda da belirttiğimiz gibi biz kredi kartını vermiş olsak bile Google kullanım kadar para çekmektedir.
Projenin "dashboard" denilen ana bir sayfası vardır. Burada projeye ilişkin pek çok özet bilgi ve hızlı erişim bağlantıları
bulunmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
GCPnin -tıpkı diğer platformlarda olduğu gibi- “CPU + RAM” kiralaması yapan ve “Compute Engine” denilen bir servisi vardır.
Benzer biçimde yine veritablarını host etmek için ve birtakım dosyaları saklamak için kullanılabilecek “Cloud Storage” hizmeti
bulunmaktadır.
GCP içerisinde birtakım servislerin erişebileceği bir storage alanına gereksim duyulmaktadır. Bunun için “Cloud Storage”
hizmetini seçmek gerekir. Ancak Google bu noktada sınırlı bedava bir hizmet verecek olsa da kredi kartı bilgilerini istemektedir.
Tıpkı AWSde olduğu gibi burada da “bucket” kavramı kullanılmıştır. Kullanıcının önce bir “bucket yaratması” gerekmektedir.
Bucket adeta cloud alanı için bize ayrılmış bir disk ya da klasör gibi düşünülebilir. Dosyalar bucket'lerin içerisinde bulunmaktadır.
Bucket yaratılması sırasında yine diğerlerinde olduğu gibi bazı sorular sorulmaktadır. Örneğin verilere hangi bölgeden erişileceği,
verilere hangi sıklıkta erişileceği gibi. Buckete verilecek isim yine AWSde olduğu gibi GCP genelinde tek (unique) olmak zorundadır.
Bir bucket yaratıldıktan sonra artık biz yerel makinemizdeki dosyaları bucket'e aupload edebiliriz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aslında GCP üzerinde işlem yapmak için çeşitli komut satırı araçları da bulundurulmuştur. Biz bu komut satırı araçlarını
yerel makinemize install edip işlemleri hiç Web arayüzünü kullanmadan bu araçlarla da yapabilmekteyiz. Bu araçlar bizim
istediğimiz komutları bir script biçiminde de çalıştırabilmektedir. Aslında bu komut satırı araçları "Cloud Shell" ismiyle
Web tabalı olarak uzak makinede de çalıştırılabilmektedir.
Yerel makinemize aşağıdkai bağlantıyı kullanarak gsutil programını kurabiliriz:
https://cloud.google.com/storage/docs/gsutil_install
Örneğin gsutil programı ile yerel makinemizdeki "cvid.csv" dosyasını GCP'deki bucket'imiz içerisine şöyle kopyalayabiliriz:
gsutil cp covid.csv gs:/kaanaslan-test-bucket
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
GCP içerisindeki Auto ML aracına "Vertex AI" denilmektedir. Vertex AI aracına erişmek için GCP kontrol panelindeki ana menüyü
kullanabilirisniz. Vertex AI'ın ana kontrol sayfasına "Dashboard" denilmektedir. Dolayısıyla bizim Dashboard'a geçmemiz gerekir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Tipik olarak Vertex-AI'da işlemn yapma aşamaları şöyledir:
1) Veri kümesi bucket içerisine upload edilir. (Bu işlem Dataset oluşturulurken de yapılabilmektedir.)
2) Dataset oluşturulur.
3) Eğitim işlemi yapılır
4) Deployment ve Test işlemleri yapılır
5) Kestirim işlemleri yapılır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Vertex AI'da ilk yapılacak şey bir "Dataset" yaratmaktır. Bunun için Vertex AI sayfasındaki "Datasets" sekmesi seçilir.
Buradan Create düğmesine basılır. Burada Dataset için bölge seçilir. (Bu bölgenin bucket ile aynı bölgede olması gerekmez
ancak aynı bölgede olması daa uygundur.) Dataset'e bir isim verilir. Sonra problemin türü seçilir. Bir CSV dosyasından hareketle
kestirim yapacaksak "Tabular" sekmesinden "Regression/Classification" seçilmelidir. Daytaset yaratıldıktan sonra artık bu dataset'in
bir CSV dosyası ilişkilendirilmesi gerekmektedir. Ancak Vertex AI backet'teki CSV dosyalarını kullanabilmektedir. Burada üç seçenek
bulunmaktadır:
* Upload CSV files from your computer
* Select CSV files from Cloud Storage
* Select a table or view from BigQuery
Biz yerel bilgiyasarımızdaki bir CSV dosyasını seçersek zaten bu CSV dosyası önce bucket içerisine kopyalanmaktadır.
Eğer zaten CSV dosyasımız bir bucket içerisindeyse doğrudan bucket içerisindeki CSV dosyasını belirtebiliriz. BigQuery
GCP içerisindeki veritabanı biçiminde organize edilmiş olan başka bir depolama birimidir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Dataset oluşturulduktan sonra artık eğitim işlemine geçilebilir. Bunun için Vertex AI içerisindeki "Traning" sekmesi kullanılmaktadır.
Training sayfasına geçildiğinde "Create" düğmesi ile eğitim belirlemelerinin yapıldığı bölüme geçilebilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Training işleminde peşi sıra birkaç aşamadan geçilmektedir. "Traingng method" aşamasında bize hangi veri kümesi için eğitim
yapılacağı ve problemin bir "sınıflandırma problemi mi yoksa lojistik olmayan regresyon problemi mi" olduğu sorulur. Bundan sonra
"Model details" aşamasına geçilir. Bu aşamada bize veri kümesindeki kestirilecek sütunun hangisi olduğu sorulmaktadır.
Bu aşamada "Advanced Options" düğmesine basıldığında test ve sınama verilerinin miktarları belirlenebilmektedir. Default durumda
test verileri ve sınama verileri veri kümesinin %10'u biçiminde alınmaktadır. "Join featurestore" aşamasından doğrudan "Continue"
ile geçilebilir. Bundan sonra karşımıza "Training options" aşaması gelecektir. Burada eğitimde hangi sütunların kullanılacağı bize
sorulmaktadır. Yine bu aşamada da "Advanced Options" seçeneği vardır. Burada bize Loss fonksiyonu sorulmatadır. Tabii bunlar default
değerlerle geçilebilir. En sonunda "Compute and pricing" aşamasına gelinir. Burada dikkat etmek gerekir. Çünkü Google eğitimde harcanan
zamanı ücretlendirmektedir. Google'ın ücretlendirme yöntemi aşağıdaki bağlantıdan imncelenebilir:
https://cloud.google.com/vertex-ai/pricing
Burada "Budget" eğitim için maksimum ne kadar zaman ayrılacağını belirtmektedir. Klasik tabular verilerde en az zaman 1
saat olarak, resim sınıflandırma gibi işlemlerde en az zaman 3 olarak girilebilmektedir.
En sonunda "Start Training" ile eğitim başlatılır. Eğitimler uzun sürebildiği için bitiminde e-posta ile bildirm yapılmaktadır.
Eğitim bittikten sonra biz eğitim hakkında bilgileri Training sekmesinden ilgili eğitimin üzerine tıklayarak görebiliriz.
Eğer problem lojistik olmayan regresyon problemi ise modelin başarısı çeşitli metrik değerlerle gösterilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Kesitirim işlemlerinin yapılabilmesi için önce modelin "deploy edilmesi ve bir endpoint oluşturulması" gerekmektedir.
Modelin deploy edilmesi demek cloud sistemi içerisinde dışarıdan kullanıma hazır hale getirilmesi demektir. Böylece biz
kestirimi uzaktan programlama yoluyla da yapabiliriz. Deployment işlemi "Prediction" sekmesinden girilerek yapılabileceği gibi
"Model Registry" sekmesninden de yapılabilmektedir. EndPoint yaratımı sırasında bize Endpoint için bir isim sorulmaktadır. Sonra
model için bir isim verilmekte ve ona bir versiyon numarası atanmaktadır. Buradaki "Minimum number of compute nodes" ne kadar yüksek
tutulursa erişim o kadar hızlı yapılmaktadır. Ancak node'ların sayısı doğrudna ücretlendirmeyi etkilemektedir. Dolayısıyla burada
en düşük sayı olan 1 değerini girebilirsiniz. Daha sonra bize modelin deploy edileceği makinenin özellikleri sorulmaktadır.
Burada eğitimin başka bir makinede yapıldığına ancak sonucun kestirilmesi için başka bir makinenin kullanıldığına dikkat ediniz.
Modelimiz deploy edildikten sonra kullanım miktarı kadar ücretlendirme yapılmaktadır. Dolaysıyla denemelerinizden sonra
bu deployment işlemini silebilirsiniz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Deployment işleminden sonra artık predict işlemi yapılabilir. Bu işlem tamamen görsel arayüzle yapılabileceği gibi
Web API'leriyle ya da bunları kullanan Python koduyla da yapılabilmektedir. Eğer deploy edilmiş modelde kestirim
işlemini programlama yoluyla yapacaksanız bunun için öncelikle aşağıdaki paketi kurmanız gerekmektedir:
pip install google-cloud-aiplatform
Bundan sonra aşağıdaki import işlemini yapıp modüldeki init fonksiyonunun uygun parametrelerle çağrılması gerekmeketdir:
from google.cloud import aiplatform
aiplatform.init(....)
predict işlemi için Training sekmesinden Deploy & Test sekmesini kullanmak gerekir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Birden fazla predict işlemi "batch predict" denilen sekme ile yapılmaktadır. Uygulamacı kestirim için yine bir CSV dosyası
oluşturur. Bu CSV dosyasına bucket'e upload eder. Sonra "Batch predict" sekmesinden bu CSV dosyasına referans ederek
işlemi başlatır. Sonuçlar yine bu işlem sırasında belirlenen bucket'ler içerisinde CSV dosyaları biçiminde oluşturulmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de Vertex AI ile resim sınıflandırma işlkemi yapalım. Resim sınıflandırma gibi bir işlem şu aşamalardan geçilerek
gerçekleştirilmektedir:
1) Resimler Google cloud'ta bir bucket'e upload edilir.
2) Resimler bir CSV dosyası haline getirilir. Tabii burada resmin içerisindeki data'lar değil onun bucket'teki yeri
kullanılmaktadır.
3) Bu CSV dosyasından hareketle Dataset oluşturulur.
4) Training işlemi yapılır.
5) Deployment ve EndPoint ataması yapılır
6) Kestirim işlemi görsel atayüz yoluyla ya da WEB API'leri ya da Pythonkoduyla yapılır.
Burada Dataset oluşturulurken bizden bir CSV dosyası istenmektedir. Bu CSV dosyası aşağıdaki gibi bir formatta oluşturulmalıdır:
dosyanın_bucketteki_yeri,sınıfı
dosyanın_bucketteki_yeri,sınıfı
dosyanın_bucketteki_yeri,sınıfı
dosyanın_bucketteki_yeri,sınıfı
Örneğin:
gs://kaanaslan-test-bucket/ShoeVsSandalVsBootDataset/Boot/boot (1).jpg,boot
gs://kaanaslan-test-bucket/ShoeVsSandalVsBootDataset/Boot/boot (10).jpg,boot
gs://kaanaslan-test-bucket/ShoeVsSandalVsBootDataset/Boot/boot (100).jpg,boot
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Metin sınıflandırmaları da nemzer biçmde yapılabilmektedir. Burada iki seçenek söz konusudur. Metinler ayrı dosyalarda
bulunudurulup dosyalar bucket içerisine upload edilebilir yine resim sınıflandırma örneğinde olduğu gibi CSV dosyası
metinlere ilişkin dosyalardan ve onların sınıflarından oluşturulabilir. Ya da doğrudan metinlerin kendisi ve onların sınıfları da
CSV dosyasının içerisinde bulunabilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Amazon firması Cloud paltformlarına ilk giren büyük firmalardandır. Amazon'un cloud platformuna AWS (Amazon Web Services)
denilmektedir. AWS iki yüzün üzerinde servis barındıran dev bir platformdur. Platformun pek çok ayrıntısı vardır. Bu nedenle
platformun öğrenilmesi ayrı bir uzmanlık alanı haline gelmiştir. Biz kurusumuzda platformun yapay zeka ve makine öğrenmesi
için nasıl kullanılacağı üzerinde özet bir biçimde duracağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
AWS ortamında makine öğrenmesi etkinlikleri işleyiş olarak aslında daha önce görmüş olduğumuz Google Cloud Platform'a
oldukça benzemektedir. Google Cloud Platform'daki "Vertex AI" servisinin Amazonda'ki mantıksal karşılığı "SageMaker"
isimli servistir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
AWS'nin ana kontrol paneline aşağıdaki bağlantı ile erişilebilir:
console.aws.amazon.com
Tabii AWS hizmeti almak için yine bir kayıt aşaması gerekmektedir. AWS kaydı sırasında işlemlker için bizden kredi kartı
bilgileri istenmektedir. Ancak AWS diğerlerinde olduğu gibi "kullanılan kadar paranın ödendiği" bir platformdur.
AWS'nin konsol ekranına giriş yapıldığında zaten bize son kullandığmız servisleri listelemektedir. Ancak ilk kez giriş
yapıyorsanız menüden "SageMaker" servisini seçmelisiniz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
SageMaker'a geçildiğinde onun da bir "dash board" denilen kontrol paneli vardır. Burada biz notebook'lar yaratıp uzaktan
manuel işlemler yapabiliriz. Ancak SageMaker'ın Auto ML aracına "AutoPilot" denilmektedir. SageMaker'ı görsel olarak daha
zahmetsiz kullanabilmek için ismine "Studio" denilen Web tabanlı bir IDE geliştirilmiştir. Son yıllarda "Google'ın collab'ına"
benzer "Studio Lab" denilen bedava bir ortam da eklenemiştir. Kullanıcılar genellikle işlemlerini bu Studio IDE'siyle yapmaktadır.
SageMaker içerisinde "Studio"ya geçebilmek için en az bir "kullanıcı profilinin (user profile)" yaratılmış olması gerekir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
AWS'nin veri saklamak için çeşitli servisleri vardır. Makine öğrenmesiş için en önemli depolama servisi S3 denilen servistir.
S3 servisinde de tıpkı GCP'de olduğu gibi "bucket" adı altında bir çeşit folder'lar oluşturulmaktadır. Sonra bu bucket'lere
dosyalar upload edilmektedir. Amazon "veri merkezlerini (data centers)" "bölge (zone)" denilen alnlarla bölümlere ayırmıştır.
Server'lar bu bölgelerin içerisindeki veri merkezlerinin içerisinde bulunmaktadır. Tıpkı GCP'de olduğu her bölgede her türlü
servis verilmeyebilmetedir. Kullanıcılar coğrafi bakımdan kendilerine yakın bölgeri seçerlerse erişim daha hızlı olabilmektedir.
Bir bucket yaratmak için ona "dünya genelinde tek olan (unique)" bir isim vermek gerekir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
SageMaker Studio'da Auto-ML etkinlikleri için Autopilot denilen uygulama kullanılmaktadır. Dolayısıyla Auto-ML işlemi için
AutoML seçilebilir. Autopilot'ta bir Auto-ML çalışması yapmak için bir "experiment" oluşturmak gerekir. Experiment
oluşturabilmek için "File/New/Create AutoML Experiment" seçilebilir ya da doğrudan Auto ML (Autopilot) penceresinde de
"Create Autopilot Experiment" seçilebilir. Yeni bir experiment yaratılırken bize onun ismi ve CSV dostasının bucket'teki
yeri sorulmaktadır. Sonra Next tuşuna basılarak bazı gerekli öğeler belirlenir. Örneğin tahmin edilecek hedef sütun ve
kestirimde kullanılacak sütunlar bu aşamada bize sorulmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Eğitim işlemi bittiğinde veri dosyanın bulunduğu bucket içerisinde bir klasör yaratılır ve bu klasör içerisinde model ile
ilgili çeşitli dosyalar bulundurulur. Buradaki iki dosya önemlidir:
SageMakerAutopilotDataExplorationNotebook.ipynb
SageMakerAutopilotCandidateDefinitionNotebook.ipynb
Buradaki "SageMakerAutopilotDataExplorationNotebook.ipynb" dosyası içerisinde veriler hakkında istatistiksel bşrtakım özellikler
raporlanır. "SageMakerAutopilotCandidateDefinitionNotebook.ipynb" dosyasının içerisinde ise Autopilot'ın bulduğu en iyi modellerin
nasıl işleme sokulacağına ilişkin açıklamalar bulunmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Kestirim yapabilmek için EndPoint oluşturulmuş olması gerekmektedir. Tıpkı GCP'de olduğu gibi modelin çalıştırılabilmesi için
bir server'a deploy edilmesi egrekmektedir. Deploy işlemi sonucunda bize bir EndPoint verilir. Biz de bu EndPoint'i kullanarak
Web arayüzü ile ya da Python programı ile uzaktan kestirimde bulunabiliriz.
AWS'de uzaktan Python ile işlem yapabilmek için "sagemaker" ve "boto3" gibi kütüphaneler oluşturulmuştur. Kütüphaneler şöyle yüklenebilir.
pip install sagemaker
pip install boto3
sagemaker kütüphanesi Web arayüzü ile yapılanları programlama yoluyla yapabilmekt için boto3 kütüphanesi ise uzaktan
kesitirm (prediction) gibi işlemleri yapabilmek için kullanılmaktadır.
sagemaker kütüphanesi ile uzaktan işlemlerin yapılması kütüphanenin dokümantasyonlarında açıklanmıştır. Aşağıdaki
bağlantıyı kullanarak kodlar üzerinde değişiklikler yaparak ve kodlarda ilgili yerleri doldurarak uzaktan işlemler yapabilirsiniz:
https://sagemaker.readthedocs.io/en/stable/overview.html
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
boto3 kütüphanesi ile uzaktan işlemler yapılırken önce bir Session nesnesinin yaratılması gerekmektedir. Sessin nesnesi yaratılırken
bizim AWS kaynaklarına ulaşabilmemiz için iki parola bilgisine sahip olmamız gerekir. Birincisi “aws_access_key_id” ve
ikincisi de “aws_secret_access_key”. Amazon servisleri uzaktan erişimler için “public key/private key” kriptografi uygulamaktadır.
Bu parola bilgileri Session nesnesi yaratılırken aşağıdaki verilebilir:
import boto3
session = boto3.Session(aws_access_key_id=XXXXX', aws_secret_access_key='YYYYY')
Session nesnesi yaratıldıktan sonra hangi servisin kullanılacağını belirten bir kaynak nesnesi yaratılır. Örneğin:
s3 = session.resource('s3')
Aslında bu kaynak nesneleri session nesnesi yaratılmdan doğrudan da yaratılabilmektedir. Ancak parolaların bu durumda
“~/.aws/credentials” dosyasına aşağıdaki formatta yazılması gerekir:
[default]
aws_access_key_id = YOUR_ACCESS_KEY
aws_secret_access_key = YOUR_SECRET_KEY
Burada yukarıdaki iki anahtarı elde etme işlemi sırasıyla şu adımlarla yapılmaktadır:
1) https://console.aws.amazon.com/iam/ Adresinden IAM işlemlerine gelinir.
2) Users sekmesi seçilir
3) Kullanıcı ismi seçilir
4) "Security credentials" sekmesi seçilir.
5) Buradan Create Acces Key seçilir.
İşlemler sırasında eğer yukarıdaki anahtarlar girilmek istenmiyorsa (bu amahtarların görülmesi istenmeyebilir) yukarıda da belirttiğimiz
gibi bu anahtarlar özel bir dosyanın içerisine yazılabilir. Oradan otomatik alınabilir. Eğer bu anahtarlar ilgili dosyanın
içerisine yazılmışsa Session nesnesi yaratılırken parametre bu iki anahtarı girmemize gerek kalmaz. Örneğin:
session = boto3.Session()
Bu dosya bu bilgiler Amazon'un komut satırından çalışan aws programıyla da girilebilmektedir. Amazon'un komut satırından çalışan aws programını aşağıdaki
bağlantıdan inmdirerek kurabilirsiniz:
https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
Bu iki anahtarı ilgili dosyaya yazmak için aws programı şöyle kullanılabilir:
aws configure
Biz programlama yoluyla uzaktan bu bucket işlemlerini yapabiliriz. Örneğin tüm bucket'lerin isimleri aşağıdaki gibi elde edilebilir:
for bucket in s3.buckets.all():
print(bucket.name)
Belli bir bucket'teki dosya aşağıdaki gibi download edilebilmektedir:
s3 = boto3.client('s3')
s3.download_file('kaanaslan-test-bucket', 'x.txt', 'y.txt')
Burada söz konusu bucket içerisindeki "x.txt" dosyası "y.txt" biçiminde download edilmiştir.
Uzaktan predict işlemi yine boto3 kütüphanesi ile yapılabilmektedir. Aşağıda buna bir ilişkin bir örnek verilmiştir:
import boto3
session = boto3.Session(aws_access_key_id='AKIAWMMTXFTMCYOF352A',aws_secret_access_key='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', region_name='eu-central-1')
predict_data = '''6,148,72,35,0,33.6,0.627,50
1,85,66,29,0,26.6,0.351,31'
'''
client = session.client('runtime.sagemaker')
response = client.invoke_endpoint(EndpointName='diabetes-test', ContentType='text/csv', Accept='text/csv', Body=predict_data)
result = response['Body'].read().decode()
print(result)
Burada Session sınıfının client metodu kullanılarak bir sagemaker nesnesi elde edilmiştir. Sonra bu nesne üzerinde invoke_endpoint
metodu çağrılmıştır. Tabii arka planda aslında işlemler Web Servisleriyle yürütülmektedir. Bu boto3 kütüphanesi bu işlemleri kendi
içerisinde yapmaktadır. Gelen mesajdaki Body kısmının elde edilip yazdırıldığında dikkat ediniz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Microsoft Azure 2009 yılında kurulan bir bulut sistemidir. 2014 yılında Microsoft bu Azure sistemine yapay zeka ve makine
öğrenmesine ilişkin servisleri eklemiştir. Microsoft Azure daha önce görmüş olduğumuz Google Cloud Platform ve Amazon AWS
sistemine benzetilebilir. Benzer hizmetler Azure üzerinde de mevcuttur. Azure ML de hiç kod yazmadan fare hareketleriyle
ve Auto MLaraçlarıyla kullanılabilmektedir. Tıpkı Google Cloud Platform ve Amazon SageMakerda olduğu gibi bir SDK eşliğinde
tüm yapılan görsel işlemler programlama yoluyla da yapılabilmektedir. Microsoft Azure ML için tıpkı GCP ve AWSde olduğu gibi
sertifikasyon süreçleri oluşturmuştur. Bu konuda sertifika sınavları da yapmaktadır. Yani Azure sistemi bütün olarak bakıldığında
çok ayrıntılara sahip bir sistemdir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Azure kullanımı için ilk yapılacak şey bir e-posta ile Microsoft hesabı açmaktır. Sonra bu hesap kullanılarak Azure hesabı
ılmalıdır. Azure hesabıılırken "bedava" ve "ödediğn kadar kullan" biçiminde seçenekler karşımıza gelmektedir. Bedava
kullanımın pek çok kısıtları vardır. Bu nedenle deneme hesabınızı "ödediğin kadar kullan" seçeneği ile oluşturabilirisiniz.
Ancak kullanmadığınız servisleri her ihtimale karşı kapatmayı unutmayınız. Azure sistemine abona olunduktan sonra bir
kullajıcı için "abone ismi" oluşturulmaktadır.
Azure sistemini e-posta ve parola ile girildikten sonra ana yönetim sayfası portal sayfasıdır. Portal sayfasına
doğrudan aşağıdaki bağlantı ile girilebilmektedir:
https://portal.azure.com
Azure'ün ana sayfasına geçtikten sonra buradan Yapay Zeka ve Makine Öğrenmesi için "Azure Machine Leraning" seçilmelidir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Azure Machine Learning sayfasına geçildiğinde önce bir "Workspace" seçiminin yapılması gerekmektedir. Workspace yapılacak çalışmalar
için bir klasör gibi bir organizasyon oluşturmaktadır. Yeni bir Workspace oluşturabilmek için "Oluştur (Create)" düğmesine basılır.
Ancak bir "workspace" oluştururken bizim bir "kaynak grubuna (resource group)" ihtiyacımız vardır. Bu nedenle önceden bir
kaynak grubu oluşturulmuş olmalıdır. Kaynak grubu oluşturabilmek için ana menüden (hamburger menüden) "Kaynak Grupları (Resource Gropus)"
seçilir. Kaynak grubu birtakım kaynakların oluşturduğu gruptur. Workscpace de bir kaynaktır. Dolayısıyla workspace'ler
kaynak gruplarının (resource groups) bulunurlar. Kaynak Grupları menüsünden "Oluştur (Create)" seçilerek kaynak grubu oluşturma
sayfasına geçilir. Yaratılacak kaynak grubuna bir isim verilir. Bütün bu isimler dünya genelinde tek olmak zorundadır.
Kaynak Grupları diğer cloud sistemlerind eolduğu gibi bölgelerle ilişkilendirilmiştir. Bu nedenle kaynak grubu yaratılırken
o kaynak grubunun bölgesi de belirtilir.
Workspace oluştururken bizden bazı bilgilerin girilmesi istenmektedir. Ancak bu bilgiler default biçimde de oluşturulabilmektedir.
Ancak bizim workspace'e bir isim vermemiz ve onun yer alacağı kaynak grubunu (resource group) belirtmemiz gerekir. Bu adımlardan
sonra nihayet workspace oluşturulacaktır. Tabii bir workspace oluşturduktan saonra tekrar tekrar workspace oluşturmaya genellikle
gerek yoktur. Farklı çalışmaları aynı workspace içerisinde saklayabiliriz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Microsoft da tıpkı Amazon'da olduğu gibi makine öğrenmesiişlemleri için Web tabanlı bir IDE benzeri sistem oluşturmuştur.
Buna "Machine Learning Studio" ya da kısaca "Studio" denilmektedir. Workspace'i seçip "Studio düğmesine basarak Studi IDE'sine
geçebiliriz. Auto ML işlemleri için Studio'da "Automated ML" sekmesine tıklanılır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Machine Learning Studio'da Auto ML işlemlerini başlatmak için "Automated ML" sayfasında "New Automated ML Job" seçilir.
Daha önceden de gördüğümüz gibi bu tür Cloud Platformlarında dört ana unsur vardır:
1) Storage (Eğitim için veri kümesisi barındırmak için ve eğitim sonucunda oluşturulacak dosyaları barındırmak için)
2) CPU (Eğitimi yapabilmek için gereken makine)
3) Model (Çeşitli yöntemlerle veri kğmesine uygun en iyi ML modeli)
4) Deployment ya da EndPoint (Hedef modelin konuşlandırılması ve Web Servisler yoluyla uzaktan kullanılabilir hale getirilmesi)
Microsoft Azure sisteminde de "New Automated ML Job" işleminin ilk aşamasında bizden hangi veri kümesi üzerinde ML çalışması yapılacağı
sorulmaktadır. Biz bu aşamada yeni bir veri kğmesini Azure'ün Storage sistemine upload edebiliriz. Ya da bu upload etme işlemi
daha önceden oluşturulabilir. Azure sisteminde upload edilmiş veri kümelerine "data asset" denilmektedir. "New Automated ML Job"
işleminde toplam dört aşama bulunmaktadır:
1) Select data asset: Bu aşamada üzerinde çalışılacak veri kümesi belirtilir. Yukarıda söz ettiğimiz gibi bu veri kümesi daha
önceden "data asset" biçiminde oluşturulmuş olabilir ya da bu aşamada oluşturulabilir.
2) Configure job: Burada işlem için önemli bazı belirlemeler yapılmaktadır. Örneğin yapılacak işleme bir isim verilmektedir.
Veri kümesindeki tahmin edilecek hedef sütun belirtilmektedir. Eğitim için kullanılacak makinenin türü de bu aşamada
sorulmaktadır. Makine türü için "Compute Instance" seçilebilir. Tabii bizim daha önceden yaratmış olduğumuz bir hesapalama
maknesi (compute instance) bulunmuyor olabilir. Bu durumda bir hesaplama makinesinin (yani eğitimde kullanılacak makinenin)
yaratılması gerekecektir. Tabii aslında bir hesaplama makinesini (compute instance) daha önce de yaratmış olabiliriz. Hesaplama
makinesini daha önceden bu işlemden bağımsız olarak yaratmak için Manage/Compute sekmesi kullanılmaktadır.
3) Select task and settings: Burada bize problemin türü sorulmaaktadır. Tabii Azure hedef sütundan hareketle aslında problemin
bir sınıflandırma (lojistik regresyon) problemi mi yoksa lojistik olmayan regresyon problemi mi olduğunu belirleyebilmektedir.
4) Hyperparameter Configuration: Bu aşamad bize sınama yönteminin ne olacağı ve test verilerinin nasıl oluşturulacağı sorulmaktadır.
Bu aşamalardan geçildikten sonra Auto-ML en iyi modelleri bulmak için işlemleri başlatır. İşlemler bitince yine bize bildirimde bulunulmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Auto-ML aracı işini bitirdikten sonra EndPoint oluşturulmultur. Biz Studo'da EndPoints sekmesine gelerek ilgili endpoint'in
üzerine tıkladığımızda yukarıdaki menüde "Details", "Test", "Consume" gibi seçenekler bulunur. Burada "Test" seçildiğinde
GUI'den kestirim yapılabilmektedir. "Consume" kısmında Web servisleri ile predict işlemi yapan bir Python kodu bulundurulmaktadır.
Ancak bu kodda api_key kısmı boş bir string'tir. Buradaki API key "Consume" sekmesinden "Primary Key" kısmından alınabilir.
Örneğin burada Consume sekmesindeki kod aşağıdaki gibidir. Ancak bu kodlarda dikkat edilmesi gereken nokta şudur: Bu kodlarda
hesaba erişim için gereken "api key" boş bırakılmıştır. Bizim bu API key'i alıp buraya kopyalamamız gerekir. Daha önceden de
belirttiğimiz gibi bu API key Consume sekmesinden Primary Key alanından elde edilebilmektedir. Buradaki "consume kodu" şaşağıda
verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import urllib.request
import json
import os
import ssl
def allowSelfSignedHttps(allowed):
# bypass the server certificate verification on client side
if allowed and not os.environ.get('PYTHONHTTPSVERIFY', '') and getattr(ssl, '_create_unverified_context', None):
ssl._create_default_https_context = ssl._create_unverified_context
allowSelfSignedHttps(True) # this line is needed if you use self-signed certificate in your scoring service.
# Request data goes here
# The example below assumes JSON formatting which may be updated
# depending on the format your endpoint expects.
# More information can be found here:
# https://docs.microsoft.com/azure/machine-learning/how-to-deploy-advanced-entry-script
data = {
"Inputs": {
"data": [
{
"age": 0,
"sex": "example_value",
"bmi": 0.0,
"children": 0,
"smoker": "example_value",
"region": "example_value"
}
]
},
"GlobalParameters": 0.0
}
body = str.encode(json.dumps(data))
url = 'https://kaanaslantestworkspace-wyndx.northeurope.inference.ml.azure.com/score'
# Replace this with the primary/secondary key or AMLToken for the endpoint
api_key = ''
if not api_key:
raise Exception("A key should be provided to invoke the endpoint")
# The azureml-model-deployment header will force the request to go to a specific deployment.
# Remove this header to have the request observe the endpoint traffic rules
headers = {'Content-Type':'application/json', 'Authorization':('Bearer '+ api_key), 'azureml-model-deployment': 'automl457b251af41-1' }
req = urllib.request.Request(url, body, headers)
try:
response = urllib.request.urlopen(req)
result = response.read()
print(result)
except urllib.error.HTTPError as error:
print("The request failed with status code: " + str(error.code))
# Print the headers - they include the requert ID and the timestamp, which are useful for debugging the failure
print(error.info())
print(error.read().decode("utf8", 'ignore'))
#----------------------------------------------------------------------------------------------------------------------------
Aslında Microaoft tarafından hazırlanmış olan azureml isimli başka bir kütüphane daha bulunmaktadır. İşlemler bu kütüphane ile
daha az kod yazarak da yapılabilmektedir. Bu kütüphane için aşağıdaki paketlerin yüklenmesi gerekmektedir:
pip install azureml
pip install azureml-core
pip install azureml-data
Aşağıda azureml kullanılarak kestirim işlemine örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import json
from pathlib import Path
from azureml.core.workspace import Workspace, Webservice
service_name = 'automl245eb70546-1',
ws = Workspace.get(
name='KaanWorkspace',
subscription_id='c06cf27d-0995-4edd-919a-47fd40f4a7ae',
resource_group='KaanAslanResourceGroup'
)
service = Webservice(ws, service_name)
sample_file_path = '_samples.json'
with open(sample_file_path, 'r') as f:
sample_data = json.load(f)
score_result = service.run(json.dumps(sample_data))
print(f'Inference result = {score_result}')
#----------------------------------------------------------------------------------------------------------------------------
Azure'ün Machine Learning servisinde diğerlerinde henüz olmayan "designer" özelliği de bulunmaktadır. Bu designed sayesinde
sürükle bırak işlemleriyle hiç kod yazmadan makşne öğrenmesi modeli görsel biçimde oluşturulabilmektedir. Bunun için
Machine Learning Stdudio'da soldaki "Designer" sekmesi seçilir. Bu sekme seçildiğinde karşımıza bazı seçenekler çıkacaktır.
Biz hazır bazı şablonlar kullanarak ve şablonları değiştirerek işlemler yapabiliriz ya da sıfırdan tüm modeli kendimiz
oluşturabiliriz.
Model oluştururken sol taraftaki pencerede bulunan iki sekme kullanılmaktadır. Data sekmesi bizim Azure yükledeiğimiz veri
kümelerini göstermektedir. Component sekmesi ise sürüklenip bırakılacak bileşnleri belirtmektedir. Her bileşen dikdörtgensel
bir kutucuk ile temsil edilmiştir. Kod yazmak yerine bu bileşenler tasarım ekranına sürüklenip bırakılır. Sonra da bu bileşenler
birbirlerine bağlanır. Sürüklenip bırakılan bileşenlerin üzerine tıklanıp farenin sağ tuşu ile bağlam menüsünden bileşene özgü
özellikler görüntülenebilir. Bu bağlam menüsünde pek çok bileşen için en önemli seçenek "Edit node name" seçenğidir. Bu seçenekte
bileşene ilişkin özenmli bazı özellikler set edilmektedir.
Eğer model için şablon kullanmayıp sıfırdan işlemler yapmak istiyorsak işlemlere Data sekmesinde ilgili veri kümesini sürükleyip
tasarım ekranına bırakmakla başlamalıyız.
Veri kümesini belirledikten sonra biz veri kümesindeki bazı sütunlar üzerinde işlem yapmak isteyebiliriz. Bunun için
"Select Columns in Dataset" bileşeni seçilir. Veri kümesinin çıktısı bu bileşene fare hareketi ile bağlanır. Sonra
"Select Columns in Dataset" bileşeninde bağlam menüsünden "Edit node name" seçilir. Buradan da "Edit columns" seçilerek
sütunlar belirlenir.
Bu işlemden sonra eksik veriler üzerinde işlemlerin yapılması isteniyorsa "Clean Missing Data" bileşni seçilerek tasarım
ekranına bırakılır.
Bundan sonra veri kümesini eğitim ve test olmak üzere ikiye ayırabiliriz. Bunun için "Split Data" bileşeni kullanılmaktadır.
Bu bileşende "Edit node names" yapıldığında bölmenin yüzdelik değerleri ve bölmenin nasıl yapılacağına yönelik bazı
belirlemeler girilebilir.
Bu işlemden sonra "Özellik Ölçeklemesi (Feature Scaling)" yapılabilir. Bunun için "Normalize Data" bileşeni seçilir.
Bu bileşende "Edit node names" seçildiğinde biz ölçekleme üzerinde belirlemeleri yapabilecek duruma geliriz.
Bu aşamalardan sonra artık sıra modelin eğitimine gelmiştir. Modelin eğitimi için "Train Model" bileşeni kullanılmaktadır.
Bu bileşenin iki girişi vardır. Girişlerden biri "Dataset" girişidir. Bu girişe veri test veri kümesi bağlanır. Bileşenin
birinci girişi olan "Untrained model" girişine ise problemin türünü belirten bir bileşen bağlanır. Çeşitli problem türleri
için çeşitli bileşenler vardır. Örneğin ikili sınıflandırma problemleri için "Two class logistic regression" bileşeni kullanılır.
Eğitime ilişkin hyper parametreler bu bileşende belirtilmektedir.
Eğtim işleminden sonra sıra modelin test edilmesine gelmiştir. Modelin testi için "Score Model" bileşeni kullanılır. Score Model
bileşeninin iki girişi vardır: "Trainded Model" ve "Dataset" girişleri. "Train Model" bleşeninin çıkışı "Trained Model" girişine,
"Split Data" bileşeninin ikinci çıkışı ise "Dataset" girişine bağlanır.
Model Designer'da oluşturulduktan sonra artık sıra modelin eğitilmesine gelmiştir. "Configure & Submit" düğmesine basılır.
Burada artık eğitim için kullanılacak CPU kaynağı belirlenir. (Anımsayacağınız gibi Automated araçlarda bizim belirlememiz
gereken üç unsur "Data" + "Model" + "CPU" biçimindeydi.)
Nihayet işlemleri başlatıp deployment işlemi için "Inference Pipeline"" seçeneği seçilmelidir.
Azure Designer'daki tüm bileşenler (components) aşağıdaki Microsoft bağlantısında dokümante edilmiştir:
https://learn.microsoft.com/en-us/azure/machine-learning/component-reference/component-reference?view=azureml-api-2
Biz Inference Pipeline işlemini yaptığımızda Azure bize modelimizi kestirimde kullanılabecek hale getirmektedir. Bunun
için model bir "Web Service Output" bileşeni eklemektedir. Bu "Web Service Output" bileşeni kestirim çıktılarının
bir web servis biçiminde verileceği anlamına gelmektedir. Eskiden Azure aynı zamanda "Inference Pipeline" seçildiğinde
modelimize bir "Web Service Input" bileşeni de ekliyordu. Bu bileşen de girdilerin web service tarafından alınacağını
belirtmekteydi. Designer'ın yeni versiyonlarında "Infererence Pipeline" yapıldığında bu "Web Service Input" bileşeni artık
eklenmemektedir. Web Service Input bileşeni eklendikten sonra artık bizim "Select Columns in Dataset" bileşeninden
kestirilecek sütunu çıkartmamız gerekmektedir. Ayrıca bu Web Service Input bileşeninin çıktısının artık "Select Columns in Dataset"
bileşenine değil doğrudan ApplyTransformation bileşenine bağlanması gerekmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Desginer'ın aytıntılı kullanımı için Microsoft dokümanlarını inceleyebilirsiniz. Örneğin aşağıdaki dokümanda lojistik olmayan
regresyon probleminin adım adım designer yardımıyla oluşturulması anlatılmaktadır:
https://learn.microsoft.com/en-us/azure/machine-learning/tutorial-designer-automobile-price-train-score?view=azureml-api-1
#----------------------------------------------------------------------------------------------------------------------------
IBM'in Cloud Platformu da en çok kullanılan platformlardan biridir. İşlevsellik olarak diğerlerine benzemektedir. IBM'de
cloud platformu içerisinde makine öğrenmesine ilişkin servisler bulundurmaktadır. Genel olarak IBM'in bu servislerine
Watson denilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
IBM Cloud platformu için yine bir hesap açılması gerekir. Hesap açma ve sign in işlemleri cloud.ibm.com adresinden
yapılmaktadır. Hesap açılırken girilen e-posta adresi aynı zmaanda "IBMid" olarak kullanılmaktadır. IBMid cloud platformunda
"user id" gibi kullanılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
IBM Cloud platformuna login olunduktan sonra karşımıza bir "Dashboard" sayfası çıkmaktadır. Burada ilk yapılacak şey
"Create Resource" seçilerek kaynak yaratılmasıdır. Buradan "Watson Studio" seçilir. Bedava hesp ile ancak bir tane "Watson Studio"
kaynağı oluşturulabilmektedir. Kaynak oluşturulduktan sonra "Launch in IBM Cloud Pak for Data" düğmesine basılarak
"IBM Watson Studio" ortamına geçilmektedir. IBM Watson Studio bir çeşit "Web Tabanlı IDE" gibi düşünülebilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
IBM Watson Studio'ya geçildiğinde öncelikle bir projenin yaratılması gerekmektedir. Bunun için "New Project" seçilir.
Proje bir isim verilir. Sonra projenin yaratımı gerçekleşir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Proje yaratıldıktan sonra "New Asset" düğmesi le yeni bir proje öğesi (asset) oluşturulmalıdır. Burada Otomatik ML işlemleri
için "Auto AI" seçilebilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Auto AI seçildiğinde yaratılacak işlem (experiment) için bir isim verilmelidir. Sonra bu işlem (experiment)" bir ML servisi
ile ilişkilendirilmelidir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Watson Studio'da Auto AI projesinde bizden öncelikle veri kümesinin yüklenmesi istenmektedir. Veri kümesi yüklendikten sonra
bu veri kümesinin ardışık "time series" verilerinden oluşup oluşmadığı bize sorulmaktadır. Bundan biz kesitim yapılacak sütun
belirleriz. Uygulamacı problemin türüne göre problemin çeşitli meta parametrelerini kendisi set edebilmektedir. Bu işlem
"Experiment Settings" düğmesiyle yapılmaktadır. Nihayet uygulamacı "Run Experiment" seçeneği ile eğitimi başlatır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Modeller oluşturulduktan sonra bunlar performansa göre iyiden kötüye doğru sıralanmaktadır. Bu modeller save edilebilir.
Modeller save edilirken istenirse model kodları bir Jupyter Notebook olarak da elde edilebilmektedir. Bu notebook yerel makineye
çekilip IBM Python kütüphanesi kurulduktan sonra yerel makinede de çalıştırılabilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Deployment işlemi için önce bir "deployment space" yaratılır. Ancak bu deployment space'te Assets kısmında deploy edilecek
modelin çıkması için dah önceden Model sayfasında "Promote to Deployment Space" seçilmelildir. Bundan sonra Deployment Space'te
Assets kısmında ilgili model seçilerek "Create Deployment" düğmesi ile deployment işlemi yapılır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Deployment sonrası yine Wen Servisleri yoluyla model kullanılabilmektedir. Bu işlem Watson Web API'leriyle yapılabilecğei gibi
diğer cloud platfotrmlarında olduğu gibi bu Web API'lerini kullanan Python kütüphaneleriyle de yapılabilmektedir. Kütüphane
aşağıdaki gibi install edilebilir:
pip install ibm-watson-machine-learning
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------