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

34975 lines
1.8 MiB
Text
Raw Normal View History

2024-07-15 13:23:34 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Yapay Zeka, Makine öğrenmesi ve Veri Bilimi
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.)
2024-10-03 14:16:20 +03:00
Son Güncelleme: 03/10/2024 - Perşembe
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
1. Ders - 23/12/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Katılımcılarla tanışıldı ve kursun tanıtımı yapıldı.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2. Ders - 24/12/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Python Programlama Dilinin gözden geçirilmesine başlandı.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
3. Ders - 06/01/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Python Programlama Dilinin gözden geçirilmesine devam edildi.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
4. Ders - 07/01/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Python Programlama Dilinin gözden geçirilmesine devam edildi.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
5. Ders - 13/01/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Python Programlama Dilinin gözden geçirilmesine devam edildi.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
6. Ders - 14/01/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Python Programlama Dilinin gözden geçirilmesine 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ı. Matplotlib
kütüphanesi gözden geçirildi.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
9. Ders - 27/01/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Kursumuzun ilk bölümünde zeka, yapay zeka, öğrenme, makine öğrenmesi ve veri 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 kullanılmıştır. 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
2024-08-22 19:31:50 +03:00
bir yapay zeka faaliyeti olarak ele alınmıyor. Benzer biçimde satrançta bir büyük ustayı yenecek 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
2024-08-22 19:31:50 +03:00
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 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 bilgi iş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, makine öğrenmesi ve veri bilimi alanlarının diğer pek çok disiplinle yakın ilgisi vardır. Bu ilişkileri açıklamak
istiyoruz.
Bilgisayar Bilimleri (Computer Science): bilgi iş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.
İstatistik: İstatistik temelde iki bölüme ayrılmaktadır:
- Betimsel istatistik (descriptive statistics)
- Çıkarımsal istatistik (inferential statistics)
Betimsel istatistik verilerin gruplanması, özetlenmesi, karakteristiklerinin betimlenmesi ve gösterilmesi ile ilgilidir. Yani
betimleyici istatistik "zaten var olan durumu" betimlemektedir. Çıkarımsal istatistik ise "kestirim yapmakla" ilgilidir.
Makine öğrenmesi ve veri bilimi 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 ve veri bilimi 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.)
İlgili Konudaki Özel Bilgiler (Domain Specific Knowledge): Şü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:
2024-10-03 14:16:20 +03:00
- Son yıllarda veri işleme ve verilerden kestirim yapma gereksinimi gittikçe artmıştır ve Python dili de veri bilimi 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 bilimi 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 bilimi için diğer dillere göre daha erken yola çıktığı söylenebilir. Bu alanda algoritma geliştiren
araştırmacılar algoritmalarını daha çok Python kullanarak gerçekleştirmişlerdir.
Pekiyi Python dilinin veri bilimi 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, scikit-learn, Tensorflow, Pytorch gibi)
asıl olarak C ve C++ programlama dilleri ile yazılmış olsalar da bu C ve 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 bilimi ve makine öğrenmesi uygulamalarında bir sorun oluşturmaz mı?
İşte Python'un 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'u
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ığını söyleyebiliriz.
İş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
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay zeka ve özellikle de makine öğrenmesi ile ilgili çalışmalar yapacak kişilerin belli düzeyde istatistiksel bilgilere
sahip olması gerekmektedir. Şüphesiz istatistik pek çok alt alanı olan geniş bir bilim dalıdır. Bu nedenle istatistiksel
konulara ilişkin pek çok ayrıntı vardır. Biz bu bölümde temel bilgiler vermekle yetineceğiz. Çeşitli ayrıntılar ilgili
konuların anlatıldığı bölümde gerektiğinde açıklanacaktır. (Örneğin "kümeleme analizi (cluster analysis)" aslında istatistikte
çok uzun süredir incelenen bir konudur. Ancak son yıllarda makine öğrenmesi bağlamında konunun önemi çok daha fazla artmış
ve bu bağlamda pek çok algoritmik yöntem geliştirilmiştir. Dolaysıyla örneğin kümeleme analizi çok değişkenli istatistiğin
bir konusu olduğu halde biz bu tekniğin ayrıntılarını "denetimsiz öğrenme (unsupervised learning)" içerisinde ele alacağız.)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
İstatistikte ölçülen ya da ölçülmüş olan değerlerin sınıflarına genel olarak "ölçek (scale)" denilmektedir. Pek çok kişi
ölçeklerin yalnızca sayısal olduğunu sanmaktadır. Halbuki ölçekler başka biçimlerde de karşımıza çıkabilmektedir. İstatistikte
ölçekler tipik olarak dört sınıfa ayrılmaktadır:
Kategorik (Nominal) Ölçekler: Bu ölçeklerde söz konusu kümenin elemanları kategorik olgulardır. Örneğin cinsiyet, renk,
coğrafi bölge gibi. Bu ölçekteki ölçülen ya da ifade edilen değerlerin sayısal karşılıkları yoktur. Örneğin "kadınlarla
erkekler arasında sigara içme miktarı arasında anlamlı bir fark olup olmadığını" anlamak için gerçekleştirilen bir araştırmada
ölçülmesi istenen değişkenlerden "cinsiyet" kategorik (nominal) bir ölçeğe ilişkindir. Benzer biçimde kişilerin renk tercihleriyle
ilgili bir araştırmada renkler (siyah, beyaz, kırmızı gibi) kategorik bir ölçekle ifade edilirler.
Sırasal (Ordinal) Ölçekler: Bu ölçeklerdeki değerler de birer kategori belirtmekle birlikte bu kategoriler arasında büyüklük
küçüklük ilişkisi söz konusudur. Örneğin eğitim durumu için kategorik değerler "ilköğretim", "lise", "üniversite" olabilir
ve bunlar arasında sıra ilişkisi vardır. Bu nedenle "eğitim durumu" bir sıralı ölçek belirtmektedir.
Aralıklı (Interval) Ölçekler: Aralıklı ölçekler sayısal bilgi içerirler. Bu tür ölçeklerde iki puan arasındaki fark aynı
miktar uzaklığı ya da yakınlığı ifade eder. Örneğin bir testte 20 puan alan 10 puan alandan beli miktarda daha iyidir. 30
puan alan da 20 puan alandan aynı miktar kadar daha iyidir. Bu tür ölçeklerde mutlak sıfır noktası yoktur. Başka bir deyişle
bu tür ölçeklerde sıfır "yokluğu" ya da "mevcut olmamayı" belirtmemektedir. Alınan puanlar her zaman belli bir göreli orijine
göre anlamlıdır. Örneğin aslında sınavlardan alınan puanlar böyle bir ölçek türündedir. Sınavdan sıfır alınabilir. Ancak bu
sıfır o kişinin o konu hakkında hiçbir şey bilmediği anlamına gelmez. Yani mutlak sıfır değildir. Ya da örneğin ısı belirten
"derece (celcius)" bir aralıklı ölçeği belirtmektedir. 50 derece ile 40 derece arasındaki ısı farkı 40 derece ile 30 derece
arasındaki fark kadardır ancak sıfır derece ısının olmadığı anlamına gelmez. Aralıklı ölçeklerde oran oluşturmak anlamlı
olmayabilmektedir. Örneğin 20 derecelik ısı ile 10 derecelik ısı arasında iki kat bir oran vardır. Ancak biz 20 derecenin 10
dereceden iki kat daha sıcağı belirttiğini söyleyemeyiz.
Oransal (Ratio) Ölçekler: Bu ölçekler de sayısal bilgi içerirler. Oransal ölçekler aralık ölçeklerin tüm özelliklerine
sahiptirler. Ancak ek olarak oransal ölçeklerde mutlak bir sıfır noktası da vardır. Dolayısıyla puanlar arasındaki oranlar
mutlak olarak anlamlıdır. Örneğin uzunluk, kütle gibi temel fiziksel özellikler oransal ölçek türlerindendir. Bir nesnenin
uzunluğunun sıfır olması onun uzunluğunun olmadığı, kütlesinin sıfır olması da onun kütlesinin olmadığı anlamına gelmektedir.
Örneğin kişinin yaşı da oransal br ölçek belirtir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
İ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'un
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 eksen (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
hesaplanabilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Diğer bir merkezi eğilim ölçüsü de "medyan (median)" denilen ölçüdür. Medyan küçükten büyüğe sıraya dizilmiş olan sayıların
ortasındaki 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ıkta bir işlemdir. Medyan işlemi Python'un 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 dizilir,
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. mmode fonksiyonu
tuple sınıfından türetilen ModeResult isimli 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 mod 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şağıdaki programda değerlerin ortalamadan
uzaklıklarının ortalaması 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
2024-04-08 14:07:40 +03:00
alınmasının nedeni uzaklıkları negatif olmaktan kurtarmak 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.
2024-04-08 14:07:40 +03:00
Aynı zamanda bu işlem bazı durumlarda başka faydalara da yol açmaktadır. (Örneğin ileride bu kare alma işleminin optimizasyon
problemleri için daha 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ölme n'e ya da (n - 1)'e yapılabilmektedir. Anakütle (population) için
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 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'ın variance fonksiyonundaki ddof parametresinin default değeri 0, Pandas'ın variance fonksiyonundaki ddof
parametresinin default değeri 1dir.
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. Aşağıda varyans işleminin nasıl yapıldığına
yönelik bir örnek verilmiş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
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
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 desteden bir kağıt çekilmesi birer rassal deneydir. 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 felsefede "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 toplam 2^n tane alt kümesi olduğunu anımsayınız.)
Ö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 deney çok sayıda yinelendikçe elde edilen olasılık değerleri 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 teorem-ispat biçiminde açıklanabileceğini göstermiştir. Kolmogorov'un üç aksiyomu şöyledir:
1) Örnek uzayının olası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 ile 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
2024-04-08 14:07:40 +03:00
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 olasılık toplamlarına eşittir. Yani E1 ve E2 iki
2024-04-08 14:07:40 +03:00
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ılık formülleri elde
2024-04-08 14:07:40 +03:00
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 değişken örnek uzayın her bir elemanını (yani basit olayını)
gerçek (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:
- Z rassal değişkeni "iki zar atıldığında zarların üzerindeki sayıların toplamını" belirtiyor olsun. Burada aslında Z
bir fonksiyondur. Örnek uzayın her bir elemanını bir değere eşlemektedir. Matematiksel gösterimle Z rassal değişkeni
şöyle belirtilebilir:
Z: S -> R
Burada Z fonksiyonunun S örnek uzayından R'ye bir fonksiyon belirttiği anlaşılmaıdır. Burada Z 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 değerler 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 olası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 olası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 olma 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 (continuous)", yalnızca belli gerçek sayı değerlerini alabiliyorsa böyle
rassal değişkenlere ise "kesikli (discrete)" rassal değişkenler denilmektedir. Örneğin "iki zarın atılmasında üste gelen
sayılar toplamını belirten Z 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.
(Sonsuz büyüklükte bir kümeye bir eleman daha eklesek bunun bir etkisi olabilir mi?)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
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 üzerinde biraz daha duracağız.
Sürekli bir rassal değişkenin aralıksal olasılıkları "intergral" işlemi ile hesaplanmaktadır. Tabii integral işlemi için bir
fonksiyona gereksinim vardır. İşte sürekli rassal değişkenlerin 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şinin 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. Olasılık yoğun
fonksiyonları bazı parametrelere de sahip olabilmektedir. Örneğin Gauss eğrisinin iki parametresi vardır: Orta noktasını
belirten "ortalama" ve zayıflığını şişmanlığını belirten "standart sapma".
Bu durumda örneğin sürekli rassal değişkenlerle ilgili bir olasılık sorusu şö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 Guass fonksiyonunun 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 çıkarılması biraz zahmetli bir işlemdir. Ancak kümülatif olasılıklar yardımıyla
olasılık yoğunluk fonksiyonları tatmin edici bir biçimde elde edilebilmektedir. Tabii 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. Yukarıda da belirttiğimiz gibi 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 kalan alanı 1'dir.
Fonksiyon simetrik olduğuna göre ortalamanın iki yanındaki eğri altında kalan alan 0.5'tir.
Yukarıda da belirttiğimiz gibi 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", standart normal dağılımdaki X değerlerine
de 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çekten de normal dağılımda ortalamadan bir standart sapma soldan ve sağdan
kaplanan alan yani P{mu - std < X < mu + std} olasılığı 0.6827, ortalamadan iki standart sapma soldan ve sağdan kaplanan
alan yani P{mu - std * 2 < X < mu + std * 2} olasılığı 0.9545, otalamadan üç standart sapma soldan ve sağdan kaplanan alan
yani P{mu - std * 3 < X < mu + std * 3} olasılığı ise 0.9956 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 fill_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()
fill_gauss(100, 15, 85, 115)
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda da belirttiğimiz gibi "kümülatif dağılım fonksiyonu (cummulative distribution function)" belli bir değere kadar
tüm birikimli olasılığı 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 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. Çıkarımsal istatistiğin dayandığı temel merkezi
limit teoremi olduğu için normal dağılım da çok önemli olmaktadır. Merkezi limit teoremini izleyen paragraflarda
ele alacağız.
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ümülatif 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 ortalama ve standart sapma değerleri girilir. (Bu değerler girilmezse ortalama için 0,
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 solundaki 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 uyuyor olsun.
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:
nd = statistics.NormalDist(100, 15)
result = 1 - nd.cdf(140)
print(result) # 0.003830380567589775
#----------------------------------------------------------------------------------------------------------------------------
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 olasılığı bu tablo yoluyla nasıl elde edebilmekteyiz? İşte aslında herhangi bir normal dağılım standart normal
dağılıma dönüştürülebilmektedir. 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 = 136 değerinin standart normal dağılımdaki Z değeri
2.40'tü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:
n = 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 metoduyla elde edilmektedir. Örneğin x = 0
için standart normal dağılımda Gauss fonksiyonun 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ış rastgele sayı üretmek için NormalDist sınıfının samples isimli metodu kullanılmaktadır. Bu metot kaç rassal
sayının üretileceğini parametre olarak alıp üretilen rassal sayıları float elemanlardan oluşan bir liste biçiminde vermektedir.
Ö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=20)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Aslında normal dağılmış rasgele sayı üretmek için standart random modülü içerisinde gauss isimli bir fonksiyon da
bulundurulmuştur. gauss fonksiyonunun parametrik yapısı şöyledir:
gauss(mu=0.0, sigma=1.0)
Python 3.11den önce bu parametreler default değer almıyordu. Fonksiyonu kullanırken bu durumu dikkate alınız. Aşağıda
gauss fonksiyonu kullanılarak üretilen normal dağılmış rassal sayıların histrogramı çizilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import random
result = [random.gauss(0, 1) for _ in range(10000)]
import matplotlib.pyplot as plt
plt.hist(result, bins=20)
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)
#----------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
norm nesnesinin ilişkin olduğu sınıfın pdf (probability density function) isimli metodu yine x değerlerinin Gauss eğrisindeki
y değerlerini vermektedir. Metodun parametrik yapısı şöyledir:
pdf(x, loc=0, scale=1)
2024-07-15 13:23:34 +03:00
Burada yine x hesaplanacak değeri, loc ortalamayı ve scale de standart sapmayı belirtmektedir.
#----------------------------------------------------------------------------------------------------------------------------
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)
#----------------------------------------------------------------------------------------------------------------------------
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()
#----------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
Diğer çok karşılaşılan sürekli dağılım "sürekli düzgün dağılım (continuos uniform distribution)" denilen dağılımdır. Burada
dağılımın a ve b biçiminde isimlendirebileceğimiz iki parametresi 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 b - a 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
}
2024-07-15 13:23:34 +03:00
Sürekli düzgün dağılım için Python'un 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
2024-07-15 13:23:34 +03:00
cdf, ppf, pdf ve rvs metotları vardır. Bu metotlar sırasıyla 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ğuna
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ın 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 tasarım kullanılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
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)
2024-07-15 13:23:34 +03:00
plt.title('Continuous 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ı üretimi 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. Benzer 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
2024-08-22 19:31:50 +03:00
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 (William Sealy Gosset)
makalesini "Student" takma adıyla yayınladığından dolayı bu dağılıma İngilizce "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ünümdedir. 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.
2024-08-22 19:31:50 +03:00
t dağılımı denildiğinde her zaman ortalaması 0 olan standart sapması karekök(df / (df - 2)) olan dağılım anlaşılmaktadır.
Tabii t dağılımı da eksende kaydırılabilir yani ortalaması değiştirilebilir. t Dağılımı standart normal dağılım için üretilmiştir.
Dolayısıyla eğer söz konusu dağılım standart normal dağılım değilse t dağılımını kullanmak için önce söz konusu normal dağılımı
standart normal dağılıma dönüştürmek gerekir.
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 da tercih edilebilmektedir.
2024-08-22 19:31:50 +03:00
t dağılımının "serbestlik derecesi (degrees of freedom)" denilen bir parametresi vardır. Serbestlik derecesi "örneklem
büyüklüğünden bir eksik olan" değerdir. Ö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
2024-08-22 19:31:50 +03:00
önce x değerini sonra serbestlik derecesini, sonra da ortalama değeri ve standart sapma değerlerini parametre olarak almaktadır.
Ortalama ve standart sapma değerleri kullanılarak dağılım önce standart normala dağılıma dönüştürülmekte ondan sonra t
dağılımı ile işlemler yapılmaktadı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.
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
import numpy as np
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)
y = t.pdf(x, 30)
plt.plot(x, y, color='red')
2024-08-22 19:31:50 +03:00
plt.legend(['Standart Normal Dağılım', 't Dağılımı (Serbestlik Derecesi = 5)',
't dağılımı (Serbestlik Derecesi = 30)'])
2024-08-22 19:31:50 +03:00
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
2024-09-16 10:49:44 +03:00
x = [-0.5, 0, 1, 1.25]s
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]
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
Biz bu konuda sürekli dağılım olarak yalnızca "normal dağılımı", "sürekli 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
2024-08-26 14:00:39 +03:00
fonksiyonlar elde edilemeyecek kesikli 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ı
2024-08-26 14:00:39 +03:00
doğrudan rassal değişkeninin ilgili noktadaki olasılığını elde etmek için kullanılmaktadır. Olasılık kütle fonksiyonu
genellikle büyük P ya da küçük p harfi ile gösterilmektedir:
px(x) = P{X = x}
Bu ifade X'in x değerine eşit olma olsılığını belirtmektedir. Buradaki X rassal değişkeni her gerçek değeri alamaz yalnızca
bazı değerleri alabilir. Tabii olasılık kütle fonksiyonu px(x) olmak üzere X'in her x değeri için px(x) değerlerinin
toplamının yine 1 olması gerekmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Tıpkı sürekli rassal 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 okunuyor)" dağılımıdır. Bu
2024-08-26 14:00:39 +03:00
kesikli dağılım adeta normal dağılımın kesikli biçimi gibidir. Poisson dağılımının olasılık kütle fonksiyonu şöyledir:
2024-08-26 14:00:39 +03:00
P(X = x) = (e^-lamda * lamda^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
2024-08-26 14:00:39 +03:00
değeri belirtmektedir. Lamda ise ortalama olay sayısını belirtmektedir. Lamda 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 bir günde 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,
2024-08-26 14:00:39 +03:00
ortalamadan iki yandan uzaklaştıkça olasılıkların çan eğrisi gibi düştüğü" bir olasılık kütle fonksiyonunu 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 singleton 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 singleton nesnelere
kullanım bakımından oldukça benzemektedir. Ancak kesikli dağılımlar için fonksiyonun ismi "pdf" değil "pmf" biçimindedir.
2024-09-16 10:49:44 +03:00
Buradaki "pmf" ismi "probability mass function" sözcüklerinden 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 futbol maçlarındaki gol sayısının poisson dağılımına 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:
2024-08-26 14:00:39 +03:00
from scipy.stats import poisson
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.
2024-09-16 10:49:44 +03:00
Sonra kişiden bazı değerleri bulmasını ister. Pekiyi soruda "bir maçta ikiden fazla gol olma olasılığı" sorulmuş olsaydı 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
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-08-26 14:00:39 +03:00
Aşağıda lamda değeri (ortalaması) 10 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
2024-08-26 14:00:39 +03:00
plt.title('Lamda = 10 İçin Poisson Dağılımı', fontweight='bold', pad=10)
x = range(0, 40)
2024-08-26 14:00:39 +03:00
y = poisson.pmf(x, 10)
plt.scatter(x, y)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Poisson dağılımında lamda değeri yüksek tutulduğunda saçılma grafiğinin Gauss eğrisine benzediğine dikkat ediniz.
#----------------------------------------------------------------------------------------------------------------------------
from scipy.stats import poisson
import matplotlib.pyplot as plt
2024-08-26 14:00:39 +03:00
plt.title('Lamda = 100 İçin Poisson Dağılımı', fontweight='bold', pad=10)
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
ilişkin 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.
----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-08-26 14:00:39 +03:00
Diğer çok karşılaşılan bir kesikli dağılım da "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.
2024-08-26 14:00:39 +03:00
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
2024-08-26 14:00:39 +03:00
kullanılması diğer singleton nesnelere benzemektedir. Örneğin ilgili sınıfın pmf fonksiyonu olasılık kütle fonksiyonunu
belirtmektedir. Bu fonksiyonlarda birinci parametre "olumlu gerçekleşme sayısını", ikinci parametre "toplam deney sayısını"
2024-08-26 14:00:39 +03:00
ve üçüncü parametre de "olumlu gerçekleşme olası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.
2024-08-26 14:00:39 +03:00
Merkezi limit teoreminin biçimsel ifadesi biraz karmaşık bir görünümdedir. Biz burada teroremi basit biçimde açıklayacağız.
Bu teoreme göre bir anakütleden elde edilen örnek ortalamaları normal dağılma eğilimindedir. Eğer anakütle normal dağılmışsa
küçük örneklerin ortalamaları da normal dağılır. Ancak anakütle normal dağılmamışsa örnek ortalamalarının normal dağılması
için örneklerin belli bir büyüklükte (tipik olarak >= 30) olması gerekmektedir. Aksi takdirde örneklem dağılımı soldan ya
da sağdan çarpık olabilmektedir. Örneğin elimizde 1,000,000 elemanlı bir anakütle olsun. Bu anakütleden 50'lik tüm alt
kümeleri yani örnekleri elde edip bunların ortalamalarını hesaplarsak bu ortalamaların normal dağıldığı görülecektir. Bir
anakütleden alınan alt kümelere "örnek (sample)" bu işleme de genel olarak "örnekleme (sampling)" denilmektedir.
2024-08-26 14:00:39 +03:00
Aşağıdaki örnekte 0 ile 1,000,000 arasındaki sayılardan oluşan 1,000,000 elemanlık düzgün dağılmış anakütle içerisinden
100,000 tane 50'lik rastgele örnekler elde edilmiştir. Sonra da elde edilen bu örneklerin ortalamaları hesaplanmış ve bu
ortalamaların histogramı çizdirilmiştir. Örnek ortalamalarının dağılımına ilişkin histogramın normal dağılıma benzediğine
dikkat ediniz.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
2024-08-26 14:00:39 +03:00
POPULATION_RANGE = 1_000_000
NSAMPLES = 100_000
SAMPLE_SIZE = 50
2024-08-27 01:07:02 +03:00
samples = np.random.randint(0, POPULATION_RANGE + 1, (NSAMPLES, SAMPLE_SIZE))
samples_means = np.mean(samples, axis=1)
2024-08-26 14:00:39 +03:00
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 8))
plt.title('Merkezi Limit Teoremi', fontweight='bold', pad=10)
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
2024-08-26 14:00:39 +03:00
POPULATION_RANGE = 1_000_000
NSAMPLES = 100_000
SAMPLE_SIZE = 50
2024-08-27 01:07:02 +03:00
samples_means = [statistics.mean(random.sample(range(POPULATION_RANGE + 1), SAMPLE_SIZE)) for _ in range(NSAMPLES)]
import matplotlib.pyplot as plt
2024-08-26 14:00:39 +03:00
plt.figure(figsize=(12, 8))
plt.title('Merkezi Limit Teoremi', fontweight='bold', pad=10)
plt.hist(samples_means, bins=50)
2024-08-26 14:00:39 +03:00
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
2024-08-26 14:00:39 +03:00
Merkezi limit teoremine göre örnek ortalamalarına ilişkin normal dağılımın ortalaması anakütle ortalamasına eşittir.
(Yani bir anakütleden çekilen örnek ortalamalarının ortalaması anakütle ortalaması ile aynıdır.) İstatistiksel sembollerle
2024-08-26 14:00:39 +03:00
bu durum aşağıdaki gibi ifade edilmektedir:
2024-08-26 14:00:39 +03:00
Muxbar = Mu (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
2024-09-16 10:49:44 +03:00
ö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
2024-08-27 01:07:02 +03:00
POPULATION_RANGE = 1_000_000
NSAMPLES = 100_000
SAMPLE_SIZE = 50
2024-08-27 01:07:02 +03:00
population_mean = np.mean(range(POPULATION_RANGE + 1))
samples = np.random.randint(1, POPULATION_RANGE + 1, (NSAMPLES, SAMPLE_SIZE))
samples_means = np.mean(samples, axis=1)
samples_means_mean = np.mean(samples_means)
print(f'Anakütle ortalaması: {population_mean}')
print(f'Örnek ortalamalarının Ortalaması: {samples_means_mean}')
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
Yukarıda bir anakütleden alınan örnek ortalamalarının 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.
2024-08-27 01:07:02 +03:00
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. Ö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.
2024-08-27 01:07:02 +03:00
Aşağıdaki örnekte örnek 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
2024-08-27 01:07:02 +03:00
POPULATION_RANGE = 1_000_000
NSAMPLES = 100_000
SAMPLE_SIZE = 50
2024-08-27 01:07:02 +03:00
population_mean = np.mean(range(POPULATION_RANGE + 1))
population_std = np.std(range(POPULATION_RANGE + 1))
2024-08-27 01:07:02 +03:00
samples = np.random.randint(1, POPULATION_RANGE + 1, (NSAMPLES, SAMPLE_SIZE))
samples_means = np.mean(samples, axis=1)
samples_means_mean = np.mean(samples_means)
sample_means_std = np.std(samples_means)
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.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-08-27 01:07:02 +03:00
Yukarıdaki örneklerde anakütlenin normal dağılmadığına düzgün (uniform) dağıldığına dikkat ediniz. Çünkü biz anakütleyi
0'dan 1,000,000'a kadar sayılardan anakütleyi oluşturduk. Yukarıdaki örneklerde oluşturduğumuz anakütlenin histogramı
aşağıda çizdirilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import matplotlib.pyplot as plt
2024-08-27 01:07:02 +03:00
POPULATION_RANGE = 1_000_000
population = range(0, POPULATION_RANGE + 1)
2024-08-27 01:07:02 +03:00
plt.figure(figsize=(12, 8))
plt.title('Merkezi Limit Teoremi', fontweight='bold', pad=10)
plt.hist(population, bins=50)
#----------------------------------------------------------------------------------------------------------------------------
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.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
Parametrik istatistiksel yöntemler genel olarak anakütle ve/veya örneklem dağılımının normal olduğu varsayımı ile yürütülmektedir.
Bu nedenle bazen anakütlenin normal dağılıp dağılmadığının örneğe dayalı olarak test edilmesi gerekebilmektedir. 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ğunu varsayabiliriz. Ancak anakütlenin normal dağılıp dağılmadığının tespit edilmesi
için bazı "hipotez testlerinden (hypothesis testing)" faydalanılmaktadır. Normal dağılıma ilişkin iki önemli hipotez testi
vardır: "Kolmogorov-Smirnov" testi ve "Shapiro-Wilk" testi. Bu testlerin istatistiksel açıklaması biraz karmaşıktır ve ayrıntılar
içermektedir. Biz bu bölümde bu konuda ayrıntılııklamalarda bulunmayacağız. Hipotez testleri kursumuzun sonraki bölümleri
içerisinde ayrı bir başlık halinde ele alınmaktadır.
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 (buna "null hipotez" de denilmektedir), 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.
2024-09-16 10:49:44 +03:00
Normallik testlerinden birisi Kolmogorov-Smirnov testidir (test ünlü Sovyet matematikçisi ve istatistikçisi Andrey Kolmogorov
ve Nikolai Smirnov tarafından geliştirildiği için bu isimle anılmaktadır). Bu test SciPy kütüphanesindeki stats modülü içerisinde
bulunan kstest fonksiyonuyla uygulanabilmektedir. Fonksiyonun parametrik yapısı şöyledir:
kstest(rvs, cdf, args=(), N=20, alternative='two-sided', method='auto', *, axis=0, nan_policy='propagate', keepdims=False)
Fonksiyonunun ilk iki parametresi zorunlu parametrelerdir. Birinci parametre anakütleden rastgele seçilen örneği, ikinci
parametre testi yapılacak dağılımın kümülatif dağılım fonksiyonunu almaktadır. Ancak bu parametre kolaylık olsun diye
yazısal biçimde de 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.
2024-09-16 10:49:44 +03:00
kstest fonksiyonu çağrıldığktan sonra bize KstestResult türünden "isimli bir demet (named tuple)" verir. Demetin statistic
isimli ilk elemanı test istatistiğini, pvalue isimli 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 daha küçük tutabilirsiniz. Yukarıda da belirttiğimiz
gibi bu testte iki hipotez vardır:
H0: Seçilen örnek normal dağılmış bir anakütleden gelmektedir.
H1: Seçilen örnek normal dağılmış bir anakütleden gelmemektedir.
2024-09-16 10:49:44 +03:00
Eğer test sonucunda elde ettiğimiz "p değeri belirlediğimiz kritik değereden (0.05) büyükse H0 hipotezi kabul edilir,
H1 hipotezi reddedilir. Eğer bu p değeri bu kritik değerden küçükse H0 hipotezi reddedilip, 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
2024-09-16 10:49:44 +03:00
fonksiyonuna sokulmuştur. Burada birinci örnek için p değeri 1e yakın, ikinci örnek için p değeri 0a çok yakın çıkmıştır.
Böylece birinci örneğin alındığı anakütlenin normal dağılmış olduğu ikinci örneğin alındığı anakütlenin ise normal
dağılmamış olduğu söylenebilir.
#----------------------------------------------------------------------------------------------------------------------------
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)
2024-09-16 10:49:44 +03:00
result_norm = kstest(sample_norm, 'norm', args=(100, 15))
sample_uniform = uniform.rvs(100, 100, size=1000)
2024-09-16 10:49:44 +03:00
result_uniform = kstest(sample_uniform, 'norm', args=(100, 15))
2024-09-16 10:49:44 +03:00
import matplotlib.pyplot as plt
plt.figure(figsize=(20, 8))
ax1 = plt.subplot(1, 2, 1)
ax1.set_title(f'p değeri: {result_norm.pvalue}', pad=15, fontweight='bold')
ax2 = plt.subplot(1, 2, 2)
ax2.set_title(f'p değeri: {result_uniform.pvalue}', pad=15, fontweight='bold')
ax1.hist(sample_norm, bins=20)
ax2.hist(sample_uniform, bins=20)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
Shapiro-Wilk testi scipy.stats modülündeki shapiro fonksiyonuyla yapılmaktadır. Fonksiyonun parametrik yapısı şöyledir:
2024-09-16 10:49:44 +03:00
shapiro(x, *, axis=None, nan_policy='propagate', keepdims=False)
Bu fonksiyonun kullanılması daha kolaydır. Bu fonksiyonun tek bir zorunlu 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 1000'lik 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)
2024-09-16 10:49:44 +03:00
result_norm = shapiro(sample_norm)
sample_uniform = uniform.rvs(100, 100, size=1000)
2024-09-16 10:49:44 +03:00
result_uniform = shapiro(sample_uniform)
import matplotlib.pyplot as plt
2024-09-16 10:49:44 +03:00
plt.figure(figsize=(20, 8))
ax1 = plt.subplot(1, 2, 1)
ax1.set_title(f'p değeri: {result_norm.pvalue}', pad=15, fontweight='bold')
ax2 = plt.subplot(1, 2, 2)
ax2.set_title(f'p değeri: {result_uniform.pvalue}', pad=15, fontweight='bold')
ax1.hist(sample_norm, bins=20)
ax2.hist(sample_uniform, bins=20)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
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
2024-09-16 10:49:44 +03:00
testi Kolmogorov-Simirnov testine göre daha iyi bir sonucun elde edilmesine yol açmaktadır. Yani örneğiniz küçükse Shapiro-Wilk
testini tercih edebilirsiniz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-10-03 14:16:20 +03:00
İstatistikte örneğe dayalı olarak anakütlenin ortalamasını (ve/veya standart sapmasını) tahmin etme sürecine "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 parametrelerinin 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.
2024-09-16 10:49:44 +03:00
Güven aralıkları bir anakütleden çekilen örneğe bağlı olarak anakütle parametrelerinin belli bir güven düzeyi (confidence
level) içerisinde aralıksal olarak belirlenmesini hedeflemektedir. Merkezi limit teoremine göre bir anakütleden çekilen
örneklemlerin ortalamalarının normal dağıldığını görmüştük. O halde biz bir anakütleden rastgele bir örnek seçip onun
ortalamasına bakarak anakütle ortalamasını belli bir güven düzeyinde tahmin edebiliriz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
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
2024-09-16 10:49:44 +03:00
soldan 0.025 ve sağdan 0.975 kümülatif olasılığa karşı gelen x değerlerinden biri olabilir. O halde 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ı elde ederek de" 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 daralma 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 olacak biçimde 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 (loc ve scale parametreleri) örneğin ortalamasını ve örneklem dağılımının
standart sapmasını belirtir. Yani biz ikinci parametreye örnek ortalamasını, üçüncü parametreye de anakütle standart
sapmasından hareketle elde ettiğimiz örneklem standart sapmasını (sigma / kök(n)) girmeliyiz. Metot güven aralığını belirten
bir demetle geri döner. Demetin ilk elemanı alt sınır, ikinci elemanı üst sınır 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 anakü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ı sanki anakütlenin
standart sapmasymış gibi işleme sokulmaktadır. Ancak dağılım olarak normal dağılım değil t dağılımı kullanılmaktadır. Zaten
William Gosset t dağılımını tamamen böyle bir problem üzerinde çalışırken 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 standart 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. Biz kursumuzda 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 kaça bölme 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 parametreleri default 0 iken Pandas'da 1'dir.) Yani bu ddof parametresi (N - değer)'deki değeri belirtmektedir. Örneğin
ddof = 0 ise bölme N'e ddof = 1 ise bölme (N - 1)'e yapılmaktadır.
Örneğin 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}]') # [94.81902512213665, 105.0959541612919]
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ığına dikkat edniz.
Örneklem dağılımının standart sapmasına standart hata denildiğ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ştuk:
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)
30 serbestlik derecesinden sonra artık t dağılımını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 kullanılsaydı da önemli bir fark oluşmayacaktı. 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 bu tür
işlemleri bilgisayarlarla yaptığımız için N >= 30 durumunda da t dağılımını kullanmak daha uygun olmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
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ı scipy.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 güven aralığının alt ve üst sıbır 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}]')
2024-10-03 14:16:20 +03:00
#----------------------------------------------------------------------------------------------------------------------------
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 hatalar 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
2024-10-03 14:16:20 +03:00
>= 30) t dağılımı normal çok yaklaşmaktadır.
2024-10-03 14:16:20 +03:00
Pekiyi örneğimiz küçükse (tipik olarak < 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ı ya da toplanmış olan
2024-10-03 14:16:20 +03:00
verilerin elde edilmesi elde edilmesi" 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
ya da topluluklar tarafından zaten oluşturulmuş durumdadır. Uygulamacının bu verileri ilgili yerden elde etmesi gerekir. Tabii
başka kurumlar ve topluluklar tarafından oluşturulmuş olan bu verileri kullanabilmeniz için bu kurumların ya da toplulukların
web sitelerine üye olmanız gerekebilir. Örneğin bunlardan en ünlüsü "kaggle.com" isimli sitedir. Bu siteye üye olmanızı tavsiye
ederiz. Kaggle 2010 yılında kurulmuştur ve 2017 yılında Google tarafından satın alınmıştır.
2024-10-11 00:32:36 +03:00
Veriler toplandığında onların bir biçimde saklanması gerekir. Veriler veritabanlarında ya da dosyalarda saklanabilirler.
Verilerin dosyalarda saklanmsı için çeşitli formatlar kullanılabilmektedir. Fakat önceden de belirttiğimiz gibi makine
öğrenmesi ve veri bilimi uygulamalarında en çok kullanılan formatlardan biri "CSV (Comma Separated Values)" formatıdır.
2024-10-11 00:32:36 +03:00
Eğer veriler veritabanlarının içerisindeyse uygulamacının ilgili veri tabanı yötetim sistemine bağlanıp verileri oradan
2024-10-03 14:16:20 +03:00
çekmesi gerekebilir. Bazı metinsel verilerin doğrudan web sitelerinden elde edilmesi de gerekebilmektedir. Biz kursumuzda
genellikle "CSV" ve "HDF (Hierarchical Data Format)" formatlarını kullanacağız. Ancak bazı uygulamalarında gerektiğinde
verileri veritabanlarından da çekeceğiz.
2024-10-11 00:32:36 +03:00
İstatistik ve veri biliminde organize edilmiş veri topluluklarına "veri kümeleri (data sets)" denilmektedir. Veri kümeleri
tipik olarak bir tablo gibi sütunlardan ve satırlardan oluşur. Veri kümesindeki sütunlara "sütun (column)" ya da "özellik
(feature)", satırlara ise "satır (row)" ya da "kayıt (record)" denilmektedir. Bu bakımdan veri kümelerinin ilişkisel
veritabanlarındaki tablolara benzediğine dikkat ediniz.
#----------------------------------------------------------------------------------------------------------------------------
<BURADA KALDIK>
#----------------------------------------------------------------------------------------------------------------------------
2024-10-03 14:16:20 +03:00
Veriler toplandıktan ya da elde edildiktan sonra hemen işleme sokulamayabilir. Veriler üzerinde çeşitli ön işlemler yapmak
gerekebilmektedir. Bu ön işlemlere "verilerin kullanıma hazır hale getirilmesi (data preparation)" denilmektedir. Biz bu
bölümde verilerin hazır hale getirilmesi için gerekli olabilecek bazı temel ön işlemler üzerinde duracağız. Ancak bazı ön
işlem etkinliklerini gerçekleştirebilmek için başka konuların bilinmesi gerekmektedir. Bu nedenle daha karmaşık ve alana
özel ön işlemler ilgili konuların açıklandığı bölümde ele alınacaktır.
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 bizim
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ü girdi 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 hangi sütunun 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):
2024-09-16 10:49:44 +03:00
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')
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-10-11 00:32:36 +03:00
Kategorik verilerin 0'dan itibaren LabelEncoder ya da OrdinalEncoder 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)
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
One-hot encoding uygulamanı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.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-10-11 00:32:36 +03:00
Binary encoding yönteminde her kategori "ikilik sistemde bir sayı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
2024-10-11 00:32:36 +03:00
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.
2024-10-11 00:32:36 +03:00
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 amacı 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.
ı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 genellikle "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 ise genellikle "regresyon" bazen de vurgulama
amacıyla "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üğü matematikteki "logaritma" sözcüğünün
kısaltmasından gelmektedir.
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 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 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)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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ı
2024-04-08 14:07:40 +03:00
çı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", regresyon problemleri için "mean squared error"
isimli loss fonksiyonları tercih edilmektedir.
2024-04-08 14:07:40 +03:00
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.
2024-04-08 14:07:40 +03:00
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 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üğü
2024-04-08 14:07:40 +03:00
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
2024-04-08 14:07:40 +03:00
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
2024-04-08 14:07:40 +03:00
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)
2024-04-08 14:07:40 +03:00
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
2024-04-08 14:07:40 +03:00
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
2024-04-08 14:07:40 +03:00
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ılabilmektedir. 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 yalnızca 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
2024-04-08 14:07:40 +03:00
metrics_names isimli bir örnek özniteliğ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}')
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
#----------------------------------------------------------------------------------------------------------------------------
28. Ders - 06/04/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
7) Artık model test de edilmiştir. Şimdi sıra "kestirim (prediction)" yapmaya gelmiştir. Kestirim işlemi için Sequential
2024-04-08 14:07:40 +03:00
sınıfının predict metodu kullanılır. Biz bu metoda girdi katmanına uygulanacak özelliklerin (yani satırların) değerlerini
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
2024-04-08 14:07:40 +03:00
ır
predict_dataset = np.array([[2 ,90, 68, 12, 120, 38.2, 0.503, 28],
[4, 111, 79, 47, 207, 37.1, 1.39, 56],
[3, 190, 65, 25, 130, 34, 0.271, 26],
[8, 176, 90, 34, 300, 50.7, 0.467, 58],
[7, 106, 92, 18, 200, 35, 0.300, 48]])
predict_result = model.predict(predict_data)
print(predict_result)
2024-04-08 14:07:40 +03:00
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. Örneğin ağın bir çıktısı varsa bu durumda predict metodu bize "n tane satırdan 1 tane
sütundan" oluşan bir matris, ağın iki çıktısı varsa "n tane satırdan 2 iki tane sütundan oluşan bir matris verecektir. O halde
örneğin çıktı olarak tek nöronun bulunduğu bir ağda ("diabetes" örneğindeki gibi) biz kestirim değerlerini şöyle yazdırabiliriz:
for i in range(len(predict_result)):
print(predict_result[i, 0])
2024-04-08 14:07:40 +03:00
Ya da şöyle yazdırabiliriz:
for result in predict_result[:, 0]:
print(result)
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)
2024-04-08 14:07:40 +03:00
predict metodu bize ağın çıktı değerini vermektedir. Yukarıdaki "diabetes.csv" örneğimizde ağın çıktı katmanındaki aktivasyon
fonksiyonunun "sigmoid" olduğunu anımsayınız. Sigmoid fonksiyonu 0 ile 1 arasında bir değer vermektedir. O halde biz ağın
çıktısındaki değer 0.5'ten büyükse ilgili kişinin şeker hastası olduğu (çünkü 1'e daha yakındır), 0.5'ten küçükse o kişinin
şeker hastası olmadığı (çünkü 0'a daha yakındır) sonucunu çıkartabiliriz. Tabii değer ne kadar 1'e yakınsa kişininin şeker
hastası olma olasılığı, değer ne kadar 0'a yakınsa kişinin şeker hastası olmama olasılığı o kadar yüksek olacaktır. O halde
sigmoid fonksiyonun çıktısının bir olasılık belirttiğini söyleyebiliriz. Bu durumda kişinin şeker hastası olup olmadığıın
çıktı değerinin 0.5'ten büyük olup olmamasıyla kesitirilebilir:
for result in predict_result[:, 0]:
print('Şeker hastası' if result > 0.5 else 'Şeker Hastası Değil')
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
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')
2024-04-08 14:07:40 +03:00
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
2024-04-08 14:07:40 +03:00
dataset = df.to_numpy()
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
2024-04-08 14:07:40 +03:00
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
2024-04-08 14:07:40 +03:00
from tensorflow.keras.layers import Input, Dense
model = Sequential(name='Diabetes')
2024-04-08 14:07:40 +03:00
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
2024-04-08 14:07:40 +03:00
model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2)
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
2024-04-08 14:07:40 +03:00
predict_dataset = np.array([[2 ,90, 68, 12, 120, 38.2, 0.503, 28],
[4, 111, 79, 47, 207, 37.1, 1.39, 56],
[3, 190, 65, 25, 130, 34, 0.271, 26],
[8, 176, 90, 34, 300, 50.7, 0.467, 58],
[7, 106, 92, 18, 200, 35, 0.300, 48]])
2024-04-08 14:07:40 +03:00
predict_result = model.predict(predict_dataset)
print(predict_result)
2024-04-08 14:07:40 +03:00
for result in predict_result[:, 0]:
print('Şeker hastası' if result > 0.5 else 'Şeker Hastası Değil')
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
Şimdi yukarıdaki adımların bazı detayları üzerinde duralım. Daha önceden de belirttiğimiz gibi pek çok problem iki saklı
katmanla ve Dense bir bağlantı ile tatminkar biçimde çözülebilmektedir. Dolayısıyla bizim ilk aklımıza gelen model iki saklı
katmanlı klasik 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ülememketedir. Bu durumda ikiden fazla saklı katman kullanılır. Bu modellere "derin
öğrenme (deep learning)" modelleri de denilmektedir.
2024-04-08 14:07:40 +03:00
Girdi katmanındaki nöron sayısı zaten problemdeki sütun sayısı kadar (özellik 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ıdır. İkili sınıflandırma
(binary classification) problemlerinde çıktı katmanı tek nörondan, çoklu sınıflandırma problemlerinde (multiclass classification)
çıktı katmanı sınıf sayısı kadar nörondan oluşur. Regresyon problemlerinde ise çıktı katmanındaki nöron
sayıları genellikle bir tane olmaktadır.
2024-04-08 14:07:40 +03:00
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ı olarak ayarlanabilmektedir. Örneğin eğitimde kullanılacak veri miktarı, problemin karmaşıklığı, hyper
parametrelerin durumları saklı katmanlardaki nöron sayıları üzerinde 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.
2024-04-08 14:07:40 +03:00
- Saklı katmanlarda gereksiz biçimde fazla sayıda nöron bulundurmak eğitim süresini uzatabileceği gibi "overfitting" durumuna
da yol açabilir. Aynı zamanda modelin diskte saklanması için gereken disk alanını da artırabilmektedir.
- Eğitim veri kümesi azsa saklı katmanlardaki nöron sayıları düşürülebilir.
- Pek çok problemde saklı katmanlardaki nöron sayıları çok fazla ya da çok az olmadıktan sonra önemli olmayabilir.
- Saklı katmanlardaki nöron sayısı girdi katmanındaki nöron sayısından az olmamlıdır.
2024-04-08 14:07:40 +03:00
Çeşitli kaynaklar saklı katmanlardaki nöronların sayıları için üstünkörü şu pratik tavsiyelerde bulunmaktadır:
2024-04-08 14:07:40 +03:00
- Saklı katmanlardaki nöronların sayıları girdi katmanındaki nöronların sayıları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ı 4 ya da 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.
2024-04-08 14:07:40 +03:00
Biz genellikle genel problemlerde iki saklı katman ve katmalarda da 16, 32, 64, 100, 128 gibi sayılarda nöron kullanacağız.
Ancak ileride özel mimarilerde bu durumu farklılaştıracağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
Yapay sinir ağı her farklı eğitimde farklı "w" ve "bias" değerlerini oluşturabilir. Bu nedenle ağın peformans değerleri de
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.
2024-04-08 14:07:40 +03:00
2) Katmanlardaki "w" değerleri (ve istersek "bias" değerleri) programın her çalıştırılmasında rastgele başlangıç değerleriyle
set edilmektedir.
3) fit işleminde her epoch sonrasında veri kümesi yeniden karıştırılmaktadır.
2024-04-08 14:07:40 +03:00
Bir rastgele sayı üretiminde ü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 NumPy ve Tensorflow gibi kütüphanelerde bu
tohum değeri programın her çalıştırılmasında rastgele biçimde bilgisayarın saatinden hareketle oluşturmaktadı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. Tipik yapılması gereken birkaç şeyi şöyle belirtebiliriz:
1) scikit-learn ve diğer bazı makine öğrenmesi kütüphanelerinde aşağı seviyeli kütüphane olarak NumPy kullanıldığı için NumPy'ın
rassal sayı üreticisinin tohum değeri belli bir değerle set edilebilir. Örneğin:
import numpy as np
np.random.seed(12345)
2) Tensorflow kütüphanesi bazı işlemlerde kendi rassal sayı üreticisini kullanmaktadır. Onun tohum değeri de belli bir değerle
set edilebilir. Örneğin:
from tensorflow.keras.utils import set_random_seed
set_random_seed(78901)
Tabii yukarıdaki işlemler yapılsa bile rassal sayı üretimi "reproducible" hale getirilemeyebilir. Çünkü bu durum bu kütüphanelerin
rassal sayı üretiminin hangi kütüpaheneler kullanılarak yapıldığı ile ilgilidir. Yukarıdaki iki madde sezgisel bir çıkarımı
ifade etmekltedir.
2024-04-08 14:07:40 +03:00
Pekiyi neden ağın her eğitilmesinde aynı sonuçların elde edilmesini (yani ağın "reproducible" sonuçlar vermesini) isteyebiliriz?
İşte bazen modellerimizde ve algoritmalarımızda yaptığımız değişiklikleri birbirleriyle kıyaslamak isteyebiliriz. Bu durumda
kıyaslamanın herp aynı biçimde yapılmasını sağlayabilmek için rassal bir biçimde alınan değerlerin her çalıştırmada aynı
değerler olmasını sağlamamız gerekir. Tabii aslında algoritmaları karşılaştırmak için bu biçimde "reproducible" rassal sayı
üretimi yapmak yerine algoritmaları çokça çalıştırıp bir ortalama değere de bakılabilir. Bu yöntem genellikle daha iyi bir
karşılaştırma olanağı sunmaktadır.
O halde biz yukarıda belirttiğimiz iki ayarlamayı yaparak "diabetes" modelimizi çalıştırırsak bu durumda her eğitimde ve
test işleminde aynı sonucu elde edebiliriz. Aşağıda buna bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
2024-04-08 14:07:40 +03:00
import numpy as np
from tensorflow.keras.utils import set_random_seed
np.random.seed(1234567)
set_random_seed(678901)
df = pd.read_csv('diabetes.csv')
2024-04-08 14:07:40 +03:00
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
2024-04-08 14:07:40 +03:00
dataset = df.to_numpy()
dataset_x = dataset[:, :-1]
dataset_y = dataset[:, -1]
from sklearn.model_selection import train_test_split
2024-04-08 14:07:40 +03:00
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
2024-04-08 14:07:40 +03:00
from tensorflow.keras.layers import Input, Dense
2024-04-08 14:07:40 +03:00
model = Sequential(name='Diabetes')
2024-04-08 14:07:40 +03:00
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
2024-04-08 14:07:40 +03:00
model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2)
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
2024-04-08 14:07:40 +03:00
predict_dataset = np.array([[2 ,90, 68, 12, 120, 38.2, 0.503, 28],
[4, 111, 79, 47, 207, 37.1, 1.39, 56],
[3, 190, 65, 25, 130, 34, 0.271, 26],
[8, 176, 90, 34, 300, 50.7, 0.467, 58],
[7, 106, 92, 18, 200, 35, 0.300, 48]])
2024-04-08 14:07:40 +03:00
predict_result = model.predict(predict_dataset)
print(predict_result)
2024-04-08 14:07:40 +03:00
for result in predict_result[:, 0]:
print('Şeker hastası' if result > 0.5 else 'Şeker Hastası Değil')
2024-04-08 14:07:40 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de dikkatimizi katmanlardaki aktivasyon fonksiyonlarına yöneltelim. Katmanlardaki aktivasyon fonksiyonları ne olmalıdır?
Girdi katmanı gerçek bir katman olmadığına göre orada bir aktivasyon fonksiyonu yoktur. Saklı katmanlardaki aktivasyon
fonksiyonları için çeşitli seçenekler bulunmaktadır. Biz de bu bölümde belli başlı aktivasyon fonksiyonlarını daha ayrıntılı
olarak ele alacağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
Özellikle son yıllarda saklı katmanlarda en fazla tercih edilen aktivasyon fonksiyonu "relu (rectified linear unit)" denilen
aktivasyon fonksiyonudur. Bu fonksiyona İngilizce "rectifier" da denilmektedir. Relu fonksiyonu şöyledir:
x >= 0 ise y = x
2024-04-08 14:07:40 +03:00
x < 0 ise y = 0
Yani relu fonksiyonu x değeri 0'dan büyük ise (ya da eşit ise) aynı değeri veren, x değeri 0'dan küçük ise 0 değerini veren
fonksiyondur. relu fonksiyonunu basit bir biçimde aşağıdaki gibi yazabiliriz:
def relu(x):
return np.maximum(x, 0)
NumPy kütüphanesinin maximum fonksiyonunun birinci parametresi bir NumPy dizisi ya da Python listesi biçiminde girilirse
fonksiyon bu dizinin ya da listenin her elemanı ile maximum işlemi yapmaktadır. Örneğin:
>>> import numpy as np
>>> x = [10, -4, 5, 8, -2]
>>> y = np.maximum(x, 3)
>>> y
array([10, 3, 5, 8, 3])
Relu fonksiyonun grafiği aşağıdaki gibi çizilebilir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
2024-04-08 14:07:40 +03:00
def relu(x):
return np.maximum(x, 0)
x = np.linspace(-10, 10, 1000)
2024-04-08 14:07:40 +03:00
y = relu(x)
2024-04-08 14:07:40 +03:00
plt.title('Relu Function', fontsize=14, fontweight='bold', pad=20)
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)
2024-04-08 14:07:40 +03:00
plt.plot(x, y, color='red')
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
Aktivasyon fonksiyonları katman nesnelerine isimsel olarak girilebileceği gibi tensorflow.keras.activations modülündeki
fonksiyonlar biçiminde de girilebilmektedir. Örneğin:
from tensorflow.keras.activations import relu
...
model.add(Dense(16, activation=relu, name='Hidden'))
2024-04-08 14:07:40 +03:00
Bu modüldeki fonksiyonlar keras Tensorflow kullanılarak yazıldığı için çıktı olarak Tensor nesneleri vermektedir. Biz relu
grafik çizdirirken fonksiyonunu kendimiz yazmak yerine tensorflow içerisindeki fonksiyonu doğrudan da kullanabiliriz. Tensor
nesnelerinin NumPy dizilerine dönüştürülmesi için Tensor sınıfının numpy metodu kullanılabilir. Örneğin:
2024-04-08 14:07:40 +03:00
from tensorflow.keras.activations import relu
x = np.linspace(-10, 10, 1000)
y = relu(x).numpy()
Aşağıdakiş örnekte relu fonksiyonun grafiği Tensorflow'daki relu fonksiyonu yardımıyla çizdirilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
import numpy as np
import matplotlib.pyplot as plt
2024-04-08 14:07:40 +03:00
def relu(x):
return np.maximum(x, 0)
x = np.linspace(-10, 10, 1000)
# y = relu(x)
from tensorflow.keras.activations import relu
y = relu(x).numpy()
plt.title('Relu Function', fontsize=14, fontweight='bold', pad=20)
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, color='red')
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
İkili sınıflandırma problemlerinde çıktı katmanında en fazla kullanılan aktivasyon fonksiyonu "sigmoid" denilen fonksiyondur.
Yukarıdaki "diabetes" örneğinde biz çıktı katmanında sigmoid fonksiyonunu kullanmıştık. Gerçekten de ikili sınıflandırma
problemlerinde ağın çıktı katmanında tek bir nöron bulunur ve bu nörounun da aktivasyon fonksiyonu "sigmoid" olur.
2024-04-08 14:07:40 +03:00
Pekiyi sigmoid nasıl bir fonksiyondur? Bu fonksiyona "lojistik (logistic)" fonksiyonu da denilmektedir. Fonksiyonun matematiksel
ifadesi şöyledir:
y = 1 / (1 + e ** -x)
2024-04-08 14:07:40 +03:00
Burada e değeri 2.71828... biçiminde irrasyonel bir değerdir. Yukarıdaki kesrin pay ve paydası e ** x ile çarpılırsa fonksiyon
aşağıdaki gibi de ifade edilebilir:
2024-04-08 14:07:40 +03:00
y = e ** x / (1 + e ** x)
2024-04-08 14:07:40 +03:00
Fonksiyona "sigmoid" isminin verilmesinin nedeni S şekline benzemesinden dolayıdır. Sigmoid eğrisi x = 0 için 0.5 değerini
veren x pozitif yönde arttıkça 1 değerine hızla yaklaşan, x negatif yönde arttıkça 0 değerine hızla yaklaşan S şeklinde bir
eğridir. Sigmoid fonksiyonunun (0, 1) arasında bir değer verdiğine dikkat ediniz. x değeri artıkça eğri 1'e yaklaşır ancak
hiçbir zaman 1 olmaz. Benzer biçimde x değeri azaldıkça eğri 0'a yaklaşır ancak hiçbir zaman 0 olmaz.
Sigmoid fonksiyonu makine öğrenmesinde ve istatistikte belli bir gerçek değeri 0 ile 1 arasına hapsetmek için sıkça
kullanılmaktadır. Sigmoid çıktısı aslında bir bakımdan kestirimin 1 olma olasılığını vermektedir. Tabii biz kestirimde
bulunurken kesin bir yargı belirteceğimiz için eğrinin orta noktası olan 0.5 değerini referans alırız. Ağın ürettiği değer
0.5'ten büyükse bunu 1 gibi, 0.5'ten küçükse 0 gibi değerlendiririz.
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()
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
Yine benzer biçimde tensorflow.keras.activations modülü içerisinde sigmoid fonksiyonu zaten hazır biçimde bulunmaktadır.
Tabii bu fonksiyon da bize Tensorflow'daki bir Tensor nesnesini vermektedir. Aşağıda sigmoid eğrisini bu hazır fonksiyonu
kullanarak çizdiriyoruz.
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
import numpy as np
import matplotlib.pyplot as plt
2024-04-08 14:07:40 +03:00
from tensorflow.keras.activations import sigmoid
x = np.linspace(-10, 10, 1000)
y = sigmoid(x).numpy()
plt.title('Sigmoid (Logistic) Function', fontsize=14, fontweight='bold', pad=20)
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, color='red')
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
Sigmoid fonksiyonu nasıl elde edilmiştir? Aslında bu fonksiyonun elde edilmesinin bazı mantıksal gerekçeleri vardır.
Sigmoid fonksiyonunun birinci türevi Gauss eğrisine benzemektedir. Aşağıdaki örnekte Sigmoid fonksiyonunun birinci türevi
alınıp eğrisi çizdirilmiştir. Ancak bu örnekte henüz görmediğimiz SymPy kütüphanesini kullandık. Sgmoid fonksiyonun birinci
türevi şöyledir:
2024-04-08 14:07:40 +03:00
sigmoid'(x) = exp(x)/(exp(x) + 1) - exp(2 * x)/(exp(x) + 1) ** 2
#----------------------------------------------------------------------------------------------------------------------------
import sympy
from sympy import init_printing
init_printing()
2024-04-08 14:07:40 +03:00
x = sympy.Symbol('x')
fx = sympy.E ** x / (1 + sympy.E ** x)
dx = sympy.diff(fx, x)
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()
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
Diğer çok kullanılan bir aktivasyon fonksiyonu da "hiperbolik tanjant" fonksiyonudur. Bu fonksiyona kısaca "tanh" fonksiyonu
da denilmektedir. Fonksiyonun matematiksel ifadesi şöyledir:
f(x) = (e ** (2 * x) - 1) / (e ** (2 * x) + 1)
2024-04-08 14:07:40 +03:00
Fonksiyonun sigmoid fonksiyonuna benzediğine ancak üstel ifadenin x yerine 2 * x olduğuna dikkat ediniz. Tanh fonksiyonu adeta
sigmoid fonksiyonunun (-1, +1) arası değer veren biçimi gibidir. Fonksiyon yine S şekli biçimindedir. Ancak noktası x = 0'dadır.
Tanh fonksiyonu saklı katmanlarda da bazen çıktı katmanlarında da kullanılabilmektedir. Eskiden bu fonksiyon saklı katmanlara
çok yoğun kullanılıyordu. Ancak artık saklı katmanlarda daha çok relu fonksiyonu tercih edilmektedir. Fakat tanh fonksiyonunun
daha iyi sonuç verdiği modeller de söz konusu olmaktadır.
2024-04-08 14:07:40 +03:00
tanh fonksiyonu Keras'ta tensorflow.keras.activations modülünde tanh ismiyle de bulunmaktadır.
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
2024-04-08 14:07:40 +03:00
problemlerinde çıktı katmanlarında kullanılmaktadır. Bu aktivasyon fonksiyonu Keras'ta "softmax" ismiyle bulunmaktadı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
2024-04-08 14:07:40 +03:00
logistic regression)" da denilmektedir. Bu tür problemlerde sinir ağında sınıf sayısı kadar nöron bulundurulur. Örneğin
yukarıdaki "elma", "armut", "kayısı", "şeftali", "karpuz" resim sınıflandırma probleminde ağın çıktısında 5 nöron bulunacaktır.
2024-04-08 14:07:40 +03:00
ı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 çıktı katmanındaki nöronların çıktı değerleri ilgili sınıfın olasılığını belirtir
hale gelir. 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 kabul ederiz.
Örneğin yukarıdaki "elma", "armut", "kayısı", "şeftali", "karpuz" sınıflandırma probleminde ağın çıktı katmanındaki nöronların
çıktı değerlerinin şöyle olduğunu varsayalım:
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
2024-04-08 14:07:40 +03:00
karpuz Nöronunun Çıktısı ---> 0.1
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 bir grup değer için o grup değerlere bağlı olarak şöyle hesaplanmaktadır:
2024-04-08 14:07:40 +03:00
softmax(x) = np.e ** x / np.sum(np.e ** x)
Burada gruptaki değerler x vektörüyle temsil edilmektedir. Fonksiyonda değerlerinin e tabanına göre kuvvetleri x değerlerinin
e tabanına göre kuvvetlerinin toplamına bölünmüştür. Bu işlemden yine gruptaki eleman sayısı kadar değer elde edilecektir.
Tabii bu değerlerin toplamı da 1 olacaktır. Örneğin elimizde aşağıdaki gibi x değerleri olsun:
x = np.array([3, 6, 4, 1, 7])
Şimdi bu x değerlerinin softmax değerlerini elde edelim:
>>> import numpy as np
>>> x = np.array([3, 6, 4, 1, 7])
>>> x
array([3, 6, 4, 1, 7])
>> 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
2024-04-08 14:07:40 +03:00
softmax fonksiyonu Keras'ta tensorflow.keras.activations modülünde softmax ismiyle de bulunmaktadır. Ancak bu fonksiyonu
kullanırken girdinin Tensorflow'daki bir Tensor nesnesi biçiminde ve iki boyutlu olarak verilmiş olması gerekmektedir.
Tensorflow kütüphanesindeki aktivasyon fonksiyonları dışarıdan değil Tensorflow içerisinden kullanılsın diye tasarlanmıştır.
Bu nedenle softmax gibi bazı fonksiyonlarda biz NumPy dizisi verememekteyiz. Ayrıca Tensorflow'daki bu aktivasyon fonksiyonları
birden fazla değer üzerinde de bir Tensor olarak işlem yapabilmektedir. (softmax fonksiyonunda aslında bir değer bir grup
değerden oluştuğu için girdi olarak da bizden iki boyutlu bir Tensor istenmektedir.) Örneğin:
>>> import numpy as np
>>> import tensorflow as tf
>>> from tensorflow.keras.activations import softmax
>>> x = np.array([[1, 2, 3, 4, 5]], dtype=np.float64)
>>> t = tf.convert_to_tensor(x)
>>> result = softmax(t)
>>> result
<tf.Tensor: shape=(1, 5), dtype=float64, numpy=array([[0.01165623, 0.03168492, 0.08612854, 0.23412166, 0.63640865]])>
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.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
Diğer çok kullanılan aktivasyon fonksiyonlarından biri de "linear" aktivasyon fonksiyonudur. Aslında bu fonksiyon y = x
ya da f(x) = x fonksiyonudur. Yani "linear" fonksiyonu 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 "regresyon problemlerinde
(lojistik olmayan regresyon problemlerinde)" çıktı katmanında kullanılmaktadır. Regresyon problemleri çıktı olarak bir sınıf
bilgisi değil gerçek bir değer bulmaya çalışan problemlerdir. Örneğin bir evin fiyatının kestirilmesi, bir otomobilin mil
başına yaktığı yakıt miktarının kestirilemsi gibi problemler lojistik olmayan regresyon problemleridir. (Anımsanacağ gibi
biz kursumuzda bir sayı kestirmek için kullanılan regresyon modellerine vurgulama amaçlı bazen "lojistik olmayan regresyon
modelleri)" de diyoruz. Aslında "regresyon modeli" denildiğinde zaten default olarak lojistik olmayan regresyon modelleri
2024-04-08 14:07:40 +03:00
anlaşılmaktadır.)
linear aktivasyon fonksiyonu Keras'ta "linear" ismiyle kullanılmaktadır. Her ne kadar bir şey yapmıyorsa da bu aktivasyon
fonksiyonu aynı zamanda tensorflow.keras.activations modülünde linear isimli bir fonksiyon biçiminde de bulunmaktadır.
Örneğin:
2024-04-08 14:07:40 +03:00
>>> from tensorflow.keras.activations import linear
>>> x = [1, 2, 3, 4, 5]
>>> x = np.array([1, 2, 3, 4, 5], dtype=np.float64)
>>> result = linear(x)
>>> result
array([1., 2., 3., 4., 5.])
linear fonksiyonunun grafiğini -bariz olmasına karşın- aşağıda veriyoruz.
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
import numpy as np
import matplotlib.pyplot as plt
def linear(x):
return x
x = np.linspace(-10, 10, 1000)
y = linear(x)
"""
from tensorflow.keras.activations import linear
y = linear(x).numpy()
"""
plt.title('Linear Function', fontsize=14, fontweight='bold', pad=20)
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, color='red')
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
Biz yukarıda çok karşılaşılan temel aktivasyon fonksiyonlarını ele aldık. Aslında yukarıda ele aldığımızdan daha fazla
aktivasyon fonksiyonu vardır. Şimdi de aoptimizasyon algoritmasının minimize etmeye çalıştığı "loss" fonksiyonları
üzerinde duracağız.
2024-04-08 14:07:40 +03:00
loss fonksiyonları gerçek değerlerle ağın tahmin ettiği değerleri girdi olarak alıp bu farklılığı bir sayısal sayısal değerle
ifade eden fonksiyonlardır. Optimizasyon algoritmaları bu loss fonksiyonlarının değerini düşürmeye çalışmaktadır. Gerçek
değerlerle ağın ürettiği değerlerin arasındaki farkın minimize edilmesi aslında ağın gerçek değerlere yakın değerler üretmesi
anlamına gelmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
Anımsanacağı gibi "w" ve "bias" değerleri "optimizer" denilen algoritma tarafından her batch işleminde güncellenmekteydi.
Yukarıda da belirttiğimiz gibi 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 girebilir. (Loss fonksiyonları için Tensorflow hem callable sınıflar hem de fonksiyonlar bulundurmuştur.)
2024-04-08 14:07:40 +03:00
Eğitim batch batch yapıldığı için loss fonksiyonları 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ğerleri
hesaplanmaktadır. Örneğin batch_size = 32 olduğu durumda aslında Keras ağa 32'lik bir giriş uygulayıp 32'lik bir çıktı elde
eder. Bu 32 çıktı değeri gerçek 32 değerle loss fonksiyonuna sokulur.
2024-04-08 14:07:40 +03:00
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Regresyon problemleri için en yaygın kullanılan loss fonksiyonu "Mean Squared Error (MSE)" denilen fonksiyondur. Bu fonksiyona
Türkçe "Ortalama Karesel Hata" diyebiliriz. MSE fonksiyonu çıktı olarak gerçek değerlerden kestirilen değerlerin farkının
karelerinin ortalamasını vermektedr. Fonksiyonun sembolik gösterimi şöyledir:
2024-04-08 14:07:40 +03:00
mse = np.mean((y - y_hat) ** 2)
2024-04-08 14:07:40 +03:00
Burada y gerçek değerleri, y_hat ise kestirilen değerleri belirtmektedir. Örneğin:
2024-04-08 14:07:40 +03:00
>>> y = np.array([1, 2, 3, 4, 5])
>>> y_hat = np.array([1.1, 1.9, 3.2, 3.8, 5.02])
>>> np.mean((y - y_hat) ** 2)
0.020080000000000032
2024-04-08 14:07:40 +03:00
Aynı işlemi tensorflow.keras.losses modülündeki mse (ya da mean_squared_error) fonksiyonuyla da aşağıdaki gibi yapabilirdik:
2024-04-08 14:07:40 +03:00
>>> from tensorflow.keras.losses import mse
>>> mse(y, y_hat)
<tf.Tensor: shape=(), dtype=float64, numpy=0.020080000000000032>
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Regresyon problemleri için kullanılabilen diğer bir loss fonksiyonu da "Mean Absolute Error (MAE)" isimli fonksiyondur.
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:
2024-04-08 14:07:40 +03:00
mae = np.mean(np.abs(y - y_hat))
Burada y gerçek değerleri y_hat ise ağın kestirdiği değerleri belirtmektedir. Regresyon problemleri için loss fonksiyonu
olarak çoğu kez MSE tercih edilmektedir. Çünkü kare alma işlemi algoritmalar için daha uygun bir işlemdir. Aynı zamanda d
eğerleri daha fazla farklılaştırmaktadır. MAE loss fonksiyonundan ziyade metrik değer olarak "insan algısına
2024-04-08 14:07:40 +03:00
>>> y = np.array([1, 2, 3, 4, 5], dtype=np.float64)
>>> y_hat = np.array([1.1, 1.9, 3.2, 3.8, 5.02])
>>> mae = np.mean(np.abs(y - y_hat))
>>> mae
0.12400000000000003
2024-04-08 14:07:40 +03:00
Ortalama karesel hata bir metrik değer olarak bizim için iyi bir çağrışım yapmamaktadır. Halbuki ortalama mutlak hata
bizim için anlamlı bir çağrışım yapmaktadır. Örneğin ağımızın ortalama mutlak hatası 0.124 ise gerçek değer ağımızın bulduğu
değerden 0.124 solda ya da sağda olabilir. Yine ortalama mutlak hata tensorflow.keras.losses modülü içerisindeki "mae" ya da
"mean_absolute_error" isimli fonksiyonla da hesaplanabilmektedir. Örneğin:
2024-04-08 14:07:40 +03:00
>>> from tensorflow.keras.losses import mae
>>> y = np.array([1, 2, 3, 4, 5], dtype=np.float64)
>>> y_hat = np.array([1.1, 1.9, 3.2, 3.8, 5.02])
>>> result = mae(y, y_hat)
>>> result
<tf.Tensor: shape=(), dtype=float64, numpy=0.12400000000000003>
#----------------------------------------------------------------------------------------------------------------------------
2024-04-08 14:07:40 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Regresyon problemleri için diğer bir loss fonksiyonu da "Mean Absolute Percentage Error (MAPE)" isimli fonksiyondur. Fonkisyonun
sembolik ifadesi şöyledir:
2024-04-08 14:07:40 +03:00
mape = 100 * np.mean(np.abs(y - y_hat) / y)
2024-04-08 14:07:40 +03:00
Burada y gerçek değerleri y_hat ise ağın kestirdiği değerleri belirtmektedir. Örneğin:
>>> y = np.array([1, 2, 3, 4, 5], dtype=np.float64)
>>> y_hat = np.array([1.1, 1.9, 3.2, 3.8, 5.02])
>>> mape = 100 * np.mean(np.abs(y - y_hat) / y)
>>> mape
5.413333333333335
Tabii aynı işlemi yine tensorflow.keras.losses modülündeki "mape" fonksiyonuyla da yapabiliriz:
>>> from tensorflow.keras.losses import mape
>>> y = np.array([1, 2, 3, 4, 5], dtype=np.float64)
>>> y_hat = np.array([1.1, 1.9, 3.2, 3.8, 5.02])
>>> result = mape(y, y_hat)
>>> result
<tf.Tensor: shape=(), dtype=float64, numpy=5.413333333333335>
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Regresyon problemleri için diğer bir loss fonksiyonu da "Mean Squared Logarithmic Error (MSLE)" isimli fonksiyondur. Bu
fonksiyon gerçek değerlerle kestirilen değerlerin logaritmalarının farklarının karelerinin ortalaması biçiminde hesaplanır.
Sembolik ifadesi şöyledir:
2024-04-08 14:07:40 +03:00
msle = np.mean((np.log(y) - np.log(y_hat)) ** 2)
Bazen bu fonksiyon gerçek ve kestirilen değerlere 1 toplanarak da oluşturulabilmektedir (Tensorflow "msle" fonksiyonunu
bu biçimde kullanmaktadır):
2024-04-08 14:07:40 +03:00
msle = np.mean((np.log(y + 1) - np.log(y_hat + 1)) ** 2)
Örneğin:
2024-04-08 14:07:40 +03:00
>>> y = np.array([1, 2, 3, 4, 5], dtype=np.float64)
>>> y_hat = np.array([1.1, 1.9, 3.2, 3.8, 5.02])
>>> msle = np.mean((np.log(y + 1) - np.log(y_hat + 1)) ** 2)
>>> msle
0.0015175569737783628
Aynı işlemi tensorflow.keras.losses modülündeki "msle" fonksiyonuyla da yapabiliriz:
>>> from tensorflow.keras.losses import msle
>>> y = np.array([1, 2, 3, 4, 5], dtype=np.float64)
>>> y_hat = np.array([1.1, 1.9, 3.2, 3.8, 5.02])
>>> result = msle(y, y_hat)
>>> result
<tf.Tensor: shape=(), dtype=float64, numpy=0.0015175569737783555>
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
İkili sınıflandırma problemleri için en yaygın kullanılan loss fonksiyonu "Binary Cross-Entropy (BCE)" denilen fonksiyondur.
Bu fonksiyonun sembolik gösterimi şöyledir:
bce = -np.mean(y * np.log(y_hat) + (1 - y) * np.log(1 - y_hat))
Burada bir toplam teriminin olduğunu görüyorsunuz. Gerçek değerler 0 ya da 1 olduğuna göre bu toplam teriminin ya sol tarafı
ya da sağ tarafı 0 olacaktır.. Burada yapılmak istenen şey aslında isabet olasılığının logaritmalarının ortalamasının alınmasıdır.
Örneğin gerçek y değeri 0 olsun ve ağ da sigmoid çıktısından 0.1 elde etmiş olsun. Bu durumda toplam fadesinin sol tarafı 0,
sağ tarafı ise log(0.9) olacaktır. Şimdi gerçek değerin 1 ancak ağın sigmoid çıktısından elde edilen değerim 0.9 olduğunu
düşününelim. Bu kez toplamın sağ tarafı 0, sol tarafı ise log(0.9) olacaktır. İşte fonksiyonda bu biçimde isabet olasılıklarının
logaritmalarının ortalaması bulunmaktadır. Örneğin:
>>> y = np.array([1, 0, 1, 1, 0])
>>> y_hat = np.array([0.9, 0.05, 0.095, 0.89, 0.111])
>>> -np.mean(y * np.log(y_hat) + (1 - y) * np.log(1 - y_hat))
0.5489448114302314
Aynı işlemi tensorflow.keras.losses modülündeki binary_crossentropy isimli fonksiyonla da yapabiliriz:
>>> y_hat = np.array([0.9, 0.05, 0.095, 0.89, 0.111])
>>> result = binary_crossentropy(y, y_hat)
>>> result
<tf.Tensor: shape=(), dtype=float64, numpy=0.5489445126600796>
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
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 da 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. (Yani örneğin ileride göreceğimiz gibi K sınıflı bir sınıflandırma
problemi için biz ağa çıktı olarak "one-hot encoding" kodlanmış K tane y değerini vermeliyiz.) K tane sınıf belirtem bir
satırın CCE değeri şöyle hesaplanır (tabii burada K tane "one-hot encoding" edilmiş gerçek değer ile M tane softmax çıktı
değeri söz konusu olmalıdır):
2024-04-08 14:07:40 +03:00
cce = -np.sum(yk * log(yk_hat))
Burada yk one-hot encoding yapılmış gerçek değerleri yk_hat ise softmax biçiminde elde edilmiş ağın çıktı katmanındaki
değerleri temsil etmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
30. Ders - 20/04/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
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 metrik fonksiyonlar hazır biçimde
bulunmaktadır. Birden fazla metrik 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
fonksiyonlar ve sınıflar biçiminde de girilebilmektedir.
Metrik fonksiyonlar da tıpkı loss fonksiyonları gibi gerçek çıktı değerleriyle ağın ürettiği çıktı değerlerini parametre
olarak almaktadır. Aslında loss fonksiyonları da bir çeşit metirk fonksiyonlardır. Ancak loss fonksiyonları optimizasyon
algoritmaları tarafından minimize edilmek için kullanılmaktadır. Halbuki metrikm fonksiyonlar bizim durumu daha iyi anlamamız
için bizim tarafımızdan kullanılmaktadır.
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.
Özetle er loss fonksiyonu bir metrik fonksiyon gibi de kullanılabilir. Ancak her metrik fonksiyon bir loss fonksiyonu olarak
kullanılmaz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
"binary_accuracy" isimli metrik fonksiyon ikili sınıflandırma problemleri için en yaygın kullanılan metrik fonksiyondur.
Bu fonksiyon kabaca kaç gözlemin değerinin kestirilen değerle aynı olduğunun yüzdesini vermektedir. Örneğin "diabetes.csv"
veri kümesinde "binary_accuracy" değerinin 0.70 olması demek her yüz ölçümün 70 tanesinin doğru biçimde kestirilmesi demektir.
"binary_accuracy" metrik değeri Keras'ta isimsel olarak girilebileceği gibi tensorflow.keras.metrics modülündeki fonksiyon
ismi olarak da girilebilir.
Aslında Keras'ta programcı kendi loss fonksiyonlarını ve metrik fonksiyonlarını da yazabilir. Ancak tensorflow bu konuda
yeterli dokümantasyona sahip değildir. Tensorflow kütüphanesinin çeşitli versiyonlarında çeşitli farklılıklar bulunabilmektedir.
Bu nedenle bu fonksiyonların programcı tarafından yazılması için bu konuya dikkat etmek gerekir.
Örneğin biz tensorflow.keras.metrics modülündeki binary_accuracy fonksiyonunu aşağıdaki gibi kullanabiliriz.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.metrics import binary_accuracy
import numpy as np
y = np.array([1, 0, 1, 1, 0])
y_hat = np.array([0.90, 0.7, 0.6, 0.9, 0.6])
result = binary_accuracy(y, y_hat)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Çok sınıflı sınıflandırma problemlerinde tipik okullanılan metrik fonksiyon "categorical_accuracy" isimli fonksiyondur.
2024-04-08 14:07:40 +03:00
Bu fonksiyon da yine gözlemlerin yüzde kaçının tam olarak isabet ettirildiğini belirtmektedir. Örneğin ikili 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 edilmektedir. Ancak
100 sınıflı bir problemde 0.50 başarı düşük bir başarı olmayabilir. Yine biz Keras'ta "categorical_accuracy" metirk fonksiyonunu
isimsel biçimde ya da tensorflow.keras.metrics modülündeki fonksiyon ismiyle kullanabiliriz.
tensorflow.keras.metrics modülündeki categorical_accuracy fonksiyonu aslında toplam isabetlere ilişkin bir Tensor nesnesi
vermektedir, ortalama vermemektedir. Aşağıda bu fonksiyonun kullanımına bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.metrics import categorical_accuracy
import numpy as np
# elma, armut, kayısı
y = np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0], [0, 1, 0], [1, 0, 0]])
y_hat = np.array([[0.2, 0.7, 0.1], [0.2, 0.1, 0.7], [0.8, 0.1, 0.1], [0.7, 0.2, 0.1], [0.6, 0.2, 0.2]])
result = categorical_accuracy(y, y_hat)
result_ratio = np.sum(result) / len(result)
print(result_ratio)
#----------------------------------------------------------------------------------------------------------------------------
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 fonksiyon 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 regresyon problemlerinde "mean_asbolute_percentage_error", "mean_squared_logarithmic_error"
fonksiyonları da metrik olarak kullanılabilmektedir.
Loss fonksiyonların metrik fonksiyonlar olarak kullanılabileceğini belirtmiştik. Aslında örneğin mean_absolute_error
loss fonksiyonu ile mean_absolute_error metrik fonksiyonu aynı işlemi yapmaktadır. Ancak Keras'ta bu fonksiyonlar
tensorflow.keras.losses ve tensorflow.keras.metrics modüllerinde ayrı ayrı bulundurulmuştur.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Metrik fonksiyonlar yazısal biçimde girilecekse onlar için uzun ya da kısa isimler kullanılabilmektedir. Örneğin:
'binary_accuracy' (kısa ismi yok)
'categorical_accuracy' (kısa ismi yok)
'mean_absolute_error' (kısa ismi 'mae')
'mean_absolute_percentage_error' (kısa ismi 'mape')
'mean_squared_error' kısa ismi ('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ğitim 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 yapı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.
Sinir ağı modelini 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 edilmelidir. Örneğin bu işlem için "CSV" dosyaları hiç uygun değildir. İşte bu tür amaçlar 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" örneği eğitildikten sonra save metodu ile saklanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2)
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
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 alır. Fonksiyon geri dönüş değeri olarak model
nesnesini mektedir. Örneğin:
from tensorflow.keras.models import load_model
model = load_model('diabetes.h5')
Artık biz modeli fit ettiğimiz biçimiyle tamamen geri almış durumdayız. Doğurdan predict metoduyla kestirim yapabiliriz.
Aşağıdaki örnekte yukarıda save ettiğimiz model yüklenerek kestirim işlemi yapılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.models import load_model
import numpy as np
model = load_model('diabetes.h5')
predict_dataset = np.array([[2 ,90, 68, 12, 120, 38.2, 0.503, 28],
[4, 111, 79, 47, 207, 37.1, 1.39, 56],
[3, 190, 65, 25, 130, 34, 0.271, 26],
[8, 176, 90, 34, 300, 50.7, 0.467, 58],
[7, 106, 92, 18, 200, 35, 0.300, 48]])
predict_result = model.predict(predict_dataset)
print(predict_result)
for result in predict_result[:, 0]:
print('Şeker hastası' if result > 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 Sequential sınıfının
save_weights metodu kullanılmaktadır. Örneğin:
model.save_weights('diabetes-weights', save_format='h5')
Aşağıdaki örnekte "diabetes" örneğindeki yalnızca modelin "w" ve "bias" değerleri saklanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2)
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
model.save_weights('diabetes-weights.h5', save_format='h5')
#----------------------------------------------------------------------------------------------------------------------------
save_weights metodu yalnızca "w" ve "bias" değerlerini save etmektedir. Bizim bunu geri yükleyebilmemiz için modeli yeniden
aynı biçimde oluşturmamız ve compile işlemini yapmamız gerekir. "w" ve "bias" değerlerinin geri yüklenmesi için Sequential
sınıfının load_weights metodu kullanılmaktadır. Örneğin:
model.load_weights('diabetes-weights.h5')
Aşağıdaki örnekte yukarıdaki örnekteki modelin "w" ve "bias" değerleri geri yüklenmiştir. Tabii yukarıda da belirttiğimiz
gibi save_weights model bilgilerini saklamadığı için modelin aynı biçimde yeniden oluşturulması gerekmektedir. Modelin
"w" ve "bias" değerlerini load_weights metodu ile geri yüklerken veri kümesini oluşturmamız gerekmemektedir. save_weights"
metodu model yalnızca "w" ve "bias" değerlerini save ettiği için model programcı tarafından orijinal haliyle oluşturulmalıdır.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense
model = Sequential(name='Diabetes')
model.add(Input((8,)))
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.load_weights('diabetes-weights.h5')
import numpy as np
predict_dataset = np.array([[2 ,90, 68, 12, 120, 38.2, 0.503, 28],
[4, 111, 79, 47, 207, 37.1, 1.39, 56],
[3, 190, 65, 25, 130, 34, 0.271, 26],
[8, 176, 90, 34, 300, 50.7, 0.467, 58],
[7, 106, 92, 18, 200, 35, 0.300, 48]])
predict_result = model.predict(predict_dataset)
print(predict_result)
for result in predict_result[:, 0]:
print('Şeker hastası' if result > 0.5 else 'Şeker Hastası Değil')
#----------------------------------------------------------------------------------------------------------------------------
Biz katman nesnelerini (Dense nesnelerini) model sınıfının (Sequential sınıfının) add metotlarıyla modelimize ekledik.
Bu katman nesnelerini ileride kullanmak istediğimizd enasıl geri alabiliriz? Tabii ilk akla gelen yöntem katman nesnelerini
yaratırken aynı zamanda saklamak olabilir. Örneğin:
model = Sequential(name='Diabetes')
model.add(Input((8,)))
layer1 = Dense(16, activation='relu', name='Hidden-1')
model.add(layer1)
layer2 = Dense(16, activation='relu', name='Hidden-2')
model.add(layer2)
layer3 = Dense(1, activation='sigmoid', name='Output')
model.add(layer3)
model.summary()
Aslında böyle saklama işlemine gerek yoktur. Zaten model nesnesinin (Sequential sınıfının) layers isimli özniteliği bir
indeks eşliğinde bizim eklediğimiz katman nesnelerini bize vermektedir. layers örnek özniteliği bir Python listesidir.
Örneğin:
layer2 = model.layers[2]
Tabii layers özniteliği bize Input katmanını gereksiz olduğundan dolayı vermemktedir. Buradaki 0'ıncı indeks ilk saklı
katmanı belirtmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
31. Ders - 21/04/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Biz yukarıda tüm modeli, modelin "w" ve "bias" değerlerini saklayıp geri yükledik. Pekiyi yalnızca tek bir katmandaki
ırlık değerlerini alıp geri yükleyebilir miyiz? İşte bu işlem katman sınıfının (yani Dense sınıfının) get_weights ve
set_weights isimli metotları ile yapılmaktadır. Biz bu metotlar sayesinde bir katman nesnesindeki "w" ve "bias" değerlerini
NumPy dizisi olarak elde edip geri yükleyebiliriz.
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ı (0'ıncı indeksli 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 k'ıncı sütun önceki
katmanın nöronlarının sonraki katmanın k'ıncı nöronuna bağlanmasındaki ağırlık değerlerini belirtmektedir. Benzer biçimde
i'inci satır ise önceki katmanın i'inci nöronunun ilgili katmanın nöron bağlantısındaki ağırlık değerlerini belirtmektedir.
Bu durumda örneğin [i, k] indeksindeki eleman önceki katmanın i'inci nörounun ilgili katmanın k'ıncı nöronu ile bağlantısındaki
ırlığı belirtmektedir.
get_weights metodunun verdiği listenin ikinci elemanı (1'inci indeksli elemanı) nöronların "bias" değerlerini vermektedir.
Bias değerlerinin ilgili katmandaki nöron sayısı kadar olması gerektiğine dikkat ediniz. Çünkü her nöron için bir tane bias
değeri vardır.
Örneğin yukarıdaki "diabetes" örneğinde ilk saklı katmandaki ağırlıkları şöyle elde edebiliriz:
layer = model.layers[0]
weights, bias = layer.get_weights()
Burada weights dizisinin shape'i yazdırıldığında (8, 16) görülecektir. bias dizisinin shape'i yazdırıldığında ise (16,)
görülecektir.
Aşağıdaki "diabetes" örneğindeki ilk saklı katmanın "w" ve "bias" değerleri görüntülenmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2)
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
hidden1 = model.layers[0]
weights, bias = hidden1.get_weights()
print('Weights')
print(weights)
print('Bias')
print(bias)
print("5'inci girdi nöronunun ilk katmanın 9'uncu nöronununa bağlantısındaki w değeri")
w = weights[5, 9]
print(w)
#----------------------------------------------------------------------------------------------------------------------------
Bir katmandaki "w" ve "bias" değerlerini Dense sınıfının set_weights metodu ile geri yükleyebiliriz. Örneğin:
hidden1 = model.layers[0]
weights, bias = hidden1.get_weights()
weights = weights + 0.1
hidden1.set_weights([weights, bias])
Burada birinci saklı katmandaki ağırlık değerlerine 0.1 eklenerek ağırlık değerleri geri yüklenmiştir. set_weights
metodunun iki elemanı bir liste aldığına dikkat ediniz. Nasıl get_weights metodu hem "w" hem de "bias" değerlerini
veriyorsa set_weights metodu da hem "w" hem de "bias" değerlerini istemektedir.
Aşağıdaki örnekte ilk saklı 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')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2)
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
hidden1 = model.layers[0]
weights, bias = hidden1.get_weights()
weights = weights + 0.1
hidden1.set_weights([weights, bias])
#----------------------------------------------------------------------------------------------------------------------------
Yapay sinir ağları ile kestirim yapabilmek için bazı aşamalardan geçmek gerekir. Bu aşamaları şöyle özetleyebiliriz:
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? Regresyon problemi midir? Şekil tanıma problemi midir? Doğal
dili anlama problemi midir? gibi...
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 şunlar olabilir:
- Eczanenin konumu
- Ecnanenin önünden geçen günlük insan sayısı
- Eczanenin destek ürünleri satıp satmadığı
- Eczanenin kozmetik ürünler satıp satmadığı
- Eczanenin anlaşmalı olduğu kurumlar
- Eczanenin 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 verilerin 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ş olan veriler
- Sensörler yoluyla elde edilen veriler
- Sosyal ağlardan 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 toplandı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üğü programlamada belli bir olay devam ederken programcının verdiği bir fonksiyonun (genel olarak callable
bir nesnenin) çağrılmasına" ilişkin mekanizmayı anlatmak için kullanılmaktadır. Keras'ın da bir callback mekanizması 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 programlama yoluyla izleyebilir duruma göre
gerekli işlemleri yapabiliriz.
Sequential sınıfının fit, evalaute ve predict metotları "callbacks" isimli bir parametre almaktadır. İşte biz bu parametreye
callback fonksiyonlarımızı ve sınıf nesnelerimizi verebiliriz. Bu metotlar da ilgili olaylar sırasında bizim verdiğimiz
bu 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 callback sınıftır. Aslında programcı bu callback 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(....)
History callback sınıfı aslında işlemler sırasında devreye girmek için değil (bu da sağlanabilir) epcoh'lar sırasındaki
değerlerin kaydedilmesi için kullanılmaktadır. Yani programcı fit işlemi bittikten sonra bu callback nesnenin içerisinden
fit işlemi sırasında elde edilen epoch değerlerini alabilmektedir. Dolayısıyla fit metodunun bize verdiği History nesnesi
eğitim sırasında her epoch'tan sonra elde edilen loss ve metrik 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 özniteliği uygulanan epoch
numaralarını bize verir. Ancak nesnenin en önemli elemanı history isimli isimli özniteliğidir. Nesnenin history isimli özniteliği
bir sözlük türündendir. Sözlüğün anahtarları yazısal olarak loss ve metrik değer isimlerini barı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 değerleri 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.
(Yani bu değerler kümülatif bir ortalama değil, her epoch'taki ortalamalara ilişkindir.)
Epoch'lar sonrasında History nesnesi yoluyla elde edilen bilgilerin grafiği çizdirilebilir. Uygulamacılar eğitimin gidişatı
hakkında fikir edinebilmek için genellikle epoch grafiğini çizdirirler. 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österebilmekte model gitgide yanlış şeyleri
öğrenir duruma gelebilmektedir. İş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. Örneğin:
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
Aşağıda "diabetes" örneği için fit metodunun geri döndürdüğü History callback nesnesi kullanılarak epoch grafikleri
çizdirilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=300, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
Aslında History callback nesnesi fit metodunun callbacks parametresi yoluyla da elde edilebilir. Örneğin:
from tensorflow.keras.callbacks import History
hist = History()
model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=300, validation_split=0.2, callbacks=[hist])
Tabii buna hiç gerek yoktur. Zaten bu History callback nesnesi fit metodu tarafından metodun içerisinde oluşturulup geri
dönüş değeri yoluyla bize verilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
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 dosyasının yol ifadesi 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')])
Aşağıdaki "diabetes" örneği için CSVLogger callback sınıfı yoluyla bir CSV dosyası yaratılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
iimport pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import CSVLogger
csv_logger = CSVLogger('diabetes-epoch-logs.csv')
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=300, validation_split=0.2, callbacks=[csv_logger])
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
32. Ders - 27/04/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Daha önce de belirttiğimiz gibi tüm callback sınıfları tensorflow.keras.callbacks modülündeki Callback isimli sınıftan
türetilmiştir. Biz de bu sınıftan türetme yaparak kendi callback sınıflarımızı yazabiliriz. fit, evaluate ve predict metotları
callbacks parametresine girilen callback nesnelerinin çeşitli metotlarını çeşitli olaylar sırasında çağırmaktadır. Programcı
da kendi callback sınıflarını yazarken aslında bu taban Callback sınıfındaki metotları override eder. Örneğin her epoch
bittiğinde Callback sınıfının on_epoch_end isimli metodu çağrılmaktadır. Callback sınıfının bu metodunun içi boştur. Ancak
biz türemiş sınıfta bu metodu overide edersek (yani aynı isimle yeniden yazarsak) bizim override ettiğimiz metot devreye
girecektir. on_epoch_end metodunun parametrik yapısı şöyle olmalıdır:
def on_epoch_end(epoch, logs):
pass
Buradaki birinci parametre epoch numarasını (yani kaçıncı epoch olduğunu) ikinci parametre ise epoch sonrasındaki eğitim
ve sınama işlemlerinden elde edilen loss ve metrik değerleri veren bir sözlük biçimindedir. Bu sözlüğün anahtarları ilgili
değerleri belirten yazılardan değerleri de o anahtara ilişkin değerlerinden oluşmaktadır.
Örneğin her epoch sonrasında biz bir fonksiyonumuzun çağrılmasını isteyelim. Bu fonksiyon içerisinde de "loss" değerini ve
"val_loss" değerini ekrana yazdırmak isteyelim. Bu işlemi şöyle yapabiliriz:
class MyCallback(Callback):
def on_epoch_end(self, epoch, logs):
loss = logs['loss']
val_loss = logs['val_loss']
print(f'epoch: {epoch}, loss: {loss}, val_loss: {val_loss}')
mycallback = MyCallback()
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=300,
validation_split=0.2, callbacks=[mycallback], verbose=0)
Burada fit metodunun verbose parametresine 0 değerini geçtik. fit metodu (evaluate ve predict metotlarında da aynı
durum söz konusu) çalışırken zaten "loss" ve "metik değerleri" ekrana yazdırmaktadır. verbose parametre için 0 girildiğinde
artık fit metodu ekrana bir şey yazmamaktadır. Dolayısıyşa yalnızca bizim callback fonksiyonda ekrana yazdırdıklarımız
ekranda görünecektir.
fit, evaluate ve predict tarafından çağrılan Callback sınıfının metotlarının en önemli olanları şunlardır:
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. Bu metotların diğer parametreleri
aşağıdaki gibidir:
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
Aşağıdaki örnekte Callback sınıfından MyCallback isimli bir sınıf türetilmiştir. Bu sınıfta on_epch_end metodu override
edilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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.callbacks import Callback
class MyCallback(Callback):
def on_epoch_end(self, epoch, logs):
loss = logs['loss']
val_loss = logs['val_loss']
print(f'epoch: {epoch}, loss: {loss}, val_loss: {val_loss}')
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
mycallback = MyCallback()
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=300, validation_split=0.2, callbacks=[mycallback], verbose=0)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
LambdaCallback isimli callback sınıfı bizden __init__ metodu yoluyla çeşitli fonksiyonlar alır ve bu fonksiyonları belli
noktalarda çağırır. Metottaki parametreler belli olaylar gerçekleştiğinde çağrılacak fonksiyonları belirtmektedir. Parametrelerin
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. Örneğin biz her epoch bittiğinde, her batch başladığında ve bittiğinde bir fonksiyonumuzun çağrılmasını
isteyelim. Bunu şöyle gerçekleştirebiliriz:
def on_epoch_end_proc(epoch, logs):
pass
def on_batch_begin_proc(batch, logs):
pass
def on_batch_end_proc(batch, logs):
pass
from tensorflow.keras.callbacks import LambdaCallback
lambda_callback = LambdaCallback(on_epoch_end=on_epoch_end_proc, on_batch_begin=on_batch_begin_proc, on_batch_end=on_batch_end_proc)
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=300,
validation_split=0.2, callbacks=[lambda_callback], verbose=0)
Aşağıdaki örnekte her epoch sonrasında "loss" ve "val_loss" değerleri callback fonksiyonda yazdırılmıştır. Ayrıca her
epoch içerisindeki batch işlemlerinin sonucunda elde edilen "loss" değerleri de ekrana yazdırılmıştır. Aynı zamanda bu
örnekte batch'lerdeki loss değerleri bir listede saklanmış ve bu loss değerlerinin ortalaması da ekrana yazdırılmıştır.
fit metodu tarafından çağrılan on_epoch_end metodundaki "loss" değeri son batch'teki "loss" değeri olarak verilmektedir.
Ancak HistoryCallback (ve fir metodunun ekrana yazdırdığı) epoch'a ilişkin "loss" değeri epoch içerisindeki batch'lerdeki
ortalama "loss" değeridir. Maalesef Keras'taki bu değerlerin nasıl elde edildiği dokümanlarda ayrıntılı biçimde açıklanmamıştır.
Dokümanlarda açıklanmayan özelliklerin zaman içerisinde değiştirilebileceğine dikkat ediniz.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
import numpy as np
batch_losses= []
def on_epoch_begin_proc(epoch, logs):
global batch_losses
batch_losses = []
print(f'eopch: {epoch}')
def on_epoch_end_proc(epoch, logs):
loss = logs['loss']
val_loss = logs['val_loss']
print(f'\nepoch: {epoch}, loss: {loss}, val_loss: {val_loss}')
print(f'batch mean: {np.mean(batch_losses)}')
print('-' * 30)
def on_batch_end_proc(batch, logs):
global total
loss = logs['loss']
batch_losses.append(loss)
print(f'\t\tbatch: {batch}, loss: {loss}')
from tensorflow.keras.callbacks import LambdaCallback
lambda_callback = LambdaCallback(on_epoch_begin=on_epoch_begin_proc, on_epoch_end=on_epoch_end_proc, on_batch_end=on_batch_end_proc)
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100,
validation_split=0.2, callbacks=[lambda_callback], verbose='auto')
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
LambdaCallback sınıfını aslında basit bir biçimde biz de yazabiliriz. Burada yapacağımız şey sınıfın __init__ metodunda
bize verilen fonksiyonları nesnenin özniteliklerinde saklamak ve override ettiğimiz metotlarda bunları çağırmaktır. Aşağıda
LambdaCallback sınıfının nasıl yazıldığına ilişkin bir örnek verilmiştir. Bu örnekteki MyLambdaCallback sınıfı orijinal
LambdaCallback sınıfının aynısı değildir. Bu örnekte yalnızca bu sınıfın nasıl yazıldığına ilişkin bir fikir vermeye
çalışıyoruz.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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()
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):
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
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)
import numpy as np
batch_losses= []
def on_epoch_begin_proc(epoch, logs):
global batch_losses
batch_losses = []
print(f'eopch: {epoch}')
def on_epoch_end_proc(epoch, logs):
loss = logs['loss']
val_loss = logs['val_loss']
print(f'\nepoch: {epoch}, loss: {loss}, val_loss: {val_loss}')
print(f'batch mean: {np.mean(batch_losses)}')
print('-' * 30)
def on_batch_end_proc(batch, logs):
global total
loss = logs['loss']
batch_losses.append(loss)
print(f'\t\tbatch: {batch}, loss: {loss}')
mylambda_callback = MyLambdaCallback(on_epoch_begin=on_epoch_begin_proc, on_epoch_end=on_epoch_end_proc, on_batch_end=on_batch_end_proc)
hist = model.fit(training_dataset_x, training_dataset_y, batch_size=32, epochs=100, validation_split=0.2, callbacks=[mylambda_callback], verbose=0)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
#----------------------------------------------------------------------------------------------------------------------------
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 w1x1 + w2x2 + w3x3 + 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 gücü kestirim bakımından zayıflar.
İşte bu nedenden dolayı işin başında sütunların (yani özelliklerin) mertebelerininin birbirlerine yaklaştırılması 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 ise gerekmemektedir. Biz
kursumuzda her konuda özellik ölçeklemesinin gerekli olup olmadığınııklayacağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Çeşitli özellik ölçeklemesi yöntemleri vardır. Veri kümelerinin dağılımına ve kullanılan yöntemlere göre değişik özellik
ölçeklendirmeleri diğerlerine göre avantaj sağlayabilmektedir. En çok kullanılan iki özellik ölçeklendirmesi yöntemi
" 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: Gerektiği halde özellik ölçeklemesini 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 özellik ölçeklemesi uyguladığı için bunlara da uygulamaktadır.
Özellik ölçeklemesi yalnızca x verilerine uygulanmalıdır, y verilerine özellik ölçeklemesi uygulamanın genel olarak faydası
ve anlamı 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 kestirim işleminden önce kestirim
verilerini de aynı biçimde ölçeklendirip işleme sokmalıyız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
33. Ders - 28/04/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
En çok kullanılan özellik ölçeklendirmesi yöntemlerinden biri "standart ölçekleme (standard scaling)" yöntemidir. Bu yöntemde
sütunlar diğerlerden bağımsız olarak kendi aralarında standart normal dağılıma uydurulmaktadır. Bu işlem şöyle yapılmaktadır:
result = (x - mean(x)) / std(x)
Tabii burada biz formülü temsili kod (pseudo code) olarak verdik. Burdaki "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 etrafında toplanır. Standart
ölçeklemenin değerleri standart normal dağılma uydurmaya çalıştığına dikkat ediniz.
Standart ölçeklemeye "standardizasyon (standardization)" da denilmektedir. Standart ölçekleme aşırı uçtaki değerlerden (outliers)
olumsuz bir biçimde etkilenme eğilimindedir. Veri bilimcileri genellikle standart ölçeklemeyi default ölçekleme olarak
kullanmaktadır. Bir NumPy dizisindeki sütunları aşağıdaki gibi bir fonksiyonla standart ölçeklemeye sokabiliriz.
Standart ölçekleme yapan bir fonksiyonu aşağıdaki gibi yazabiliriz:
def standard_scaler(dataset):
scaled_dataset = np.zeros(dataset.shape)
for col in range(dataset.shape[1]):
scaled_dataset[:, col] = (dataset[:, col] - np.mean(dataset[:, col])) / np.std(dataset[:, col])
return scaled_dataset
Tabii aslında NumPy'ın eksensel işlem yapma özelliğinden faydalanarak yukarıdaki işlemi aşağıdaki gibi tek satırla
da yapabiliriz:
def standard_scaler(dataset):
return (dataset - np.mean(dataset, axis=0)) / np.std(dataset, axis=0)
Aşağıda standart ölçeklemeye ilişkin bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
data= np.array([4, 7, 1, 90, 45, 70, 23, 12, 6, 9, 45, 82, 65])
mean = np.mean(data)
std = np.std(data)
print(f'Mean: {mean}, Standard Deviation: {std}')
result = (data - mean) / std
print(f'Data: {data}')
print(f'Scaled Data: {result}')
print('--------------------------------')
dataset = np.array([[1, 2, 3], [2, 1, 4], [7, 3, 8], [8, 9, 2], [20, 12, 3]])
def standard_scaler(dataset):
scaled_dataset = np.zeros(dataset.shape)
for col in range(dataset.shape[1]):
scaled_dataset[:, col] = (dataset[:, col] - np.mean(dataset[:, col])) / np.std(dataset[:, col])
return scaled_dataset
result = standard_scaler(dataset)
print(dataset)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Asında scikit-learn kütüphanesinde 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 kullanılmaktadır. Yani önce StandardScaler sınıfı
türünden bir nesne yaratılır. Sonra bu nesne ile fit ve transform metotları çağrılır. Tabii fit ve transform metotlarında
aynı veri kümesi kullanılacaksa bu işlem tek hamlede fit_transform metoduyla yapılabilir. Örneğin:
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(dataset)
scaled_dataset = ss.tranform(dataset)
fit işlemi sütunların ortalamasını ve standart sapmasını nesnenin içerisinde saklamaktadır. transform işleminde bu bilgiler
kullanılmaktadır. fit işleminden sonra nesnenin özniteliklerinden sütunlara ilişkin bu bilgiler elde edilebilir. Örneğin
fit işleminden sonra sınıfın mean_ örnek özniteliğinden sütun ortaalamaları, scale_ örnek özniteliğinden sütun standart
sapmaları ve var_ örnek özniteliğinden sütunların varyansları elde edilebilir.
Aşağıda StandardScaler sınıfının kullanımına ilişkin bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
dataset = np.array([[1, 2, 3], [2, 1, 4], [7, 3, 8], [8, 9, 2], [20, 12, 3]])
print(dataset)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(dataset)
print(f'{ss.mean_}, {ss.scale_}')
scaled_dataset = ss.transform(dataset)
print(scaled_dataset)
#----------------------------------------------------------------------------------------------------------------------------
Sinir ağlarında özellik ölçeklemesi yapılırken şu noktaya dikkat edilmelidir: Özellik ölçeklemesi önce eğitim veri kümesinde
gerçekleştirilir. Sonra eğitim veri kümesindeki sütunlardaki bilgiler kullanılarak test veri kümesi ve kestirim veri kümesi
ölçeklendirilir. (Yani test veri kümesi ve kestirim veri kümesi kendi arasında ölçeklendirilmez. Eğitim veri kümesi referans
alınarak ölçeklendirilir. Çünkü modelin test edilmesi ve kestirimi eğitim şartlarında yapılmalıdır.) Bu durumu kodla şöyle
ifade edebiliriz:
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)
scaled_predict_dataset_x = ss.transform(predict_dataset_x)
Ö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 sonra da "eğitim" ve "test" olmak üzere ikiye ayırırız. Ondan sonra eğitim veri kümesi üzerinde
özellik ölçeklemesi yapıp eğitimi uygularız. Yukarıda da belirttiğimiz gibi eğitim veri kğmesinden 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ğıda "dibates" örneğini standart ölçeklendirme uygulayarak yeniden düzenliyoruz.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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 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 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()
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)
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y, batch_size=32)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
predict_dataset = np.array([[2 ,90, 68, 12, 120, 38.2, 0.503, 28],
[4, 111, 79, 47, 207, 37.1, 1.39, 56],
[3, 190, 65, 25, 130, 34, 0.271, 26],
[8, 176, 90, 34, 300, 50.7, 0.467, 58],
[7, 106, 92, 18, 200, 35, 0.300, 48]])
scaled_predict_dataset = ss.transform(predict_dataset)
predict_result = model.predict(scaled_predict_dataset)
print(predict_result)
for result in predict_result[:, 0]:
print('Şeker hastası' if result > 0.5 else 'Şeker Hastası Değil')
#----------------------------------------------------------------------------------------------------------------------------
Diğer çok kullanılan bir özellik ölçeklemesi yöntemi de "min-max" ölçeklemesi denilen yöntemd.r. Bu ölçeklemede sütun
değerleri [0, 1] arasında noktalı sayılarla temsil edilir. Min-max ölçeklemesi aşağıdaki temsili kodda olduğu gibi yapılmaktadır:
(a - min(a)) / (max(a) - min(a))
Örneğin sütun değerleri aşağıdaki gibi olsun:
2
5
9
4
12
Burada min-max ölçeklemesi şöyle yapılmaktadır:
2 => (2 - 2) / 10
5 => (5 - 2) / 10
9 => (9 - 2) / 10
4 => (4 - 2) / 10
12 => (12 - 2) / 10
Min-max ölçeklemesinde en küçük değerin ölçeklenmiş değerinin 0 olduğuna, en büyük değerin ölçeklenmiş değerinin 1 olduğuna,
diğer değerlerin ise 0 ile 1 arasında ölçeklendirildiğine dikkat ediniz.
Min-max ölçeklemesi yapan bir fonksiyon şöyle yazılabilir:
import numpy as np
def minmax_scaler(dataset):
scaled_dataset = np.zeros(dataset.shape)
for col in range(dataset.shape[1]):
min_val, max_val = np.min(dataset[:, col]), np.max(dataset[:, col])
scaled_dataset[:, col] = 0 if max_val - min_val == 0 else (dataset[:, col] - min_val) / (max_val - min_val)
return scaled_dataset
Bir sütundaki tüm değerlerin aynı olduğunu düşünelim. Böyle bir sütunun veri kümesinde bulunmasının bir faydası olabilir
mi? Tabii ki olmaz. Min-max ölçeklemesi yaparken sütundaki tüm değerler aynı ise sıfıra bölme gibi bir anomali oluşabilmektedir.
Yukarıdak kodda bu durum da dikkate alınmıştır. (Tabii aslında böyle bir sütun önişleme aşamasında veri kümesinden zaten
atılması gerekir.) Yukarıdaki fonksiyonu yine NumPy'ın eksensel işlem yapma yenetiğini kullanarak tek satırda da yazabiliriz:
def minmax_scaler(dataset):
return (dataset - np.min(dataset, axis=0)) / (np.max(dataset, axis=0) - np.min(dataset, axis=0))
Aşağıda min-max ölçeklemesine ilişkin bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
def minmax_scaler(dataset):
scaled_dataset = np.zeros(dataset.shape)
for col in range(dataset.shape[1]):
min_val, max_val = np.min(dataset[:, col]), np.max(dataset[:, col])
scaled_dataset[:, col] = 0 if max_val - min_val == 0 else (dataset[:, col] - min_val) / (max_val - min_val)
return scaled_dataset
"""
def minmax_scaler(dataset):
return (dataset - np.min(dataset, axis=0)) / (np.max(dataset, axis=0) - np.min(dataset, axis=0))
"""
dataset = np.array([[1, 2, 3], [2, 1, 4], [7, 3, 8], [8, 9, 2], [20, 12, 3]])
result = minmax_scaler(dataset)
print(dataset)
print(result)
#----------------------------------------------------------------------------------------------------------------------------
sckit-learn kütüphanesinde sklearn.preprocessing modülünde min-max ölçeklemesi yapan MinMaxScaler isimli bir sınıf da
bulunmaktadır. Sınıfın kullanımı tamamen benzerleri gibidir. Örneğin:
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(dataset)
scaled_dataset = mms.transform(dataset)
Yine sınıfın fit ve tarnsform işlemini birlikte yapan fit_transform isimli metodu bulunmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
dataset = np.array([[1, 2, 3], [2, 1, 4], [7, 3, 8], [8, 9, 2], [20, 12, 3]])
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler()
mms.fit(dataset)
scaled_dataset = mms.transform(dataset)
print(dataset)
print(scaled_dataset)
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de "dibates" örneğini min-max ölçeklemesi ile yeniden gerçekleştirelim. Aşağıdaki programı çalıştırdığımızda min-max
ölçeklemesinin standart ölçeklemeden bu veri kümesi için daha iyi sonuçlar verdiğini görmekteyiz.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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 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 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()
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)
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y, batch_size=32)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
predict_dataset = np.array([[2 ,90, 68, 12, 120, 38.2, 0.503, 28],
[4, 111, 79, 47, 207, 37.1, 1.39, 56],
[3, 190, 65, 25, 130, 34, 0.271, 26],
[8, 176, 90, 34, 300, 50.7, 0.467, 58],
[7, 106, 92, 18, 200, 35, 0.300, 48]])
scaled_predict_dataset = mms.transform(predict_dataset)
predict_result = model.predict(scaled_predict_dataset)
print(predict_result)
for result in predict_result[:, 0]:
print('Şeker hastası' if result > 0.5 else 'Şeker Hastası Değil')
#----------------------------------------------------------------------------------------------------------------------------
34. Ders - 04/05/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Diğer bir özellik ölçeklemesi de "maxabs" ölçeklemesi denilen ölçeklemedir. Maxabs ölçeklemesinde sütundaki değerler sütundaki
değerlerin mutlak değerlerinin en büyüğüne bölünmektedir. Böylece sütun değerleri [-1, 1] arasına ölçeklenmektedir. Maxabs
ölçeklemesi şöyle yapılmaktadır:
x / max(abs(x))
Burada sütundaki tüm değerler en büyük mutlak değere bölündüğüne göre ölçeklenmiş değerler -1 ile +1 arasında olacaktır.
scikit-learn kütüphanesinde "maxabs" ölçeklemesi MaxAbsScaler isimli sınıfla temsil edilmiştir. Bu sınıf diğer scikit-learn
sınıflarında olduğu gibi kullanılmaktadır.
Aşağıda "maxabs" ölçeklemesinin manuel ve scikit-learn kütüphanesiyle yapılmasına bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
def maxabs_scaler(dataset):
scaled_dataset = np.zeros(dataset.shape)
for col in range(dataset.shape[1]):
maxabs_val = np.max(np.abs(dataset[:, col]))
scaled_dataset[:, col] = 0 if maxabs_val == 0 else dataset[:, col] / maxabs_val
return scaled_dataset
"""
def maxabs_scaler(dataset):
return dataset / np.max(np.abs(dataset), axis=0)
"""
dataset = np.array([[1, 2, 3], [2, 1, 4], [7, 3, 8], [8, 9, 2], [20, 12, 3]])
result = maxabs_scaler(dataset)
print(dataset)
print(result)
print('------------------------------------------------')
dataset = np.array([[1, 2, 3], [2, 1, 4], [7, 3, 8], [8, 9, 2], [20, 12, 3]])
from sklearn.preprocessing import MaxAbsScaler
mas = MaxAbsScaler()
mas.fit(dataset)
scaled_dataset = mas.transform(dataset)
print(dataset)
print(scaled_dataset))
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda "diabetes" örneğinde maxabs ölçeklemesinin kullanılmasına ilişkin örnek verilmiştir. Diabetes örneğinde aslında
maxabs ölçeklemesi diğerlerine göre uygun bir ölçekleme değildir. Ancak biz burada ölçeklemenin kullanımına ilişkin bir
örnek vermek istiyoruz.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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 sklearn.preprocessing import MaxAbsScaler
mas = MaxAbsScaler()
mas.fit(training_dataset_x)
scaled_training_dataset_x = mas.transform(training_dataset_x)
scaled_test_dataset_x = mas.transform(test_dataset_x)
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()
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)
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y, batch_size=32)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
predict_dataset = np.array([[2 ,90, 68, 12, 120, 38.2, 0.503, 28],
[4, 111, 79, 47, 207, 37.1, 1.39, 56],
[3, 190, 65, 25, 130, 34, 0.271, 26],
[8, 176, 90, 34, 300, 50.7, 0.467, 58],
[7, 106, 92, 18, 200, 35, 0.300, 48]])
scaled_predict_dataset = mas.transform(predict_dataset)
predict_result = model.predict(scaled_predict_dataset)
print(predict_result)
for result in predict_result[:, 0]:
print('Şeker hastası' if result > 0.5 else 'Şeker Hastası Değil')
#----------------------------------------------------------------------------------------------------------------------------
Biz yukarıda üç ölçeklendirmeyi tanıttık. Bunlar "standart ölçekleme", "minmax ölçeklemesi" ve "maxabs ölçeklemesi" idi.
Aslında bunların dışında başka ölçeklemeler de kullanılabilmektedir. Bu konuda başka kaynaklara başvurabilirsiniz.
Pekiyi elimizdeki veri kümesi için hangi ölçeklemenin daha iyi olduğuna nasıl karar verebiliriz? Aslında bu kararı vermenin
çok prtaik yolları yoktur. En iyi yöntem yine de "deneme yanılma yoluyla" kıyaslama yapmaktır. Fakat yine de ölçekleme
türünü seçerken aşağıdaki durumlara dikkat edilmelidir:
- Sütunlarda aşırı uç değerlerin (outliers) bulunduğu durumda minmax ölçeklemesi ölçeklenmiş değerlerin birbirinden
uzaklaşmasına yol açabilmektedir. Bu durumda bu ölçeklemenin performansı düşürebileceğine dikkat etmek gerekir.
- Sütunlardaki değerler normal dağılıma benziyorsa (örneğin doğal birtakım olgulardan geliyorsa) standart ölçekleme
diğerlerine göre daha iyi performans gösterebilmektedir.
- Sütunlardaki değerler düzgün dağılmışsa ve aşırı uç değerler yoksa minmax ölçeklemesi tercih edilebilir.
Ancak yine uygulamacılar veri kümeleri hakkında özel bir bilgiye sahip değilse ve deneme yanılma yöntemini kullanmak
istemiyorlarsa standart ölçeklemeyi default ölçekleme olarak tercih etmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
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 kullanarak yaptık. Sequential sınıfının save metodu modeli save ederken bu ölçeklemeler modelin
bir parçası olmadığı için onları saklayamamaktadır. 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 yüklenmesine "nesnelerin seri hale
getirilmesi (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'un standart kütüphanesindeki pickle modülü ile yapılabilmektedir.
Örneğin scikit-learn ile standard ölçekleme yapmış olalım ve bu ölçekleme bilgisini Python'un standart pickle modülü ile
bir dosyada saklamak isteyelim. Bu işlemi şöyle yapabiliriz:
import pickle
with open('diabetes-scaling.dat', 'wb') as f:
pickle.dump(ss, f)
Nesneyi dosyadan geri yükleme işlemi de şöyle yapılmaktadır:
with open('diabetes-scaling.dat', 'rb') as f:
ss = pickle.load(f)
Aşağıdaki örnekte model "diabetes.h5" dosyası içerisinde, MinMaxScaler nesnesi de "diabetes-scaling.dat" dosyası içerisinde
saklanmıştır ve geri yüklenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
# diabetes-scaling-save.py
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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 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 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()
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)
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y, batch_size=32)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
model.save('diabetes.h5')
import pickle
with open('diabetes-scaling.dat', 'wb') as f:
pickle.dump(ss, f)
# diabetes-scaling-load.py
import numpy as np
from tensorflow.keras.models import load_model
import pickle
model = load_model('diabetes.h5')
with open('diabetes-scaling.dat', 'rb') as f:
ss = pickle.load(f)
predict_dataset = np.array([[2 ,90, 68, 12, 120, 38.2, 0.503, 28],
[4, 111, 79, 47, 207, 37.1, 1.39, 56],
[3, 190, 65, 25, 130, 34, 0.271, 26],
[8, 176, 90, 34, 300, 50.7, 0.467, 58],
[7, 106, 92, 18, 200, 35, 0.300, 48]])
scaled_predict_dataset = ss.transform(predict_dataset)
predict_result = model.predict(scaled_predict_dataset)
print(predict_result)
for result in predict_result[:, 0]:
print('Şeker hastası' if result > 0.5 else 'Şeker Hastası Değil')
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda da belirttiğimiz gibi eğer biz ölçekleme işlemini Keras'ın bir katmanı gibi ifade edip uygularsak Keras modelini
sakladığımızda zaten bu bilgi de saklanmış olacaktır. Son zamanlara kadar Keras'ta bu işlemi yapacak hazır katmanlar
bulunmuyordu. Ancak daha sonraları Keras da diğer framewok'ler gibi böylesi katmanları bulundurmaya başlamıştır. Tabii
bu biçimdeki hazır katmanlar yokken programcılar yine bu işlemleri yapan kendi katman nesnelerini oluşturabiliyordu.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Keras'a sonradan eklenen Normalization isimli katman özellik ölçeklemesi yapabilmektedir. Normalization katmanı default
olarak "standart ölçekleme" için düşünülmüştür. Ancak "minmax ölçeklemesi" için de kullanılabilir. Bu katmanı kullanabilmek
için önce Normalization türünden bir nesnenin yaratılması gerekir. Normalization sınıfının __init__ metodunun parametrik
yapısı şöyledir:
tf.keras.layers.Normalization(axis=-1, mean=None, variance=None, invert=False, **kwargs)
Burada mean sütunların ortalama değerlerini, variance ise sütunların varysans değerlerini almaktadır. Ancak programıcnın
bu değerleri girmesine gerek yoktur. Normalization sınıfının adapt isimli metodu bizden bir veri kümesi alıp bu değerleri
o kümeden elde edebilmektedir. Bu durumda standart ölçekleme için Normalization katmanı aşağıdaki gibi oluşturulabilir.
from tensorflow.keras.layers import Normalization
norm_layer = Normalization()
norm_layer.adapt(traininf_dataset_x)
Tabii nu katmanı input katmanından sonra modele eklememiz gerekir. Örneğin:
model = Sequential(name='Diabetes')
model.add(Input((training_dataset_x.shape[1],)))
model.add(norm_layer)
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()
Örneğin:
import numpy as np
dataset = np.array([[1, 2, 3], [4, 5, 6], [3, 2, 7], [5, 9, 5]])
print(dataset)
from tensorflow.keras.layers import Normalization
norm_layer = Normalization()
norm_layer.adapt(dataset)
print(norm_layer.mean)
print(norm_layer.variance)
Tabii biz ölçeklemeyi bir katman biçiminde modele eklediğimizde artık test ve predict işlemlerinde ayrıca ölçekleme
yapmamıza gerek kalmamaktadır. Bu katman zaten modele dahil olduğuna göre işlemler ölçeklendirilerek yapılacaktır.
Yukarıda da belirttiğimiz gibi özellik ölçeklemesini Keras'ın bir katmanına yaptırdıımızda ayrıca ölçekleme bilgilerinin
saklanmasına gerek olmadığına dikkat ediniz. Çünkü ölçekleme bilgileri artık modelin bir parçası durumundadır.
Aşağıda "diabetes" örneğinin Keras'ın hazır Normalization katmanı kullanılarak gerçekleştirimi verilmiştir. Burada biz
konunun anlaşılması için gerekmediği halde modeli saklayıp geri de yükledik.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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, Normalization, Dense
norm_layer = Normalization()
norm_layer.adapt(training_dataset_x)
model = Sequential(name='Diabetes')
model.add(Input((training_dataset_x.shape[1],)))
model.add(norm_layer)
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()
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)
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import numpy as np
predict_dataset = np.array([[2 ,90, 68, 12, 120, 38.2, 0.503, 28],
[4, 111, 79, 47, 207, 37.1, 1.39, 56],
[3, 190, 65, 25, 130, 34, 0.271, 26],
[8, 176, 90, 34, 300, 50.7, 0.467, 58],
[7, 106, 92, 18, 200, 35, 0.300, 48]])
model.save('diabetes.h5')
from tensorflow.keras.models import load_model
model = load_model('diabetes.h5')
predict_result = model.predict(predict_dataset)
print(predict_result)
for result in predict_result[:, 0]:
print('Şeker hastası' if result > 0.5 else 'Şeker Hastası Değil')
#----------------------------------------------------------------------------------------------------------------------------
Keras'ta minmax ölçeklemesi için hazır bir katman bulunmamaktadır. Ancak böyle bir katman nesnesi programcı tarafından da
oluşturulabilir. Aşağıdaki makalede buna ilişkin bir örnek verilmiştir:
https://fdnieuwveldt.medium.com/implementing-sklearn-like-transformers-in-keras-a-custom-preprocessing-layer-example-60688ba821e0
Tabii biz henüz Tensorflow kütüphanesini incelemediğimiz için şu anda böyle bir örnek vermeyeceğiz.
Aslında hazır Normalization sınıfını biz minmax ölçeklemesi için de kullanabiliriz. Standart ölçeklemenin aşağıdaki gibi
yapıldığını anımsayınız:
(X - mu) / sigma
Burada mu ve sigma ilgili sütunun ortalamasını ve standart sapmasını belirtmektedir. Minmax ölçeklemesinin ise şöyle yapıldığını
anımsayınız:
(X - min ) / (max - min)
O halde biz aslında standart ölçeklemeyi minmax ölçeklemesi haline de getirebiliriz. Burada mu değerinin min,
sigma değerinin ise max - min olması gerektiğine dikkat ediniz. Örneğin.
mins = np.min(training_dataset_x, axis=0)
maxmin_diffs = np.max(training_dataset_x, axis=0) - np.min(training_dataset_x, axis=0)
norm_layer = Normalization(mean=mins, variance=maxmin_diffs ** 2)
Aşağıda Normalization sınıfının minmax ölçeklemesi için kullanımına örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('diabetes.csv')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
impute_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
df[impute_features] = si.fit_transform(df[impute_features])
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, Normalization, Dense
import numpy as np
mins = np.min(training_dataset_x, axis=0)
maxmin_diffs = np.max(training_dataset_x, axis=0) - np.min(training_dataset_x, axis=0)
norm_layer = Normalization(mean=mins, variance=maxmin_diffs ** 2)
model = Sequential(name='Diabetes')
model.add(Input((training_dataset_x.shape[1],)))
model.add(norm_layer)
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()
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)
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
import numpy as np
predict_dataset = np.array([[2 ,90, 68, 12, 120, 38.2, 0.503, 28],
[4, 111, 79, 47, 207, 37.1, 1.39, 56],
[3, 190, 65, 25, 130, 34, 0.271, 26],
[8, 176, 90, 34, 300, 50.7, 0.467, 58],
[7, 106, 92, 18, 200, 35, 0.300, 48]])
model.save('diabetes.h5')
from tensorflow.keras.models import load_model
model = load_model('diabetes.h5')
predict_result = model.predict(predict_dataset)
print(predict_result)
for result in predict_result[:, 0]:
print('Şeker hastası' if result > 0.5 else 'Şeker Hastası Değil')
#----------------------------------------------------------------------------------------------------------------------------
Biz şimdiye kadar Keras ve sinir ağı modeli üzerinde temel konuları anlayabilmek için hep diabetes örneği üzerinde çalıştık.
Şimdi başka veri kümelerini kullanarak başka örnekler üzerinde çalışacağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Anımsanacağı gibi çıktının sınıfsal olmadığı modellere "regresyon modelleri" deniyordu. Biz kursumuzda bu durumu vurgulamak
için "lojistik olmayan regresyon modelleri" terminini de kullanıyorduk. Regreson modellerinde sinir ağının
çıktı katmanındaki aktivasyon fonksiyonu "linear" olmalıdır. Linear aktivasyon fonksiyonu bir şey yapmayan fonksiyondur.
Başka bir deyişle f(x) değerinin x ile aynı olduğu fonksiyondur. (Zaten anımsanacağı gibi Dense katmanında activation
parametresi girilmezse default durumda aktivasyon fonksiyonu "linear" alınmaktaydı.)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
35. Ders - 05/05/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Auto-MPG otomobillerin bir galon benzinle kaç mil gidebildiklerininin (başka bri deyişle yakıt tüketiminin) tahmin edilmesi
amacıyla oluşturulmuş bir veri kümesidir. 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/dataset/9/auto+mpg
Veri kümesi bir zip dosyası olarak indirilmektedir. Buradaki "auto-mpg.data" dosyasını kullanabilirsiniz. Zip dosyasındaki
"auto-mpg.names" dosyasında veri kğmesi hakkında açıklamalar ve sütunların isimleri ve anlamları bulunmaktadır.
Veri 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 Amerika orijinli, 2 ise Avrupa orijinli ve 3 ise Japon
orijinlidir.
Veri kümesinin text dosyadaki görünümü aşağıdaki gibidir:
18.0 8 307.0 130.0 3504. 12.0 70 1 "chevrolet chevelle malibu"
15.0 8 350.0 165.0 3693. 11.5 70 1 "buick skylark 320"
18.0 8 318.0 150.0 3436. 11.0 70 1 "plymouth satellite"
16.0 8 304.0 150.0 3433. 12.0 70 1 "amc rebel sst"
17.0 8 302.0 140.0 3449. 10.5 70 1 "ford torino"
25.0 4 98.00 ? 2046. 19.0 71 1 "ford pinto"
19.0 6 232.0 100.0 2634. 13.0 71 1 "amc gremlin"
16.0 6 225.0 105.0 3439. 15.5 71 1 "plymouth satellite custom"
17.0 6 250.0 100.0 3329. 15.5 71 1 "chevrolet chevelle malib
....
Veri kümesi incelendiğinde dördüncü sütunda (horsepower) '?' karakteri ile belirtilen eksik verilerin bulunduğu görülmektedir.
Veri kümesindeki veriler az olmadığı için bu eksik verilerin bulunduğuğu satırlar tamamen atılabilir. Ya da daha önceden
de yaptığımız gibi ortalama değerle imputation uygulanabilir. Ayrıca arabanın yakıt tüketimi arabanın markası ile ilişkili
olsa da arabaların pek çok alt modelleri vardır. Bu nednele son sütundaki araba isimlerinin kestirimde faydası dokunmayacaktır.
Bu sütun da veri kümesinden atılabilir.
Veri kümesinin bir başlık kısmı içermediğine dikkat ediniz. read_csv default durumda veri kümesinde başlık kısmının olup
olmadığına kendi algoritması ile karar vermektedir. Ancak başlık kısmının bu biçimde read_csv tarafından otomatik belirlenmesi
sağlam bir yöntem değildir. Bu nedenle bu veri kümesi okunurken read_csv fonksiyonunda header parametresi None geçilmelidir.
read_csv default olarak sütunlardaki ayıraçların ',' karakteri olduğunu varsaymaktadır. Halbuki bu veri kümesinde sütun
ayıraçları ASCII SPACE ve TAB karakterleridir. Bu nedenle dosyanın read_csv tarafından düzgün parse edilebilmesi için
delimeter parametresine r'\s+' biçiminde "düzenli ifade (regular expression)" kalıbı girilmelidir. (Düzenli ifadeler "Python
Uygulamaları" kursunda ele alınmaktadır.) read_csv fonksiyonu eüer dosyada başlık kısmı yoksa sütun isimlerini 0, 1, ...
biçiminde nümerik almaktadır.
Bu durumda yukarıdaki veri kümsinin okunması şöyle yapılabilir:
df = pd.read_csv('auto-mpg.data', delimiter=r'\s+', header=None)
Şimdi bizim araba markalarının bulunduğu son sütundan kurtulmamız gerekir. Bu işlemi şöyle yapabiliriz:
df = df.iloc[:, :-1]
Tabii bu işlemi DataFrame sınıfının drop metodu ile de yapabiliriz:
df.drop(8, axis=1, inplace=True)
Aslında bu sütun read_csv ile okuma sırasında usecols parametresi yardımıyla da atılabilir.
Şimdi de 3'üncü indeksli sütundaki eksik verileri temsil eden '?' bulunan satırlar üzerinde çalışalım. Yukarıda da
belirttiğimiz gibi bu sütundaki eksik verilerin sayıları çok az olduğu için veri kümesinden atılabilirler. Bu işlem
şöyle yapılabilir:
df = df[df.iloc[:, 3] != '?']
Ancak biz geçmiş konuları da kullanabilmek için bu eksik verileri sütun ortalamaları ile doldurmaya (imputation) çalışalım.
Burada dikkat edilmesi gereken nokta DataFrame nesnesinin 3'üncü indeksli sütununun türünün nümerik olmamasıdır. Bu nedenle
öncelikle bu sütunun türünü nümerik hale getirmek gerekir. Tabii sütunda '?' karakterleri olduğuna göre önce bu karakterler
yerine 0 gibi nümerik değerleri yerleştirmeliyiz:
df.iloc[df.loc[:, 3] == '?', 3] = 0
Tabii eğer ilgili sütunda zaten 0 değerleri varsa bu durumda 0 ile doldurmak yerine np.nan değeri ile dolduma yolunu tercih
edebilirsiniz. Örneğin:
df.iloc[df.loc[:, 3] == '?', 3] = np.nan
Artık sütunun türünü nümerik hala getirebiliriz:
df[3] = df[3].astype('float64')
Aslında read_csv ile okuma sırasında da fonksiyonun na_values parametresi yardımıyla işin başında '?' karakterleri yerine
fonksiyonun np.nan değerlerini yerleştirmesini de sağlayabiliriz.
Burada doğrudan indekslemede sütun isimlerinin kullanılması gerektiğine, sütun isimlerinin de sütun başlığı olmadığı için
sayısal biçimde verildiğine dikkat ediniz. Artık 3'üncü indeksli sütun üzerinde imputation uygulayabiliriz:
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=0)
df[3] = si.fit_transform(df[[3]])
Henüz NumPy'a dönmeden önce 7'inci sütundaki kategorik verileri Pandas'ın get_dummis fonksiyonu ile "one-hot encoding"
biçimine dönüştürebiliriz:
df = pd.get_dummies(df, columns=[7], dtype='uint8')
Artık NumPy'a dönebiliriz:
dataset = df.to_numpy()
Şimdi de veri kümesini x ve y olarak ayrıştıracağız. Ancak y verilerinin son sütunda değil ilk sütunda olduğuna dikkat
ediniz:
dataset_x = dataset[:, 1:]
dataset_y = dataset[:, 0]
Bundan sonra veri kümesi eğitim ve test amasıyla train_test_slit fonkisyonu ile ayrıştrılabiliriz
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)
Artık özellik ölçeklemesi yapabiliriz. Özellik ölçeklemesini scikit-learn kullanarak ya da yukarıda da bahsettiğimiz gibi
Normailzation isimli Keras katmanı kullanarak da yapabiliriz. Sütun dağılımlarına bakıldığında standart ölçekleme yerine
minmax ölçeklemesinin daha iyi performans verebileceği izlenimi edinilmektedir. Ancak bu konuda deneme yanılma yöntemi
uygulamak gerekir. Biz default standart ölçekleme uygulayalım:
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)
Artık modelimizi oluşturabiliriz. Bunun için yine iki saklı katman kullanacağız. Saklı katmanlardaki aktivasyon fonksiyonlarını
yine "relu" olarak alacağız. Ancak çıktı katmanındaki aktivasyonun "linear" olması gerektiğini anımsayınız:
model = Sequential(name='Auto-MPG')
model.add(Input((training_dataset_x.shape[1],)))
model.add(Dense(32, activation='relu', name='Hidden-1'))
model.add(Dense(32, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
Şimdi de modelimizi compile edip fit işlemi uygulayalım. Modelimiz için optimizasyon algoritması yine "rmsprop" seçilebilir.
Regresyon problemleri için loss fonksiyonunun genellikle "mean_squared_error" biçiminde alınabileceğini belirtmiştik. Yine
regresyon problemleri için "mean_absolute_error" metrik değeri kullanılabilir:
odel.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)
Modelimizi test veri kümesiyle test edebiliriz:
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
Şimdi kestirim yapmaya çalışalım. Kesitirilecek veriler üzerinde de one-hot encoding dönüştürmesinin ve özellik ölçeklemesinin
yapılması gerektiğini anımsayınız. Kestirilecek verileri bir "predict.csv" isimli bir dosyada aşağıdaki gibi oluşturmuş olalım:
8,307.0,130.0,3504,12.0,70,1
4,350.0,165.0,3693,11.5,77,2
8,318.0,150.0,3436,11.0,74,3
Bu dosyayı okuduktan predict işlemi yapmadan önce sonra sırasıyla "one-hot encoding" ve standart ölçeklemenin uygulanması
gerekir:
predict_df = pd.read_csv('predict.csv', header=None)
predict_df = pd.get_dummies(predict_df, columns=[6])
predict_dataset_x = predict_df.to_numpy()
scaled_predict_dataset_x = ss.transform(predict_dataset_x)
predict_result = model.predict(scaled_predict_dataset_x)
for val in predict_result[:, 0]:
print(val)
Aşağıda tüm işlemler bir bütün halinde verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
import numpy as np
df = pd.read_csv('auto-mpg.data', delimiter=r'\s+', header=None)
df = df.iloc[:, :-1]
df.iloc[df.loc[:, 3] == '?', 3] = np.nan
df[3] = df[3].astype('float64')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=np.nan)
df[3] = si.fit_transform(df[[3]])
df = pd.get_dummies(df, columns=[7], dtype='uint8')
dataset = df.to_numpy()
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 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 Input, Dense
model = Sequential(name='Auto-MPG')
model.add(Input((training_dataset_x.shape[1],)))
model.add(Dense(32, activation='relu', name='Hidden-1'))
model.add(Dense(32, 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)
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Mean Absolute Error', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['mae'])
plt.plot(hist.epoch, hist.history['val_mae'])
plt.legend(['Measn Absolute Error', 'Validation Mean Absolute Error'])
plt.show()
# prediction
predict_df = pd.read_csv('predict.csv', header=None)
predict_df = pd.get_dummies(predict_df, columns=[6])
predict_dataset_x = predict_df.to_numpy()
scaled_predict_dataset_x = ss.transform(predict_dataset_x)
predict_result = model.predict(scaled_predict_dataset_x)
for val in predict_result[:, 0]:
print(val)
#----------------------------------------------------------------------------------------------------------------------------
36. Ders - 11/05/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
one-hot encoding işleminde Pandas'ın get_dummies fonksiyonunu kullanırken dikkat ediniz. Bu fonksiyon one-hot encoding
yapılacak sütundaki kategorileri kendisi belirlemektedir. (Bu fonksiyon "one-hot encoding" yapılacak sütunda unique olan
değerlerden hareketle kategorileri belirler.) Eğer predict yapacağınız CSV dosyasındaki satırlar tüm kategorileri içermezse
bu durum bir sorun yaratır. scikit-learn içerisindeki OneHotEncoder sınıfı bu tür durumlarda "categories" isimli parametreyle
bizlere yardımcı olmaktadır. Maalesef 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.
Biz "one-hot encoding" yapılacak sütundaki kategorileri kendimiz belirlemek istiyorsak bu parametreyi kullanabiliriz. categories
parametresi iki boyutlu bir liste olarak girilmelidir. Çünkü birden fazla sütun "one-hot encoding" işlemine sokulabilmektedir.
Bu durumda bu iki boyutlu listenin her elemanı sırasıyla sütunlardaki kategorileri belirtir. Örneğin:
ohe = OneHotEncoder(sparse=False, categories=[[0, 1, 2], ['elma', 'armut', 'kayısı']])
Burada biz iki sütunlu bir veri kümesinin 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 'elma', 'armut', 'kayısı' biçimindedir. Eğer
bu sütunlarda daha az kategori varsa burada belirtilen sayıda ve sırada sütun oluşturulacaktır.
Tabii OneHotEncoder sınıfı ile fit işlemi yaptığımızda zaten bu kategoriler nesnenin içerisinde oluşturulmaktadır. Anımsanacağı
gibi OneHotEncoder sınıfının categories_ örnek özniteliği bize fit işleminden elde edilen kategorileri vermektedir. Bu nesne
eğer saklanmışsa doğrudan predict işleminde kullanılabilir. Bu durumda bir sorun oluşmayacaktır. Pekiyi eğer bu nesne
saklanmamışsa ne yapabiliriz?
predict işlemi yapılırken uygulanan "one-hot encoding" işlemindeki sütunların eğitimdeki "one-hot encoding" sütunlarıyla
uyuşması gerekir. Aslında kütüphanelerde "one-hot encoding" işlemini yapan fonksiyonlar ve metotlar önce sütunlardaki
unique elemanları belirleyip sonra onları sıraya dizip sütunları bu sırada oluşturmaktadır. Örneğin Pandas'daki get_dummies
fonksiyonu ve scikit-learn'deki OneHotEncoder sınıfı böyle davranmaktadır. Fakat yine de tam uyuşma için one-hot encoding
işlemlerini farklı sınıflarla yapmamaya çalışınız. Predict işlemindeki "one-hot encoding" sütunların eğitimde kullanılan
one-hot encoding sütunlarıyla uyuşmasını sağlamak için eğitimde kullanılan sütunlardaki değerleri saklabilirsiniz. OneHotEncoder
sınıfının categories_ örnek özniteliği zaten bu kategorileri bize vermektedir.
Burada NumPy ve Pandas ile ilgili bir ayrıntı üzerinde durmak istiyoruz. NumPy'dki unique fonksiyonu ve metodu unique
elemanları elde ettikten sonra onları sırasyı dizip vermektedir. Ancak Pandas'taki uniuq fonksiyonu ve metodu sıraya dizme
işlemini ayrca yapmamaktadır.
Aşağıda Auto-MPGveri kümesinde predict işleminin nasıl yapıldığı gösterilmektedir. Burada predict sırasında yeni bir
OneHotEncoder nesnesi oluşturulmamış eğitim sırasındaki OneHotEncoder nesnesi kullanılmıştır. Ayrıca bu örnekte modelde
kullanılan nesnelerin disk'te saklandığına da dikkat ediniz.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
import numpy as np
df = pd.read_csv('auto-mpg.data', delimiter=r'\s+', header=None)
df = df.iloc[:, :-1]
df.iloc[df.loc[:, 3] == '?', 3] = np.nan
df[3] = df[3].astype('float64')
from sklearn.impute import SimpleImputer
si = SimpleImputer(strategy='mean', missing_values=np.nan)
df[3] = si.fit_transform(df[[3]])
df_1 = df.iloc[:, :7]
df_2 = df.iloc[:, [7]]
dataset_1 = df_1.to_numpy()
dataset_2 = df_2.to_numpy()
import numpy as np
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse_output=False)
dataset_2 = ohe.fit_transform(dataset_2)
dataset = np.concatenate([dataset_1, dataset_2], axis=1)
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 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 Input, Dense
model = Sequential(name='Auto-MPG')
model.add(Input((training_dataset_x.shape[1],)))
model.add(Dense(32, activation='relu', name='Hidden-1'))
model.add(Dense(32, 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)
eval_result = model.evaluate(scaled_test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Mean Absolute Error', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['mae'])
plt.plot(hist.epoch, hist.history['val_mae'])
plt.legend(['Measn Absolute Error', 'Validation Mean Absolute Error'])
plt.show()
model.save('auto-mpg.h5')
import pickle
with open('auto-mpg.pickle', 'wb') as f:
pickle.dump((ohe, ss), f)
# prediction
predict_df = pd.read_csv('predict.csv', header=None)
predict_df_1 = predict_df.iloc[:, :6]
predict_df_2 = predict_df.iloc[:, [6]]
predict_dataset_1 = predict_df_1.to_numpy()
predict_dataset_2 = predict_df_2.to_numpy()
predict_dataset_2 = ohe.transform(predict_dataset_2)
predict_dataset = np.concatenate([predict_dataset_1, predict_dataset_2], axis=1)
scaled_predict_dataset = ss.transform(predict_dataset)
predict_result = model.predict(scaled_predict_dataset)
for val in predict_result[:, 0]:
print(val)
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte save edilmiş olan model nesnesinin ve sckit-learn nesnelerinin başka bir Python grogramında nasıl
yüklenerek kullanılacağı gösterilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pickle
import numpy as np
import pandas as pd
from tensorflow.keras.models import load_model
# prediction
model = load_model('auto-mpg.h5')
with open('auto-mpg.pickle', 'rb') as f:
ohe, ss = pickle.load(f)
predict_df = pd.read_csv('predict.csv', header=None)
predict_df_1 = predict_df.iloc[:, :6]
predict_df_2 = predict_df.iloc[:, [6]]
predict_dataset_1 = predict_df_1.to_numpy()
predict_dataset_2 = predict_df_2.to_numpy()
predict_dataset_2 = ohe.transform(predict_dataset_2)
predict_dataset = np.concatenate([predict_dataset_1, predict_dataset_2], axis=1)
scaled_predict_dataset = ss.transform(predict_dataset)
predict_result = model.predict(scaled_predict_dataset)
for val in predict_result[:, 0]:
print(val)
#----------------------------------------------------------------------------------------------------------------------------
Bir veri kümesinde "one-hot encoding" yapılacak birden fazla sütun varsa -yukarıdaki örnekte olduğu gibi- veri kümesini
önce "one-hot encoding" yapılacak kısım ile yapılmayacak kısmı iki parçaya ayırıp one-hot encoding işlemini tek hamlede
birden fazla sütun için uygulayabilirsiniz. Anımsanacağı gibi scikit-learn içerisindeki OneHotEncoder sınıfı zaten tek
hamlede birden fazla sütunu "one-hot encoding" yapabiliyordu. İzleyen paragraflarda bu tür örnekler üzerinde duracağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi "one-hot encoding" işlemi Keras'taki bir katmana yaptırılamaz mı? Biz daha önce özellik ölçeklemesini bir katmana
yaptırmıştık. Ancak maalesef Keras bu konuda önemli bir pratiklik sunmamaktadır. One-hot encoding yapan bir katman nesnesi
programcı tarafından oluşturulabilir. Ancak bunun oluşturulabilmesi için Tensorflow kütüphanesinin kullanımının biliniyor olması
gerekir. Keras içerisinde CategoryEncoding isimli bir katman bulunuyor olsa da bu katman tüm girdideki sütunları "one-hot
encoding" yapmaktadır. Bu nedenle bu katmanın kullanılabilmesi için girdi katmanın parçalara ayrılması gerekir. Bu işlemleri
ileride "Keras Modelinin Fonksiyonel Biçimde Oluşturulması" konusu içerisinde ele alacağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Regresyon problemlerinde çok kullanılan veri kümelerinden biri de "Boston Housing Prices (BHP)" isimli veri kümesidir.
Bu veri kümesi daha önce görümüş olduğumuz "Melbourne Housing Snapshot (MHS)" veri kümesine benzemektedir. Bu veri kümesinde
evlerin çeşitli bilgileri sütunlar halinde kodlanmıştır. Amaç yine evin fiyatının kestirilmesidir. Veriler 1970 yılında
toplanmıştır. Bu nedenle ev fiyatları size düşükmüş gibi gelebilir. (Türkiye'de yaşayan kişilerin bir bölümü ABD'de ve Avrupa
ülkelerinde hiç enflasyon olmadığını sanabiliyorlar. Biz paradan 6 sıfır attık. Ancak örneğin dolardan hiçbir zaman sıfır
atılmadı. ABD'deki enflasyon çok düşük olsa bile 70'li yıllardan bu yana enflasyondaki bileşik artış aradaki farkı dikkate
değer hale gelmektedir.) BHP veri kümesi aşağıdaki bağlantıdan indirilebilir:
https://www.kaggle.com/datasets/vikrishnan/boston-house-prices
Buradan veri kümesini bir zip dosyası biçiminde indireceksiniz. Zip dosyasııldığında "housing.csv" isimli dosya elde
edilmektedir. Veri kümesi aşağıdaki görünümdedir:
0.00632 18.00 2.310 0 0.5380 6.5750 65.20 4.0900 1 296.0 15.30 396.90 4.98 24.00
0.02731 0.00 7.070 0 0.4690 6.4210 78.90 4.9671 2 242.0 17.80 396.90 9.14 21.60
0.02729 0.00 7.070 0 0.4690 7.1850 61.10 4.9671 2 242.0 17.80 392.83 4.03 34.70
0.03237 0.00 2.180 0 0.4580 6.9980 45.80 6.0622 3 222.0 18.70 394.63 2.94 33.40
0.06905 0.00 2.180 0 0.4580 7.1470 54.20 6.0622 3 222.0 18.70 396.90 5.33 36.20
0.02985 0.00 2.180 0 0.4580 6.4300 58.70 6.0622 3 222.0 18.70 394.12 5.21 28.70
0.08829 12.50 7.870 0 0.5240 6.0120 66.60 5.5605 5 311.0 15.20 395.60 12.43 22.90
0.14455 12.50 7.870 0 0.5240 6.1720 96.10 5.9505 5 311.0 15.20 396.90 19.15 27.10
............
Burada da görüldüğü gibi her ne kadar dosyasının uzantısı ".csv" ise de aslında sütunlar virgüllerle değil SPACE karakterleriyle
ayrıştırılmıştır. Tüm sütunlarda sayısal bilgiler olduğu için aslında bu dosya en kolay bir biçimde NumPy'ın loadtxt
fonksiyonuyla okunabilir. Örneğin:
dataset = np.loadtxt('housing.csv')
Ancak biz kursumuzda ilk aşamaları hep Pandas ile yaptığımızdan aynı süreçleri izlemek için okumayı da yine Pandas'ın read_csv
fonksiyonuyla yapacağız. Tabii read_csv fonksiyonunda yine delimiter parametresi boşlukları belirten "düzenli ifade (regular
expression)" biçiminde olmalıdır. Dosyada bir başlık kısmının olmadığına da dikkat ediniz. read_csv fonksiyonu ile okumayı
şöyle yapabiliriz:
df = pd.read_csv('housing.csv', header=None, delimiter=r'\s+')
Veri kümesindeki sütunlar için İngilizce olarak aşağıdaki açıklamalar yapılmıştır:
1. CRIM: per capita crime rate by town
2. ZN: proportion of residential land zoned for lots over 25,000 sq.ft.
3. INDUS: proportion of non-retail business acres per town
4. CHAS: Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)
5. NOX: nitric oxides concentration (parts per 10 million)
6. RM: average number of rooms per dwelling
7. AGE: proportion of owner-occupied units built prior to 1940
8. DIS: weighted distances to five Boston employment centers
9. RAD: index of accessibility to radial highways
10. TAX: full-value property-tax rate per $10,000
11. PTRATIO: pupil-teacher ratio by town
12. B: 1000(Bk0.63)2 where Bk is the proportion of blacks by town
13. LSTAT: % lower status of the population
14. MEDV: Median value of owner-occupied homes in $1000s
Buradaki son sütun evin fiyatını 1000 dolar cinsinden belirtmektedir. Veri kümesinde eksik veri yoktur. Veri kümesinin
4'üncü sütununda kategorik bir bilgi bulunmaktadır. Ancak bu alanda yalnızca 0 ve 1 biçiminde iki değer vardır. İki değerli
sütunlar için "one-hot encoding" işlemine gerek olmadığını anımsayınız. Ancak 9'uncu sütunda ikiden daha fazla sınıf içeren
kategorik bir bilgi bulunmaktadır. Dolayısıyla bu sütun üzerinde "one-hot encoding" dönüştürmesi uygulamalıyız. Sütunlar
arasında önemli basamaksal farklılıklar göze çarpmaktadır. Yani veri kümesi üzerinde özellik ölçeklemesinin yapılması
gerekmektedir. Veri kümesinin sütunlarında aşırı uç değerler (outliers) de bulunmamaktadır. Özellik ölçeklemesi için standart
ölçekleme ya da min-max ölçeklemesi kullanılabilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
37. Ders - 12/05/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
BHP veri kümesi için oluşturulmuş olan CSV dosyasında bir başlık kısmı olmadığına dikkat ediniz. Pandas'ın read_csv fonksiyonu
ile CSV dosyası okunurken eğer dosyada başlık kısmı yoksa Pandas sütun başlık isimlerini 0'dan başlayarak sayısal biçimde
vermektedir.
8'inci sütun kategorik olduğu için o sütunu "one-hot encoding" işlemine sokalım:
highway_class = df.iloc[:, 8].to_numpy()
ohe = OneHotEncoder(sparse_output=False)
ohe_highway = ohe.fit_transform(highway_class.reshape(-1, 1))
Şimdi "one-hot encoding" işleminden elde ettiğimiz matrisi DataFrame nesnesinin sonuna yerleştirebiliriz. Tabii bundan
önce bu sütunu silip "y" verilerini de ayırmalıyız:
dataset_y = df.iloc[:, -1].to_numpy()
df.drop([8, 13], axis=1, inplace=True)
dataset_x = pd.concat([df, pd.DataFrame(ohe_highway)], axis=1).to_numpy()
Şimdi elimizde NumPy dizisine dönüştürülmüş olan dataset_x ve dataset_y verileri var. Artık veri kümesini eğitim ve test
olmak üzere ikiye ayırabiliriz:
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.1)
Bu işlemden sonra diğer adımları uygulayabiliriz. Ancak önce eğitim veri kümesini ölçeklememiz gerekir:
ss = StandardScaler()
ss.fit(training_dataset_x)
scaled_dataset_x = ss.transform(training_dataset_x)
Burada standart ölçekleme uyguladık. Ancak bu veri kümesine min-max ölçeklemesi de uygulayabilirsiniz.
Veri kümesi için iki saklı katman içeren klasik model kullanılabilir:
model = Sequential(name='Boston-Housing-Prices')
model.add(Input((training_dataset_x.shape[1], ), name='Input'))
model.add(Dense(64, activation='relu', name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
model.compile('rmsprop', loss='mse', metrics=['mae'])
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=200, validation_split=0.2)
Modelin çıktı katmanındaki aktivasyon fonksiyonunun "linear" olarak, loss fonksiyonunun "mean_sequred_error", metrik
değerin de "mean_absolute_error" olarak dikkat ediniz.
Aşağıda BHP veri kümesi uygulamasının tüm kodlar verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
highway_class = df.iloc[:, 8].to_numpy()
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse_output=False)
ohe_highway = ohe.fit_transform(highway_class.reshape(-1, 1))
dataset_y = df.iloc[:, -1].to_numpy()
df.drop([8, 13], axis=1, inplace=True)
dataset_x = pd.concat([df, pd.DataFrame(ohe_highway)], 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.1)
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.models import Sequential
from tensorflow.keras.layers import Dense, Input
model = Sequential(name='Boston-Housing-Prices')
model.add(Input((training_dataset_x.shape[1], ), name='Input'))
model.add(Dense(64, activation='relu', name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
model.compile('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=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=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=(14, 6))
plt.title('Mean Absolute Error - Validation Mean Absolute Error Graph', fontsize=14, pad=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, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
"""
import pickle
model.save('boston-housing-prices.h5')
with open('boston-housing-prices.pickle', 'wb') as f:
pickle.dump([ohe, ss], f)
"""
predict_df = pd.read_csv('predict-boston-housing-prices.csv', delimiter=r'\s+', header=None)
highway_class = predict_df.iloc[:, 8].to_numpy()
ohe_highway = ohe.transform(highway_class.reshape(-1, 1))
predict_df.drop(8, axis=1, inplace=True)
predict_dataset_x = pd.concat([predict_df, pd.DataFrame(ohe_highway)], axis=1).to_numpy()
scaled_predict_dataset_x = ss.transform(predict_dataset_x )
predict_result = model.predict(scaled_predict_dataset_x)
for val in predict_result[:, 0]:
print(val)
# predict-boston-hosing-prices.csv
0.13554 12.50 6.070 0 0.4090 5.5940 36.80 6.4980 4 345.0 18.90 396.90 13.09
0.12816 12.50 6.070 0 0.4090 5.8850 33.00 6.4980 4 345.0 18.90 396.90 8.79
0.08826 0.00 10.810 0 0.4130 6.4170 6.60 5.2873 4 305.0 19.20 383.73 6.72
0.15876 0.00 10.810 0 0.4130 5.9610 17.50 5.2873 4 305.0 19.20 376.94 9.88
0.09164 0.00 10.810 0 0.4130 6.0650 7.80 5.2873 4 305.0 19.20 390.91 5.52
0.19539 0.00 10.810 0 0.4130 6.2450 6.20 5.2873 4 305.0 19.20 377.17 7.54
#----------------------------------------------------------------------------------------------------------------------------
38. Ders 18/05/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Makine öğrenmesi ve veri bilimine ilişkin pek çok kğtğphane ve framework'te hazır bazı veri kümeleri bulunabilmektedir.
Örneğin Keras içerisinde tensorflow.keras.datasets modülünde aşağıdaki veri kümeleri hazır biçimde bulunmaktadır:
boston_housing
california_housing
cifar10
cifar100
fashion_mnist
imdb
mnist
reuters
Keras dokümanlarında BHS veri kümesi için etik bir uyarı da eklenmiştir. Bu veri kümelerinin hepsi benzer biçimde
kullanılmaktadır: Önce yukarıdaki modüller import edilir sonra da modüllerin load_data isimli fonksiyonu çağrılır.
Bu fonksiyonlar ikişer elemandan oluşan ikili bir demet vermektedir. Dolayısıyla bu load_data fonksiyonu aşağıdaki gibi
ılabilir:
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = boston_housing.load_data()
Görüldüğü gibi load_data fonksiyonu zaten eğitim ve test veri kğmelerini ayrıştırılmış olarak bize vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şimdi Keras içerisindeki hazır boston_housing veri kğmesini kullanan bir örnek yapalım. Veri kümesi aşağıdaki gibi elde
edilebilir:
from tensorflow.keras.datasets import boston_housing
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = boston_housing.load_data()
8'inci sütunu OneHotEncoding yapabiliriz:
ohe = OneHotEncoder(sparse_output=False, dtype='uint8')
ohe_result_training = ohe.fit_transform(training_dataset_x[:, 8].reshape(-1, 1))
ohe_result_test = ohe.transform(test_dataset_x[:, 8].reshape(-1, 1))
Artık 8'inci sütunu silip one-hot encoding edilmiş biçimi veri kümelerine eklememiz gerekir. Tabii bu tür durumlarda aslında
one-hot encoding yapılacak sütunların hepsini tek hamlede one-hot encoding yapıp bunları veri kümesinin sonuna ekleyebiliriz.
Ancak burada bir değişiklik olsun diye one-hot encoding yapılacak verileri kategorik sütunun yerine (8'inci sütun) insert
edelim:
training_dataset_x = np.delete(training_dataset_x, 8, axis=1)
test_dataset_x = np.delete(test_dataset_x, 8, axis=1)
training_dataset_x = np.insert(training_dataset_x, [8], ohe_result_training, axis=1)
test_dataset_x = np.insert(test_dataset_x, [8], ohe_result_test, axis=1)
Yukarıda da belirttiğimiz gibi bu işlem aslında append fonksiyonuyla ya da concatenate fonksiyonuyla da yapılabilir.
Geri kalan işlemler önceki örnekte olduğu gibi yürütülebilir. Aşağıda tüm örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.datasets import boston_housing
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = boston_housing.load_data()
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse_output=False, dtype='uint8')
ohe_result_training = ohe.fit_transform(training_dataset_x[:, 8].reshape(-1, 1))
ohe_result_test = ohe.transform(test_dataset_x[:, 8].reshape(-1, 1))
import numpy as np
training_dataset_x = np.delete(training_dataset_x, 8, axis=1)
test_dataset_x = np.delete(test_dataset_x, 8, axis=1)
training_dataset_x = np.insert(training_dataset_x, [8], ohe_result_training, axis=1)
test_dataset_x = np.insert(test_dataset_x, [8], ohe_result_test, axis=1)
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.models import Sequential
from tensorflow.keras.layers import Dense, Input
model = Sequential(name='Boston-Housing-Prices')
model.add(Input((training_dataset_x.shape[1], ), name='Input'))
model.add(Dense(64, activation='relu', name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
model.compile('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=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=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=(14, 6))
plt.title('Mean Absolute Error - Validation Mean Absolute Error Graph', fontsize=14, pad=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, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
"""
import pickle
model.save('boston-housing-prices.h5')
with open('boston-housing-prices.pickle', 'wb') as f:
pickle.dump([ohe, ss], f)
"""
import pandas as pd
predict_df = pd.read_csv('predict-boston-housing-prices.csv', delimiter=r'\s+', header=None)
predict_dataset_x = predict_df.to_numpy()
ohe_result_predict = ohe.transform(predict_dataset_x [:, 8].reshape(-1, 1))
predict_dataset_x = np.delete(predict_dataset_x, 8, axis=1)
predict_dataset_x = np.insert(predict_dataset_x, [8], ohe_result_predict, axis=1)
scaled_predict_dataset_x = ss.transform(predict_dataset_x )
predict_result = model.predict(scaled_predict_dataset_x)
for val in predict_result[:, 0]:
print(val)
# predict-boston-housing-pripces.csv
0.13554 12.50 6.070 0 0.4090 5.5940 36.80 6.4980 4 345.0 18.90 396.90 13.09
0.12816 12.50 6.070 0 0.4090 5.8850 33.00 6.4980 4 345.0 18.90 396.90 8.79
0.08826 0.00 10.810 0 0.4130 6.4170 6.60 5.2873 4 305.0 19.20 383.73 6.72
0.15876 0.00 10.810 0 0.4130 5.9610 17.50 5.2873 4 305.0 19.20 376.94 9.88
0.09164 0.00 10.810 0 0.4130 6.0650 7.80 5.2873 4 305.0 19.20 390.91 5.52
0.19539 0.00 10.810 0 0.4130 6.2450 6.20 5.2873 4 305.0 19.20 377.17 7.54
#----------------------------------------------------------------------------------------------------------------------------
Biz regresyon terimini daha çok "çıktının bir sınıf değil sayısal bir değer olduğu" modeller için kullanıyorduk. Bu paragrafta
daha genel bir terim olarak kullanacağız. Regresyon çeşitli biçimlerde yani çeşitli yöntemlerle gerçekleştirilebilmektedir.
Aslında yapay sinir ağları da regresyon için bir yöntem grubunu oluşturmaktadır.
Regresyon en genel anlamda girdi ile çıktı arasında bir ilişki kurma sürecini belirtmektedir. Matematiksel olarak regresyon
y = f(x) biçiminde bir f fonksiyonunun elde edilme süreci olarak da tanımlanabilir. Eğer biz böyle bir f fonksiyonu bulursak
x değerlerini fonksiyonda yerine koyarak y değerini elde edebiliriz. Tabi y = f(x) fonksiyonunda x değişkeni aslında
(x0, x1, x2, ..., xn) biçiminde birden fazla değişkeni de temsil ediyor olabilir. Bu durumda f fonksiyonu f((x0, x1, x2, ..., xn))
biçiminde çok değişkenli bir fonksiyon olacaktır. Benzer biçimde y = f(x) eşitliğinde f fonksiyonu birden fazla değer de
veriyor olabilir. Yani buradaki y değeri (y0, y1, y2, ..., ym) biçiminde de 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 (x değişkeni) bir tane ise buna genellikle "basit regresyon (simple regression)" denilmektedir.
- Eğer bağımsız değişken (x değişleni) birden fazla ise buna da genellikle "çoklu regresyon (mulptiple regression)"
denilmektedir.
- Eğer girdiyle çıktı arasında doğrusal bir ilişki kurulmak isteniyorsa (yani regresyon işleminden doğrusal bir fonksiyon
elde edilmek isteniyorsa) bu tür regresyonlara "doğrusal regresyon (linear regression)" denilmektedir. Doğrusal regresyon
da bağımsız değişken 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ğımlı değişken arasında polinomsal ilişki kurulmaya çalışılabilir. (Yani regresyon sonucunda
bir polinom 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 fonksiyon da oluşturulmak
istenebilir). Bu tür regresyonlara "doğrusal olmayan regresyon (nonlinear regressions)" denilmektedir. Yukarıda da belirttiğimiz
gibi her ne kadar polinomlar doğrusal fonksiyonlar olmasa da bunlar transformasyonla doğrusal hale getirilebildikleri için doğrusal
olmayan regresyon denildiğinde genel olarak polinomsal regresyonlar 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))
Regresyon terminolojisinde "çok değişkenli" sözcüğü bağımsız değişkenin birden fazla olmasını değil (buna "çoklu" denilmektedir)
bağımlı değişkenin birden fazla olmasını anlatan bir terimdir. İngilizce bu bağlamda "çok değişkenli" terimi "multivariate"
biçiminde ifade edilmektedir.
- Eğer regresyonun çıktısı kategorik değerler ise yani f fonksiyonu kategorik bir değer üretiyorsa buna "lojistik regresyon
(logictic 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şuyorsa böyle lojistik regresyonlara da "çok sınıflı lojistik
regresyon (multi-class/multinomial logistic regression)" denilmektedir. Tabii aslında makine öğrenmesinde ve sinir sinir ağlarında
"lojistik regresyon" terimi yerine "sınıflandırma (classification)" terimi tercih edilmektedir. Bizim de genellikle (ancak her
zaman değil) kategorik kestirim modellerine "lojistik regresyon modelleri" yerine "sınıflandırma problemleri" dediğimizi
anımsayınız.
- Sınıflandırma problemlerinde bir de "etiket (label)" kavramı sıklıkla karşımıza çıkmaktadır. Etiket genellikle çok
değişkenli (multivariate) 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 obez olup olmadığı", "kişinin mutlu olup olmadığı". Burada üç tane etiket vardır. Sınıf kavramının belli
bir etiketteki kategorileri belirtmek için kullanıldığına dikkat ediniz. Etiketlerin sayısına göre lojistik regresyon modelleri
(yani "multivariate lojistik regresyon" modelleri) genellikle aşağıdaki gibi sınıflandırılmaktadır:
- Tek Etiketli İki Sınıflı Sınıflandırma (Single Label Binary Classification) Modelleri: Bu modellerde çıktı yani etiket
bir tanedir. Etiket de iki sınıftan oluşmaktadır. Örneğin bir tümörün iyi huylu mu kötü huylu mu olduğunu kestirimeye çalışan
model tek etiketli iki sınıflı modeldir.
- Tek Etiketli Çok Sınıflı Sınıflandırma (Single Label Multiclass) Modelleri: Burada bir tane çıktı vardır. Ancak çıktı
ikiden fazla sınıftan oluşmaktadır. Örneğin bir resmin "elma mı, armut mu, kayısı mı" olduğunu anlamaya çalışan sınıflandırma
problemi tek etiketli çok sınıflı bir modeldir.
- Çok Etiketli İki Sınıflı Sınıflandırma (Multilabel Binary Classification) Modelleri: Çok etiketli modeller denildiğinde
zaten default olarak iki sınıflı çok etiketli modeller anlaşılmaktadır. Örneğin bir yazının içeriğine göre yazıyı
tag'lamak istediğimizde her tag ayrı bir etikettir. O tag'ın olması ya da olmaması da iki sınıflı bir çıktı belirtmektedir.
- Çok Etiketli Çok Sınıflı Sınıflandırma (Multilabel Multiclass / Multidimentional Classification) Modelleri: Bu tür
modellere genellikle "çok boyutlu (multidimentional)" modeller denilmektedir. Yani çıktı birden fazladır. Her çıktı da
ikiden fazla sınıfa ilişkin olabilmektedir. Bu modelleri çok etiketli sınıflandırma modellerinin genel biçimi olarak
düşünebilirsiniz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de tek etiketli çok sınıflı bir sınıflandırma problemine örnek verelim. Örneğimizde "iris (zambak)" isimli bir veri
kümesini kullanacağız. Bu veri kümesi bu tür uygulamalarda örnek veri kümesi olarak çok sık kullanılmaktadır. Veri kümesi a
şağıdaki bağlantıdan indirilebilir:
https://www.kaggle.com/datasets/uciml/iris?resource=download
Yukarıdaki bağlantıdan Iris veri kümesi ibir zip dosyası biçiminde indirilmektedir. Bu zip dosyasııldığında "Iris.csv"
dosyası elde edilecektir.
Veri kümesi aşağıdaki görünümdedir:
Id,SepalLengthCm,SepalWidthCm,PetalLengthCm,PetalWidthCm,Species
1,5.1,3.5,1.4,0.2,Iris-setosa
2,4.9,3.0,1.4,0.2,Iris-setosa
3,4.7,3.2,1.3,0.2,Iris-setosa
4,4.6,3.1,1.5,0.2,Iris-setosa
5,5.0,3.6,1.4,0.2,Iris-setosa
6,5.4,3.9,1.7,0.4,Iris-setosa
7,4.6,3.4,1.4,0.3,Iris-setosa
8,5.0,3.4,1.5,0.2,Iris-setosa
9,4.4,2.9,1.4,0.2,Iris-setosa
......
Veri kümesinde üç grup zambak vardır: "Iris-setosa", "Iris-versicolor" ve "Iris-virginica". x verileri ise çanak (sepal)
yaprakların ve taç (petal) 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 bu sütunun bir faydası yoktur.
Çok sınıflı sınıflandırma (multiclass lojistik regresyon) problemlerinde çıktıların (yani y verilerinin) "one-hot encoding"
işlemine sokulması gerekir. Çıktı sütunu one-hot encoding yapıldığında uygulamacının hangi sütunların hangi sınıfları belirttiğini
biliyor olması gerekir. (Anımsanacağı gibi Pandas'ın get_dummies fonksiyonu aslında unique fonksiyonunu ile elde ettiği unique değerleri
sort ettikten sonra "one-hot encoding" işlemi yapmaktadır. (Dolayısıyla aslında get_dummies fonksiyonu sütunları kategorik değerleri
küçükten büyüğe sıraya dizerek oluşturmaktadır. Scikit-learn içerisindeki OneHotEncoder sınıfı zaten kendi içerisinde categories_
özniteliği le bu sütunların neler olduğunu bize vermektedir. Tabii aslında OneHotEncoder sınıfı da kendi içerisinde unique işlemini
uygulamaktadır. NumPy'ın unique fonksiyonunun aynı zamanda sıraya dizmeyi de yaptığını anımsayınız. Yani aslında categories_
özniteliğindeki kategoriler de leksikografik olarak sıraya dizilmiş biçimdedir.)
Veri kümesini aşağıdaki gibi okuyabiliriz:
df = pd.read_csv('Iris.csv')
x verilerini aşağıdaki gibi ayrıştırabiliriz:
dataset_x = df.iloc[:, 1:-1].to_numpy(dtype='float32')
y verilerini aşağıdaki gibi onet hot encoding yaparak ayrıştırabiliriz:
ohe = OneHotEncoder(sparse= False)
dataset_y = ohe.fit_transform(df.iloc[:, -1].to_numpy().reshape(-1, 1))
Anımsanacağı gibi çok sınıflı sınıflandırma problemlerindeki loss fonksiyonu "categorical_crossentropy", çıktı katmanındaki
aktivasyon fonksiyonu "softmax" olmalıdır. Metrik değer olarak "binary_accuracy" yerine "categorical_accuracy" kullanılmalıdır.
(Keras metrik değer olarak "accuracy" girildiğinde zaten problemin türüne göre onu "binary_accuracy" ya da "categorical_accuracy"
biçiminde ele alabilmektedir.) Veri kümesi yine özellik ölçeklemesine sokulmalıdır. Bunun için standart ölçekleme kullanılabilir.
Sinir ağı modelini şöyle oluşturulabiliriz:
model = Sequential(name='Iris')
model.add(Input((training_dataset_x.shape[1], ), name='Input'))
model.add(Dense(64, activation='relu', name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(dataset_y.shape[1], activation='softmax', name='Output'))
model.summary()
Ç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 çıktı değerlerinin 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 aslında 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ılabilir. Pekiyi varsayalım ki ilki 0 olmak üzere 2 numaralı 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ütununun one hot
encoding sonucundaki 2 numaralı sütununu 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. Zaten OneHotEncoder sınıfının bu bilgiyi categories_ örnek
özniteliğinde sakladığını anımsayınız.
Aşağıda örneğin tüm kodu bütünsel olarak verilmiş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 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.1)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(training_dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Input
model = Sequential(name='Iris')
model.add(Input((training_dataset_x.shape[1], ), name='Input'))
model.add(Dense(64, activation='relu', name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(dataset_y.shape[1], activation='softmax', name='Output'))
model.summary()
model.compile('rmsprop', loss='categorical_crossentropy', metrics=['categorical_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=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=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=(14, 6))
plt.title('Categorcal Accuracy - Validation Categorical Accuracy', fontsize=14, pad=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()
scaled_test_dataset_x = ss.transform(test_dataset_x)
eval_result = model.evaluate(scaled_test_dataset_x , test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_dataset_x = pd.read_csv('predict-iris.csv').to_numpy(dtype='float32')
scaled_predict_dataset_x = ss.transform(predict_dataset_x)
import numpy as np
predict_result = model.predict(scaled_predict_dataset_x)
predict_indexes = np.argmax(predict_result, axis=1)
for pi in predict_indexes:
print(ohe.categories_[0][pi])
"""
predict_categories = ohe.categories_[0][predict_indexes]
print(predict_categories)
"""
#predict-iris.csv
4.8,3.4,1.6,0.2
4.8,3.0,1.4,0.1
4.3,3.0,1.1,0.1
6.6,3.0,4.4,1.4
6.8,2.8,4.8,1.4
6.7,3.0,5.0,1.7
6.0,2.9,4.5,1.5
5.7,2.6,3.5,1.0
6.3,2.5,5.0,1.9
6.5,3.0,5.2,2.0
6.2,3.4,5.4,2.3
5.9,3.0,5.1,1.8
#----------------------------------------------------------------------------------------------------------------------------
39. Ders 25/05/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yapay zeka ve makine öğrenmesinin en önemli uygulama alanlarından biri de "doğal dil işleme (natuaral lanuage processing)"
alanıdır. Doğal dil işleme denildiğinde Türkçe, İngilizce gibi konuşma dilleri üzerindeki her türlü işlemler kastedilmektedir.
Bugün doğal dil işlemede artık ağırlıklı olarak makine öğrenmesi teknikleri kullanılmaktadır. Örneğin makine çevirisi (machine
translation) süreci aslında doğal dil işlemenin bir konusudur ancak bugün artık makine çevirileri artık neredeyse tamamen
makine öğrenmesi teknikleriyle yapılmaktadır. Doğal dil işleme alanı ile ilişkili olan diğer bir alan da "metin madenciliği
(text mining)" denilen alandır. Metin madenciliği metinler içerisinden faydalı bilgilerin çekilip alınması ve onlardan faydalanılması
ile ilgili süreçleri belirtmektedir. Bugün metin madenciliğinde de yine veri bilimi ve makine öğrenmesi teknikleri yoğun
olarak kullanılmaktadır.
Metinler üzerinde makine öğrenmesi teknikleri uygulanırken metinlerin önişlemlere sokularak sayısal biçime dönüştürülmesi
gerekir. Çünkü makine öğrenmesi tekniklerinde yazılar üzerinde değil sayılar üzerinde işlemler yapılmaktadır. Bu nedenle
makine öğrenmesinde yazıların önişleme sokulması ve sayısal hale dönüştürülmesi sürecinde doğal dil işleme ve metin madenciliği
alanlarındaki daha önceden elde edilmiş bilgiler ve deneyimlerden faydalanılmaktadır. Örneğin bir film hakkında aşağıdaki gibi
bir yazı olsun:
"Filmi pek beğenmedim. Oyuncular iyi oynayamamışlar. Filmde pek çok abartılı sahneler de vardı. Neticede filmin iyi mi
kötü mü olduğu konusunda kafam karışık. Size tavsiyem filme gidip boşuna para harcamayın!"
Bu yazıyı sayısal hale dönüştürmeden önce yazı üzerinde bazı önişlemlerin yapılması gerekebilmektedir. Tipk önişlemler
şunlardır:
- Yazıyı sözcüklere ayırma ve noktalama işaretlerini atmak (tokenizing)
- Sözükleri küçük harfe ya da büyük harfe dönüştürmek (transformation)
- Kendi başına anlamı olyaman edatlar gibi, soru ekleri gibi sözcüklerin atılması (bunlara İngilizce "stop words")
denilmektedir.
- Sözcüklerin köklerini elde ve köklerini kullanmak (stemming)
- Bağlam içerisinde farklı sözcükleri aynı sözcükle yer değiştirmek (lemmatization)
Yukarıdaki işlemleri yapabilen çeşitli kütüphaneler de bulunmaktadır. Bunlardan Python'da en çok kullanılanlardan biri
NLTK isimli kütüphanedir.
Sözcükleri birbirinden bağımsız sayılarmış gibi ele alarak denetimli ya da denetimsiz modeller oluşturulabilmektedir.
Ancak son 20 yılda yazılardaki sözcüklerin bir bağlam içerisinde ele alınabilmesine yönelik sinir ağları geliştirilmiştir.
Bunun ağlarda sözcüklerin sırası dikkate alınmakta ve sinir ağına bir hafıza kazandırılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Sınıflandırma problemlerinde üzerinde çokça çalışılan İngilizce "sentiment analysis" denilen bir problem grubu vardır.
Biz buna Türkçe "duygu analizi" diyeceğiz. Duygu analizi problemlerinde kişiler bir olgu hakkında kanılarını belirten yazılar
yazarlar. Bu yazılar tipik olarak "olumlu", "olumsuz" biçiminde iki sınıflı etiketlenmektedir. Ancak çok sınıflı
etiketlendirmeler de söz konusu olabilmektedir. Böylece eğitim sonrasında bir yazının olumulu mu olumsuz mu olduğu yönünde
kestirimler yapılabilmektedir.
Duygu analizi için oluşturulmuş çeşitli örnek veri kümeleri vardır. Bunlardan en çok kullanılanlarından biri "IMDB (Internet
Movie Database)" isimli veri kümesidir. Bu veri kümesinde kişiler filmler hakkında yorum yazıları yazmışlardır. Bu yorum
yazıları "olumlu (positive)" ya da "olumsuz (negative)" olarak etiketlendirilmiştir. Böylece eğitim sonrasında birisinin yazdığı
yazının olumlu ya da olumsuz yargı içerdiği otomatik olarak tespit edilebilmektedir.
IMDB veri kümesindeki girdiler (yani dataset_x) yazılardan oluşmaktadır. Çıktılar ise (yani dataset_y) "olumlu" ya da "olumsuz"
biçiminde ikili bir çıktıdır. IMDB veri kümesini aşağıdaki bağlantıdan indirebilirsiniz:
https://www.kaggle.com/datasets/lakshmi25npathi/imdb-dataset-of-50k-movie-reviews
Buradan veri kümesi zip dosyası olarak indirilir. Açıldığında "IMDB Dataset.csv" isimli CSV dosyası elde edilmektedir.
Bu CSV dosaysında "review" ve "sentiment" isimli iki sütun vardır. "review" sütunu film hakkındaki yorum yazılarını
"sentiment" sütunu ise "positive" ya da "negative" biçiminde kanıları içermektedir. Buradaki modelin "ikili sınflandırma"
problemi olduğuna dikkat ediniz.
Duygu analizi problemlerinde 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. Yazıların sayısal hale dönüştürülmesi için tipik olarak iki yöntem kullanılmaktadır:
1) Vektörizasyon (vectorization) yöntemi
2) Sözcük Gömme (Word Embedding) yöntemi
Her iki yöntemde de önce yazılar sözcüklere ayrılır ve gerekli görülen önişlemlerden geçirilir. Böylece bir yazı
bir sözcük grubu haline getirilir. Biz burada en basit yöntem olan "vektörizasyon" yöntemi üzerinde duracağız.
Sözcük gömme yöntemini sonraki paragraflarda ele alacağız.
Vektörizasyon işlemi şöyle uygulanmaktadır:
1) Tüm yorumlardaki tüm sözcüklerin kümesine "kelime haznesi (vocabulary)" denilmektedir. Örneğin IMDB veri kümesinde tek
olan (unique) tüm sözcüklerin sayısı 50000 ise kelime haznesi bu 50000 sözcükten oluşmaktadır.
2) Veri kümesindeki x verileri yorum sayısı kadar satırdan, sözcük haznesindeki sözcük sayısı kadar sütundan oluşan iki boyutlu
bir matris biçiminde oluşturulur. Örneğin sözcük haznesindeki sözcük sayısı 50000 ise ve toplamda veri kümesinde 10000 yorum
varsa x veri kümesi 10000x50000 büyüklüğünde bir matris biçimindedir. Bir yorum bu matriste bir satır ile temsil edilmektedir.
Yoruma ilişkin satırda eğer sözcük haznesindeki bir sözcük kullanılmışsa o sözcüğe ilişkin sütun 1, kullanılmamışsa 0
yapılmaktadır. Böylece yorum yazıları 0'lardan ve 1'lerden oluşmuş olan eşit uzunluklu sayı dizilerine dönüştürülmüş olur.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
40. Ders - 26/05/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Örneğin elimizdeki yorum yazıları şunlar olsun:
"Film güzeldi. Ancak uzundu."
"Film güzel değildi."
"Film kötüydü. Tavsiye etmem."
"Film iyiydi. Tavsiye ederim."
"Film kötü değildi."
Yukarıda da belirttiğimiz gibi yazılar üzerinde doğal dil işlemede kullanılan bazı tekniklerle önişlem yapılması uygun
olmaktadır. Ancak biz burada yalnızca noktalama işaretlerini atıp sözcükleri küçük harfe dönüştürelim. Bu durumda sözcük
haznesi şöyle olacaktır:
film
güzeldi
ancak
güzel
uzundu
değildi
kötüydü
tavsiye
etmem
iyiydi
ederim
kötü
Burada tüm bu sözcükleri bir sözlükte toplayarak bunlara birer numara verelim:
film 0
güzeldi 1
ancak 2
uzundu 3
güzel 4
değildi 5
kötüydü 6
tavsiye 7
etmem 8
iyiydi 9
ederim 10
kötü 11
Burada toplam 5 tane yorum vardır ve sözcük haznesinin büyüklüğü de 12'dir. Bu durumda örneğin birinci yorum şöyle vektör
haline getirilir:
0 1 2 3 4 5 6 7 8 9 0 1
----------------------------
1 1 1 1 0 0 0 0 0 0 0 0
Diğer yorumlar da şöyle kodlanacaktır:
0 1 2 3 4 5 6 7 8 9 0 1
----------------------------
1 1 1 1 0 0 0 0 0 0 0 0 (1. Yorum: "Film güzeldi. Ancak uzundu.")
1 0 0 0 1 1 0 0 0 0 0 0 (2. Yorum: "Film güzel değildi.")
1 0 0 0 0 0 1 1 1 0 0 0 (3. Yorum: "Film kötüydü. Tavsiye etmem.")
1 0 0 0 0 0 0 1 0 1 1 0 (4. Yorum: "Film iyiydi. Tavsiye ederim.")
1 0 0 0 0 1 0 0 0 0 0 1 (5. Yorum: "Film kötü değildi.")
Yorum yazılarını sözcüklerin numaralarından oluşan sayılar biçiminde de ifade edebiliriz. Örneğin:
0, 1, 2, 3 (1. Yorum: "Film güzeldi. Ancak uzundu.")
0, 4, 5 (2. Yorum: "Film güzel değildi.")
0, 6, 7, 8 (3. Yorum: "Film kötüydü. Tavsiye etmem.")
0, 7, 9, 10 (4. Yorum: "Film iyiydi. Tavsiye ederim.")
0, 11, 5 (5. Yorum: "Film kötü değildi.")
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 "seyrek matris (sparse matrix)" denilmektedir. Buradaki vektörlerin seyrek biçimde olduğuna
dikkat ediniz. Eğer sözcük haznesi ç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 bizden training_dataset_x ve training_dataset_y yi bir
bütün olarak istemektedir. Bu durumda değişik teknikler kullanılabilmektedir. İzleyen paragraflarda bu teknikler üzerinde de
duracağız.
Yukarıdaki gibi vektörizasyon işleminde sözcükler arasında sırasal bir ilişkinin ortadan kaldırıldığına dikkat ediniz.
Bu biçimde uygulanan vektörüzasyon sözcükleri bağlamı içerisinde değerlendirmeye olanak sağlamayacaktır. Ayrıca yukarıdaki
vektörizasyon ikili (binary) biçimdedir. Yani yazı içerisinde aynı sözcükten birden fazlakez kullanılmış olsa da o sözcüğe
ilişkin sütun elemanı 1 yapılmıştır. Ancak istenirse vektör ikili olmaktan çıkartılıp sözcüklerin frekanslarıyla da oluşturulabilir.
Örneğin "film" sözcüğü yazı içerisinde 10 kere geçmişse vektörde ona karşılık gelen eleman 1 yapılmak yerine 10 yapılabilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de IMDB örneğinde yukarıda açıkladığımız ikili vektörüzasyon işlemini programlama yoluyla yapalım. Önce veri kümesini
okuyalım:
df = pd.read_csv('IMDB Dataset.csv')
Şimdi tüm yorumlardaki farklı olan tüm sözcüklerden bir sözcük haznesi (vocabulary) oluşturalım:
import re
vocab = set()
for text in df['review']:
words = re.findall('[a-zA-Z0-9]+', text.lower())
vocab.update(words)
Burada Python'daki "düzenli ifade (regular expression)" kütüphanesinden faydalanılmıştır. re modülündeki findall fonksiyonu
ile yazı içerisindeki sözcükler elde edilmiştir. Şimdi de sözcük haznesindeki her bir sözcüğe bir numara verelim. Sözcüğe göre
arama yapılacağı için bir sözlük nesnesinin kullanılması uygun olacaktır. Bu işlem sözlük içlemi ile tek hamlede gereçekleştirilebilir:
vocab_dict = {word: index for index, word in enumerate(vocab)}
Aslında burada yapılan şey aşağıdaki ile aynıdır:
vocab_dict = {}
for index, word in enumerate(vocab):
vocab_dict[word] = index
Şimdi artık x veri kümesini oluşturalım. Bunun için önce içi sıfırlarla dolu bir matris oluşturalım. Bu matrisin satır sayısı
len(df) kadar (yani yorum sayısı kadar) sütun sayısı ise sözcük haznesi kadar (yani len(vocab) kadar) olmalıdır:
dataset_x = np.zeros((len(df), len(vocab)), dtype='uint8')
Şimdi yeniden tüm yorumları tek tek sözcüklere ayırıp onları sayısallaştırıp dataset_x matrisinin ilgili staırının ilgili
sütunlarını 1 yapalım:
for row, text in enumerate(df['review']):
words = re.findall('[a-zA-Z0-9]+', text.lower())
word_numbers = [vocab_dict[word] for word in words]
dataset_x[row, word_numbers] = 1
y değerlerini de "positive" için 1, "negatif" için 0 biçiminde oluşturabiliriz:
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
Veri kümsini eğitim ve test biçiminde ikiye ayırabiliriz:
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
Artık dataset_x ve dataset_y hazırlanmıştır. Bundan sonra ikili sınıflandırma problemi için daha önce kullandığımız sinir
ı modellerini aynı biçimde oluşturabiliriz Ancak girdi katmanında çok fazla nöron olduğu için katmanlardaki nöron sayılarını
yükseltebiliriz.
model = Sequential(name='IMDB')
model.add(Input((training_dataset_x.shape[1],)))
model.add(Dense(128, activation='relu', 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)
Bu veri kümesinden bu haliyle %88 civarında bir başarı elde edilmektedir. Veri kümesi için predict işlemi de yine benzer
biçimde yapılabilir. Bunun için aşağıdaki gibi "predict.imdb" isimli CSV dosyası oluşturabiliriz:
review
The movie was very bad. The players showed a poor performance. The script is not interesting.
I liked the movie very much. The players also played well. The ending of the movie was very surprising.
The movie is a work of medium quality. I think it's neither good nor bad.
Great movies should always leave you with a feeling during or after seeing them.
The worst movie I've ever seen in my life
I can't say that I am very happy that I went to this movie.
Bu CSV dosyasının okunması ve hazır hale getirilmesi işlemi de şöyle yapılmıştır:
predict_df = pd.read_csv('predict-imdb.csv')
predict_dataset_x = np.zeros((len(predict_df), len(vocab)))
for row, text in enumerate(predict_df['review']):
words = re.findall('[a-zA-Z0-9]+', text.lower())
word_numbers = [vocab_dict[word] for word in words]
predict_dataset_x[row, word_numbers] = 1
Kestirim işlemini de şöyle yapabiliriz:
predict_result = model.predict(predict_dataset_x)
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
Pekiyi biz binary vektör haline geitirilmiş yazıyı bu vektörden hareketle yeniden orijinal haline getirebilir miyiz? Hayır
getiremeyiz. Çünkü biz burada binary vektör oluşturduğumuz için sözük sıralarını ve sıklıklarını kaybetmiş durumdayız. Ancak
anlamsız olsa da bir vektörü bir yazı haline aşağıdaki gibi getirebiliriz:
rev_vocab_dict = {index: word for word, index in vocab_dict.items()}
word_indices = np.argwhere(dataset_x[0] == 1).flatten()
words = [rev_vocab_dict[index] for index in word_indices]
text = ' '.join(words)
print(text)
NumPy'ın where ya da argwhere fonksiyonları belli koşulu sağlayan elemanların indekslerini bize verebilmektedir. Buradaki
argwhere fonksiyonu bize iki boyutlu bir dizi vermektedir. Biz de onu flatten (ya da reshape) fonkisyonu ile tek boyutlu
dizi haline getirdik, sonra da liste içlemiyle bu indekslere karşı gelen sözcükleri bir liste biçiminde elde ettik. Nihayetinde
de bunları oin metodu aralarına SPACE karakterlerini koyarak jile tek bir yazı biçimine dönüştürdük.
Aşağıda örnek bir bütün olarak verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
vocab = set()
import re
for text in df['review']:
words = re.findall('[a-zA-Z0-9]+', text.lower())
vocab.update(words)
vocab_dict = {word: index for index, word in enumerate(vocab)}
"""
vocab_dict = {}
for index, word in enumerate(vocab):
vocab_dict[word] = index
"""
import numpy as np
dataset_x = np.zeros((len(df), len(vocab)), dtype='uint8')
for row, text in enumerate(df['review']):
words = re.findall('[a-zA-Z0-9]+', text.lower())
word_numbers = [vocab_dict[word] for word in words]
dataset_x[row, word_numbers] = 1
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
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='IMDB')
model.add(Input((training_dataset_x.shape[1],)))
model.add(Dense(128, activation='relu', 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=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
# prediction
predict_df = pd.read_csv('predict-imdb.csv')
predict_dataset_x = np.zeros((len(predict_df), len(vocab)))
for row, text in enumerate(predict_df['review']):
words = re.findall('[a-zA-Z0-9]+', text.lower())
word_numbers = [vocab_dict[word] for word in words]
predict_dataset_x[row, word_numbers] = 1
predict_result = model.predict(predict_dataset_x)
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
# dataset_x'teki birinci yorumun yazı haline getirilmesi
rev_vocab_dict = {index: word for word, index in vocab_dict.items()}
word_indices = np.argwhere(dataset_x[0] == 1).flatten()
words = [rev_vocab_dict[index] for index in word_indices]
text = ' '.join(words)
print(text)
#----------------------------------------------------------------------------------------------------------------------------
41. Ders - 01/06/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yazıların yukarıda uyguladığımız gibi gibi vektörizasyon işlemiyle binary bir vektöre dönüştürülmesinin görünen açık
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.
Yani yukarıdaki örnekte sözcük frekansları dikkate alınmamıştı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. Örneğin 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ğitimde training_dataset_x ve training_dataset_y fit metoduna tek hamlede verilmez. Parça parça
verilir. Keras'ta parçalı eğitim izleyen paragraflarda ele alınacaktır.
- Biz yukarıdaki örnekte doğal dil işlemede kullanılan bazı önişlemleri hiç yapmadık. Bu önişlemlerin yapılması modelin
performansını artıracaktır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aslında vektörizasyon işlemi pratik bir biçimde scikit-learn kütüphanesindeki sklearn.feature_extraction.text modülünde
bulunan CountVectorizer sınıfıyla yapılabilmektedir. Sınıfın kullanımı şöyledir:
1) Ö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 elde edilecek vektörün elemanlarının türünü belirtmektedir. Bu
parametreyi elde edilecek matrisin kaplayacğı yeri azaltmak için 'uint8' gibi küçük bir tür olarak geçmek istebilirsiniz.
Default durumda bu dtype parametresi 'float64' biçimindedir. Sınıf yine default durumda tüm sözcükleri küçük harfe dönüştürmektedir.
Ancak metodun lowercase parametresi False geçilirse bu dönüştürme yapılmamaktadır. Metodun diğer önemli parametreleri de vardır.
Örneğin metodun stop_words parametresi "stop word" denilen anlamsız sözcükleri atmak için kullanılabilir. Bu parametreye
stop words'lerden oluşan bir liste ya da NumPy dizisi girilirse bu sözcükler sözcük haznesinden atılmaktadır. Başka bir
deyişle yokmuş gibi ele alınmaktadır. Metodun binary parametresi default olarak False biçimdedir. Bu durumda bir yazı
içerisinde aynı sözcükten birden fazla kez geçerse vektörün ilgili elemanı 1 değil o sözcüğün sayısı olacak biçimde set
edilmektedir. Biz eğer yukarıkdaki örneğimizde olduğu gibi binary bir vektör oluşturmak istiyorsak bu parametreyi True
yapabiliriz. Metodun diğer parametreleri için scikit-learn dokümanlarına başvurabilirsiniz.
Örneğin:
cv = CountVectorizer(dtype='uint8', stop_words=['de', 'bir', 've', 'mu'], binary=True)
2) 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. Bu
vocabulary_ tıpkı bizim yukarıdaki örnekte yaptığımız gibi anahtarları sözcükler değerleri de sözcüklerin indeksinden oluşan bir
sözlğk biçimindedir. Örneğin:
texts = ["film güzeldi ve senaryo iyidi", "film berbattı, tam anlamıyla berbattı", "seyretmeye değmez",
"oyuncular güzel oynamışlar", "senaryo berbattı, böyle senaryo olur mu?", "filme gidin de bir de siz görün"]
cv = CountVectorizer(dtype='uint8', stop_words=['de', 'bir', 've', 'mu'], binary=True)
cv.fit(texts)
fit işlemi sonrasında elde edilen vocabulary_ sözlüğü şöyledir:
{'film': 4, 'güzeldi': 9, 'senaryo': 14, 'iyidi': 10, 'berbattı': 1, 'tam': 17, 'anlamıyla': 0, 'seyretmeye': 15, 'değmez': 3,
'oyuncular': 13, 'güzel': 8, 'oynamışlar': 12, 'böyle': 2, 'olur': 11, 'filme': 5, 'gidin': 6, 'siz': 16, 'görün': 7}
fit medodunun yalnızca sözük haznesi oluşturduğuna dikkat ediniz. Asıl dönüştürmeyi transform metodu yapmaktadır. Ancak tranform
bize vektörel hale getirilmiş olan yazıları "seyrek matris (sparse matrix)" biçiminde csr_matrix isimli bir sınıf nesnesi olarak
vermektedir. Bu sınıfın todense metodu ile biz bu seyrek matrisi normal matrise dönüştürebiliriz. Örneğin:
dataset_x = cv.transform(dataset).todense()
Aslında fit metodu herhangi bir dolaşılabilir nesneyi parametre olarak kabul etmektedir. Örneğin yazılar satır satır
bulunuyorsa biz doğrudan dosye nesnesini bu fit metoduna verebiliriz. Bu durumda tüm yazıları belleğe okumak zorunda kalmayız.
Örneğin:
from sklearn.feature_extraction.text import CountVectorizer
f = open('text.csv')
cv = CountVectorizer()
cv.fit(f)
Artık bu CountVectorizer nesnesi predict işleminde de aynı biçimde kullanılabilir.
Aşağıda CountVectorizer sinıfının kullanımına ilişkin bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
from sklearn.feature_extraction.text import CountVectorizer
texts = ["film güzeldi ve senaryo iyidi", "film berbattı, tam anlamıyla berbattı", "seyretmeye değmez", "oyuncular güzel oynamışlar",
"senaryo berbattı, böyle senaryo olur mu?", "filme gidin de bir de siz görün"]
cv = CountVectorizer(dtype='uint8', stop_words=['de', 'bir', 've', 'mu'])
cv.fit(texts)
print(cv.vocabulary_)
dataset_x = cv.transform(texts).todense()
print(dataset_x)
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda da belirttiğimiz gibi 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 frekanslı mı vektörizsyon yapmalıyız? Aslında frekanslı vektörizasyon yapmak toplamda daha iyidir.
Ancak binary bilgilerin tutulma biçiminden özel olarak bir kazanç sağlanmaya çalışılabilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda IMDB veri kümesinde daha önce manuel olarak yaptığımız vektörizasyon işlemini CountVectorizer sınıfını kullnarak
yapıyoruz.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(dtype='uint8', binary=True)
cv.fit(df['review'])
dataset_x = cv.transform(df['review']).todense()
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
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='IMDB')
model.add(Input((training_dataset_x.shape[1],)))
model.add(Dense(128, activation='relu', 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=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
# prediction
predict_df = pd.read_csv('predict-imdb.csv')
predict_dataset_x = cv.transform(predict_df['review']).todense()
predict_result = model.predict(predict_dataset_x)
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
# dataset_x'teki birinci yorumun yazı haline getirilmesi
import numpy as np
rev_vocab_dict = {index: word for word, index in cv.vocabulary_.items()}
word_indices = np.argwhere(dataset_x[0] == 1)[:, 1]
words = [rev_vocab_dict[index] for index in word_indices]
text = ' '.join(words)
print(text)
#----------------------------------------------------------------------------------------------------------------------------
Keras içerisinde tensorflow.keras.datasets modülünde IMDB veri kümesi de hazır biçimde bulunmaktadır. Diğer hazır veri
kümelerinde olduğu gibi bu IMDB veri kğmesi de modüldeki load_data fonksiyonu ile yüklenmektedir. Örneğin:
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 sözcük 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ınmaktadır.
Bize load_data fonksiyonu x verileri olarak vektörizasyon sonucundaki vektörleri vermemektedir. Sözcüklerin indekslerine ilişkin
vektörleri bir liste olarak vermektedir. (Bunun nedeni uygulamacının vektörizasyon yerine başka işlemler yapabilmesine olanak
sağlamaktır.) load_data fonksiyonun verdiği index listeleri için şöyle bir ayrıntı da vardır: Bu fonksiyon bize listelerdeki
sözcük indekslerini üç fazla vermektedir. Bu sözcük indekslerindeki 0, 1 ve 2 indeksleri özel anlam ifade etmektedir.
Dolayısıyla aslında örneğin bize verilen 1234 numaralı indeks 1231 numaralı indekstir. Bizim bu indekslerden 3 çıkartmamız
gerekmektedir.
imdb modülündeki get_word_index fonksiyonu bize sözcük haznesini bir sözlük olarak vermektedir. num_words ne olursa olsun bu
sözlük her zaman tüm kelime haznesini içermektedir. Başka bir deyişle buradaki get_word_index fonksiyonu bizim kodlarımızdaki
vocab_dict sözlüğünü vermektedir. Örneğin:
vocab_dict = imdb.get_word_index()
Bu durumda biz training_dataset_x ve test_dataset_x listelerini aşağıdaki gibi binary vector haline getiren bir fonksiyon
yazabiliriz:
def vectorize(sequence, colsize):
dataset_x = np.zeros((len(sequence), colsize), dtype='uint8')
for index, vals in enumerate(sequence):
dataset_x[index, vals] = 1
return dataset_x
Burada vectorize fonksiyonu indekslerin bulunduğu liste listesini ve oluşturulacak matrisin sütun uzunluğunu parametre
olarak almıştır. Fonksiyon vektörize edilmiş NumPy dizisi ile geri dönmektedir. Ancak biz bu fonksiyonu kullanırken
colsize parametresine get_word_index ile verilen sözlüğün eleman sayısından 3 fazla olan değeri geçirmeliyiz. Çünkü
bu indeks listelerinde 0, 1 ve 2 değerleri özel bazı amaçlarla kullanılmıştır. Dolayısıyla buradaki sözcük indeksleri hep
3 fazladır. Yapay sinir ağımızda bu indekslerin 3 fazla olmasının bir önemi yoktur. Ancak ters dönüşüm uygulanacaksa tüm
indeks değerleriden 3 çıkartılmalıdır. O halde vektörizasonu şöyle yapabiliriz:
training_dataset_x = vectorize(training_dataset_x, len(vocab_dict) + 3)
test_dataset_x = vectorize(test_dataset_x, len(vocab_dict) + 3)
Artık her şey tamadır. Yukarıda yaptığımız işlemleri yapabiliriz.
Kestirim işleminde de aynı duruma dikkat edilmesi gerekir. Biz eğitimi sözcük indekslerinin 3 fazla olduğu duruma göre
yaptık. O halde kestirim işleminde de aynı şeyi yapmamız gerekir. Yani kestirim yapılacak yazıyı get_word_index sözlüğüne
sokup onun numarasını elde ettikten sonra ona 3 toplamalıyız. Bu biçimde liste listesi oluşturursak bunu yine yukarıda
yazmış olduğumuz vectorize fonksiyonuna sokabiliriz.
predict_df = pd.read_csv('predict-imdb.csv')
predict_list = []
for text in predict_df['review']:
index_list = []
words = re.findall('[A-Za-z0-9]+', text.lower())
for word in words:
index_list.append(vocab_dict[word] + 3)
predict_list.append(index_list)
predict_dataset_x = vectorize(predict_list, len(vocab_dict) + 3)
predict_result = model.predict(predict_dataset_x)
Aşağıda örneğin tüm kodları verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.datasets import imdb
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = imdb.load_data()
vocab_dict = imdb.get_word_index()
import numpy as np
def vectorize(sequence, colsize):
dataset_x = np.zeros((len(sequence), colsize), dtype='uint8')
for index, vals in enumerate(sequence):
dataset_x[index, vals] = 1
return dataset_x
training_dataset_x = vectorize(training_dataset_x, len(vocab_dict) + 3)
test_dataset_x = vectorize(test_dataset_x, len(vocab_dict) + 3)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense
model = Sequential(name='IMDB')
model.add(Input((training_dataset_x.shape[1],)))
model.add(Dense(128, activation='relu', 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=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
import pandas as pd
import re
predict_df = pd.read_csv('predict-imdb.csv')
predict_list = []
for text in predict_df['review']:
index_list = []
words = re.findall('[A-Za-z0-9]+', text.lower())
for word in words:
index_list.append(vocab_dict[word] + 3)
predict_list.append(index_list)
predict_dataset_x = vectorize(predict_list, len(vocab_dict) + 3)
predict_result = model.predict(predict_dataset_x)
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
# dataset_x'teki birinci yorumun yazı haline getirilmesi
rev_vocab_dict = {index: word for word, index in vocab_dict.items()}
word_indices = np.argwhere(training_dataset_x[0] == 1).flatten()
words = [rev_vocab_dict[index - 3] for index in word_indices if index > 2]
text = ' '.join(words)
print(text)
#----------------------------------------------------------------------------------------------------------------------------
42. Ders - 02/06/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
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" problemleri için örnek amacıyla kullanılmaktadır. Haberler toplam
46 farklı konuya ilişkindir. Veri kümesinin orijinali "çok etiketli (multilabel)" biçimdedir. Yani veri kümesindeki bazı
yazılara birden fazla etiket iliştirilmiştir. Ancak biz burada bir yazıya birden fazla etiket iliştirilmişse onun yalnızca
ilk etiketini alacağız. Böylece veri kümesini "çok etiketli (multilabel)" olmaktan çıkartıp "çok sınıflı (multiclass)"
biçimde kullanacağız
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 aşağıdaki bağlantıda Reuters veri kümesindeki her yazı bir dosya biçiminde
kaydedilmiş biçimde sunulmaktadı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
stopwords
Ancak veri kümesini açtığınızda iç içe bazı dizinlerin olduğunu göreceksiniz. Bu dizinlerden yalznıca bri tanesini alıp
diğerlerini atabilirsiniz.
Buradaki "cats.txt" dosyası tüm yazıların kategorilerinin belirtildiği bir dosyadır. training dizininde ve test dizininide
her bir yazı bi rtext dosya biçiminde oluşturulmuştur. Buradaki text dosyaları okumak için "latin-1" encoding'ini
kullanmalısınız. Biz yukarıdaki dizin yapısını çalışma dizininde "ReutersData" isimli bir dizine çektik. Yani veri kümesinin
dizin yapısı şu hale getirilmiştir:
ReutersData
training <DIR>
test <DIR>
cats.txt
stopwords
Burada veri kümesi "eğitim" ve "test" biçiminde zaten ikiye ayrılmış durumdadır. Dolayısıyla bizim veri kümesini ayrıca "eğitim"
ve "test" biçiminde ayırmamıza gerek yoktur. Buradaki "cats.txt" dosyasının içeriği aşağıdaki gibidir:
test/14826 trade
test/14828 grain
test/14829 nat-gas crude
test/14832 rubber tin sugar corn rice grain trade
test/14833 palm-oil veg-oil
test/14839 ship
test/14840 rubber coffee lumber palm-oil veg-oil
...
raining/5793 nat-gas
training/5796 crude
training/5797 money-supply
training/5798 money-supply
training/5800 grain
training/5803 gnp
training/5804 gnp
training/5805 gnp
training/5807 gnp
training/5808 acq
training/5810 trade
training/5811 money-fx
training/5812 carcass livestock
...
Reuters veri kümesinde ayrıca "stopwords" isimli bir dosya içersinde stop word'lerin listesi de verilmiştir. Bu sözcüklerin
sözcük haznesinden çıkartılması (yani stop word'lerin atılması) daha iyi bir sonucun elde edilmesine yol açabilecektir.
Biz burada vektörizasyon işlemini önce manuel bir biçimde yapıp sonra CountVectorizer sınıfını kullanacağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Önce "cats.txt" dosyasınııp buradaki bilgilerden training_dict ve test_dict isimli iki sözlük nesnesi oluşturalım. Bu
sözlük nesnelerinin anahtarları dosya isimleri değerleri ise o dosyadaki yazının sınıfını belirtiyor olsun:
training_dict = {}
test_dict = {}
cats = set()
with open('ReutersData/cats.txt') as f:
for line in f:
toklist = line.split()
ttype, fname = toklist[0].split('/')
if ttype == 'training':
training_dict[fname] = toklist[1]
else:
if ttype == 'test':
test_dict[fname] = toklist[1]
cats.add(toklist[1])
vocab = set()
training_texts = []
training_y = []
for fname in os.listdir('ReutersData/training'):
with open('ReutersData/training/' + fname, encoding='latin-1') as f:
text = f.read()
training_texts.append(text)
words = re.findall('[a-zA-Z0-9]+', text.lower())
vocab.update(words)
training_y.append(training_dict[fname])
test_texts = []
test_y = []
for fname in os.listdir('ReutersData/test'):
with open('ReutersData/test/' + fname, encoding='latin-1') as f:
text = f.read()
test_texts.append(text)
words = re.findall('[a-zA-Z0-9]+', text.lower())
vocab.update(words)
test_y.append(test_dict[fname])
Burada tüm sözcük haznesinin vocab isimli bir kümede tüm kategorilerin de cats isimli bir kümede toplandığına dikkat ediniz.
Hazır dosyaları açmışken dosyalar içerisindeki yazıları da training_texts ve test_texts isimli listelerde topladık. Ayrıca
her yazının kategorilerini de training_y ve test_y listelerinde topladığımıza dikkat ediniz. Artık sözcüklere numaralar
verebiliriz:
vocab_dict = {word: index for index, word in enumerate(vocab)}
Şimdi manuel olarak binary vektörizasyon uygulayalım:
training_dataset_x = np.zeros((len(training_texts), len(vocab)), dtype='uint8')
test_dataset_x = np.zeros((len(test_texts), len(vocab)), dtype='uint8')
for row, text in enumerate(training_texts):
words = re.findall('[a-zA-Z0-9]+', text.lower())
word_numbers = [vocab_dict[word] for word in words]
training_dataset_x[row, word_numbers] = 1
for row, text in enumerate(test_texts):
words = re.findall('[a-zA-Z0-9]+', text.lower())
word_numbers = [vocab_dict[word] for word in words]
test_dataset_x[row, word_numbers] = 1
Problem çok sınıflı bir sınıflandırma problemidir. Bunun için y değerlerinideğerleri üzerinde one-hot encoding dönüştürmesi
uygulayabiliriz:
ohe = OneHotEncoder(sparse_output=False, dtype='uint8')
ohe.fit(np.array(list(cats)).reshape(-1, 1))
training_dataset_y = ohe.transform(np.array(training_y).reshape(-1, 1))
test_dataset_y = ohe.transform(np.array(test_y).reshape(-1, 1))
Artık modelimizi kurup eğitebiliriz:
model.add(Input((training_dataset_x.shape[1],)))
model.add(Dense(128, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(len(cats), 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=10, validation_split=0.2)
Kestirim işlemi için eğitimdeki veri kğmesine benzer bir veri kümesi oluşturulabilir. Biz örneğimizde kestirim için
"PredictData" isimli bir dizin oluşturup o dizine yazılardan oluşan dosyalar yerleştirdik. O dosyaların da olması gereken
tiketlerini dosya ismine ek yaptık. PredictData dizinindeki dosya isimleri şöyledir:
14829-nat-gas
14841-wheat
14849-interest
14854-ipi
14860-earn
14862-bop
14876-earn
21394-acq
Kestirim kodu şöyle oluşturulabilir:
word_numbers_list = []
fnames = []
for fname in os.listdir('PredictData'):
with open('PredictData/' + fname, encoding='latin-1') as f:
text = f.read()
words = re.findall('[a-zA-Z0-9]+', text.lower())
word_numbers = [vocab_dict[word] for word in words]
word_numbers_list.append(word_numbers)
fnames.append(fname)
predict_dataset_x = np.zeros((len(word_numbers_list), len(vocab)), dtype='uint8')
for row, word_numbers in enumerate(word_numbers_list):
predict_dataset_x[row, word_numbers] = 1
predict_result = model.predict(predict_dataset_x)
predict_indexes = np.argmax(predict_result, axis=1)
for index, pi in enumerate(predict_indexes):
print(f'{fnames[index]} => {ohe.categories_[0][pi]}')
Aşağıda örneğin tüm kodu verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import os
import re
training_dict = {}
test_dict = {}
cats = set()
with open('ReutersData/cats.txt') as f:
for line in f:
toklist = line.split()
ttype, fname = toklist[0].split('/')
if ttype == 'training':
training_dict[fname] = toklist[1]
else:
if ttype == 'test':
test_dict[fname] = toklist[1]
cats.add(toklist[1])
vocab = set()
training_texts = []
training_y = []
for fname in os.listdir('ReutersData/training'):
with open('ReutersData/training/' + fname, encoding='latin-1') as f:
text = f.read()
training_texts.append(text)
words = re.findall('[a-zA-Z0-9]+', text.lower())
vocab.update(words)
training_y.append(training_dict[fname])
test_texts = []
test_y = []
for fname in os.listdir('ReutersData/test'):
with open('ReutersData/test/' + fname, encoding='latin-1') as f:
text = f.read()
test_texts.append(text)
words = re.findall('[a-zA-Z0-9]+', text.lower())
vocab.update(words)
test_y.append(test_dict[fname])
vocab_dict = {word: index for index, word in enumerate(vocab)}
import numpy as np
training_dataset_x = np.zeros((len(training_texts), len(vocab)), dtype='uint8')
test_dataset_x = np.zeros((len(test_texts), len(vocab)), dtype='uint8')
for row, text in enumerate(training_texts):
words = re.findall('[a-zA-Z0-9]+', text.lower())
word_numbers = [vocab_dict[word] for word in words]
training_dataset_x[row, word_numbers] = 1
for row, text in enumerate(test_texts):
words = re.findall('[a-zA-Z0-9]+', text.lower())
word_numbers = [vocab_dict[word] for word in words]
test_dataset_x[row, word_numbers] = 1
import numpy as np
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse_output=False, dtype='uint8')
ohe.fit(np.array(list(cats)).reshape(-1, 1))
training_dataset_y = ohe.transform(np.array(training_y).reshape(-1, 1))
test_dataset_y = ohe.transform(np.array(test_y).reshape(-1, 1))
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense
model = Sequential(name='Reuters')
model.add(Input((training_dataset_x.shape[1],)))
model.add(Dense(128, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(len(cats), 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=10, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=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=(14, 6))
plt.title('Categorcal Accuracy - Validation Categorical Accuracy', fontsize=14, pad=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 , test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
# prediction
word_numbers_list = []
fnames = []
for fname in os.listdir('PredictData'):
with open('PredictData/' + fname, encoding='latin-1') as f:
text = f.read()
words = re.findall('[a-zA-Z0-9]+', text.lower())
word_numbers = [vocab_dict[word] for word in words]
word_numbers_list.append(word_numbers)
fnames.append(fname)
predict_dataset_x = np.zeros((len(word_numbers_list), len(vocab)), dtype='uint8')
for row, word_numbers in enumerate(word_numbers_list):
predict_dataset_x[row, word_numbers] = 1
predict_result = model.predict(predict_dataset_x)
predict_indexes = np.argmax(predict_result, axis=1)
for index, pi in enumerate(predict_indexes):
print(f'{fnames[index]} => {ohe.categories_[0][pi]}')
#----------------------------------------------------------------------------------------------------------------------------
43. Ders - 08/06/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de Reuters örneğini CountVectorizer sınıfını kullanılarak gerçekleştirelim. Anımsanacağı gibi CountVectorizer sınıfı
zaten vektörizasyon işlemini kendisi yapmaktaydı. O halde biz Reuters yazılarını bir listede topladıktan sonra CountVectorizer
sınıfı ile fit işlemini yapabiliriz. Orijinal Reuters veri kümesinde ayrıca "stopwords" dosyası içerisinde "stop word'ler"
satır satır sözcükler biçiminde verilmiştir. (Anımsanacağı gibi CountVectorizer sınıfında biz stop word'leri de ayrıca
belirtebiliyorduk.) Reuters veri kümesinde verilen stop word'leri bir Python listesi biçiminde şöyle elde edebiliriz:
import pandas as pd
df_sw = pd.read_csv('ReutersData/stopwords', header=None)
sw = df_sw.iloc[:, 0].to_list()
CountVectorizer sınıfı önce yazıları sözcüklere ayırıp (tokenizing) sonra stop word'leri atmaktadır. Ancak sınıfın sözcüklere
ayırmada default kullandığı düzenli ifade kalıbı tek tırnaklı sözcüklerdeki tırnaklardan da ayrıştırma yapmaktadır. (Fakat
tırnaktan sonraki kısmı da atmaktadır.) Orijinal veri kümesinde verilen stop word'ler tek tırnaklı yazı içerdiği için burada
bir uyumsuzluk durumu ortaya çıkmaktadır. (Yani stop'word'ler içerisindeki tek tırnak içeren sözcükler zaten sınıfın ayırdığı
sözcükler içerisinde bulunmayacaktır.) İşte CountVectorizer sınıfının fit metodu bu durumu fark edip bize bir uyarı mesajı
biçiminde bunu aşağıdaki gibi iletmektedir:
"C:\Users\aslan\anaconda3\lib\site-packages\sklearn\feature_extraction\text.py:409: UserWarning: Your stop_words may be
inconsistent with your preprocessing. Tokenizing the stop words generated tokens ['ain', 'aren', 'couldn', 'didn', 'doesn',
'don', 'hadn', 'hasn', 'haven', 'isn', 'll', 'mon', 'shouldn', 've', 'wasn', 'weren', 'won', 'wouldn'] not in stop_words."
O halde biz ya sınıfın kullandığı sözcüklere ayırma düzenli ifadesini (token_pattern parametresini kastediyoruz) tırnakları
kapsayacak biçimde değiştirmeliyiz ya da bu tırnaklı stop word'lerdeki tırnakları silmeliyiz. Dosyanın orijinalini bozmamak
için uyarıda sözü edilen sözcükleri de listeye ekleyerek problemi pratik bir biçimde çözebiliriz:
sw += ['ain', 'aren', 'couldn', 'didn', 'doesn', 'don', 'hadn', 'hasn', 'haven', 'isn', 'll', 'mon',
'shouldn', 've', 'wasn', 'weren', 'won', 'wouldn']
Ayrıca CountVectorizer sınıfının stop_words parametresine 'english' girilirse scikit-learn içerisindeki İngilizce için
oluşturulmuş default stop word listesi kullanılmaktadır. Tabii veri kümesindeki orijinal listenin kullanılması daha uygun
olacaktır. Yukarıda da belirttiğimiz gibi biz CountVectorizer sınıfının token_pattern parametresine tırnakları da alacak
biçimde bir düzenli ifade kaalıbını da girebiliriz. Örneğin:
cv = CountVectorizer(token_pattern="[a-zA-Z-0-9']+")
Burada artık CountVectorizer bizim belirlediğimiz düzenli ifadeyi kullanarak sözcükleri ayrıştırcaktır. Bu düzenli ifade
"sözcüklerin içerisinde tek tırnakların da olabileceğini" belirtmektedir.
Şimdi vektörizasyon işlemini sınıfın kendi default token_pattern kalıbını kullanarak yapalım:
df_sw = pd.read_csv('ReutersData/stopwords', header=None)
sw = df_sw.iloc[:, 0].to_list()
sw += ['ain', 'aren', 'couldn', 'didn', 'doesn', 'don', 'hadn', 'hasn', 'haven', 'isn', 'll', 'mon',
'shouldn', 've', 'wasn', 'weren', 'won', 'wouldn']
cv = CountVectorizer(dtype='uint8', stop_words=sw, encoding='latin-1')
cv.fit(training_texts + test_texts)
training_dataset_x = cv.transform(training_texts).todense()
test_dataset_x = cv.transform(test_texts).todense()
Kesitirim işlemi için yine daha önce yaratmış olduğumuz CountVectorizer nesnesini kullanabiliriz:
predict_texts = []
fnames = []
for fname in os.listdir('PredictData'):
with open('PredictData/' + fname, encoding='latin-1') as f:
text = f.read()
predict_texts.append(text)
fnames.append(fname)
predict_dataset_x = cv.transform(predict_texts).todense()
CountVectorizer sınıfında binary parametresinin default olarak False biçimde olduğunu anımsayınız. Yani default durumda
vektörize edilmiş matris sözcüklerin sıklıkları tutacaktır.
Örneğin bütünsel hali aşağıda verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import os
import re
training_dict = {}
test_dict = {}
cats = set()
with open('ReutersData/cats.txt') as f:
for line in f:
toklist = line.split()
ttype, fname = toklist[0].split('/')
if ttype == 'training':
training_dict[fname] = toklist[1]
else:
if ttype == 'test':
test_dict[fname] = toklist[1]
cats.add(toklist[1])
vocab = set()
training_texts = []
training_y = []
for fname in os.listdir('ReutersData/training'):
with open('ReutersData/training/' + fname, encoding='latin-1') as f:
text = f.read()
training_texts.append(text)
words = re.findall('[a-zA-Z0-9]+', text.lower())
vocab.update(words)
training_y.append(training_dict[fname])
test_texts = []
test_y = []
for fname in os.listdir('ReutersData/test'):
with open('ReutersData/test/' + fname, encoding='latin-1') as f:
text = f.read()
test_texts.append(text)
words = re.findall('[a-zA-Z0-9]+', text.lower())
vocab.update(words)
test_y.append(test_dict[fname])
vocab_dict = {word: index for index, word in enumerate(vocab)}
import pandas as pd
df_sw = pd.read_csv('ReutersData/stopwords', header=None)
sw = df_sw.iloc[:, 0].to_list()
sw += ['ain', 'aren', 'couldn', 'didn', 'doesn', 'don', 'hadn', 'hasn', 'haven', 'isn',
'll', 'mon', 'shouldn', 've', 'wasn', 'weren', 'won', 'wouldn']
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(dtype='uint8', stop_words=sw, encoding='latin-1')
cv.fit(training_texts + test_texts)
training_dataset_x = cv.transform(training_texts).todense()
test_dataset_x = cv.transform(test_texts).todense()
import numpy as np
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse_output=False, dtype='uint8')
ohe.fit(np.array(list(cats)).reshape(-1, 1))
training_dataset_y = ohe.transform(np.array(training_y).reshape(-1, 1))
test_dataset_y = ohe.transform(np.array(test_y).reshape(-1, 1))
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense
model = Sequential(name='Reuters')
model.add(Input((training_dataset_x.shape[1],)))
model.add(Dense(128, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(len(cats), 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=10, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=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=(14, 6))
plt.title('Categorcal Accuracy - Validation Categorical Accuracy', fontsize=14, pad=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 , test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
# prediction
predict_texts = []
fnames = []
for fname in os.listdir('PredictData'):
with open('PredictData/' + fname, encoding='latin-1') as f:
text = f.read()
predict_texts.append(text)
fnames.append(fname)
predict_dataset_x = cv.transform(predict_texts).todense()
predict_result = model.predict(predict_dataset_x)
predict_indexes = np.argmax(predict_result, axis=1)
for index, pi in enumerate(predict_indexes):
print(f'{fnames[index]} => {ohe.categories_[0][pi]}')
#----------------------------------------------------------------------------------------------------------------------------
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 diğer veri kümelerinde olduğu gibi load_data fonksiyonuyla yapılabilir. Fonksiyonun num_words
parametresi yine kelime haznesini belli bir sayıda tutmak için kullanılabilir. Eğer bu parametre için argüman girilmezse
bütün Reuters verileri kullanılacaktır. Bu modüldeki Reuters verilerinde toplam 46 farklı kategori vardır. Ancak Keras
dokümanları bu kategorileri herhangi bir biçimde kullanıcıya vermemiştir. Fakat yapılan incelemeler sonucunda kategorilerin
sırasıyla şunlar olduğu tespit edilmiştir:
cats = ['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']
Ancak Tensorflow kütüphanesinin son versiyonlarında (öreğin 2.16.1) artık Reuters veri kümesindeki kategorilerin isimleri de
get_label_names fonksiyonu ile verilmektedir.
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 bir sözlük
nesnesi biçiminde vermektedir. load_data fonksiyonu da yine bize sözcük numaralarından oluşan listeler verir. Yine imdb
modülünde olduğu gibi x verilerindeki sayılar yine 3 fazla olarak kodlanmıştır.
Biz bu hazır Reuters verilerinde CountVectorizer sınıfını kullanamayız. Çünkü CountVectorizer sınıfı yazı dizilerinden
vektörizasyon yapmaktadır. Halbuki burada bizim elimizde yazılar değil yazılara karşı gelen sayılar vardır. Tabii biz
sayıları yazılara dönüştürüp CountVectorizer sınıfını kullanabiliriz. Ancak bu işlem yavaş olur. Burada doğrudan sayıalrdan
vektörizasyon matrisini elde etmek daha uygun olacaktır. Bu işlemi yapan fonksiyonu şöyle yazabiliriz:
def vectorize(sequence, colsize):
dataset_x = np.zeros((len(sequence), colsize), dtype='uint8')
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 örneğin tüm kodları verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.datasets import reuters
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = reuters.load_data()
vocab_dict = reuters.get_word_index()
import numpy as np
def vectorize(sequence, colsize):
dataset_x = np.zeros((len(sequence), colsize), dtype='uint8')
for index, vals in enumerate(sequence):
dataset_x[index, vals] = 1
return dataset_x
training_dataset_x = vectorize(training_dataset_x, len(vocab_dict) + 3)
test_dataset_x = vectorize(test_dataset_x, len(vocab_dict) + 3)
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse_output=False, dtype='uint8')
ohe.fit(np.concatenate([training_dataset_y, test_dataset_y]).reshape(-1, 1))
training_dataset_y = ohe.transform(training_dataset_y.reshape(-1, 1))
test_dataset_y = ohe.transform(test_dataset_y.reshape(-1, 1))
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense
model = Sequential(name='Reuters')
model.add(Input((training_dataset_x.shape[1],)))
model.add(Dense(128, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(len(ohe.categories_[0]), 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=10, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=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=(14, 6))
plt.title('Categorcal Accuracy - Validation Categorical Accuracy', fontsize=14, pad=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, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
# prediction
cats = ['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']
import os
import re
word_numbers_list = []
fnames = []
for fname in os.listdir('PredictData'):
with open('PredictData/' + fname, encoding='latin-1') as f:
text = f.read()
words = re.findall('[a-zA-Z0-9]+', text.lower())
word_numbers = [vocab_dict[word] + 3 for word in words]
word_numbers_list.append(word_numbers)
fnames.append(fname)
predict_dataset_x = vectorize(word_numbers_list, len(vocab_dict) + 3)
predict_result = model.predict(predict_dataset_x)
predict_indexes = np.argmax(predict_result, axis=1)
for index, pi in enumerate(predict_indexes):
print(f'{fnames[index]} => {cats[pi]}')
# reverse transformation test
rev_vocab_dict = {index: word for word, index in vocab_dict.items()}
convert_text = lambda text_numbers: ' '.join([rev_vocab_dict[tn - 3] for tn in text_numbers if tn > 2])
print(convert_text(word_numbers_list[0]))
#----------------------------------------------------------------------------------------------------------------------------
Aslında vektörizasyon işlemi daha sonraları Keras'a eklenmiş olan TextVectorization isimli katman sınıfı yoluyla da
yapılabilmektedir. Uygulamacı Input katmanından sonra bu katman nesnesini, daha sonra da diğer katman nesnelerini
modele ekler. Böylece alınan girdiler önce TextVectorization katmanı yoluyla vektörel hale getirilip diğer katmanlara
iletilir. Tabii bu durumda bizim ağa girdi olarak vektörleri değil yazıları vermemiz gerekir. Çünkü bu katmanın kendisi
zaten yazıları vektörel hale getirmektedir. Örneğin:
tv = TextVectorization(...)
...
model = Sequential(name='IMDB')
model.add(Input((1, ), dtype='string'))
model.add(tv)
model.add(Dense(128, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
Burada ağın girdi katmanında tek sütunlu bir veri kümesi olduğuna dikkat ediniz. Çünkü biz ağa artık yazıları girdi olarak
vereceğiz. Bu yazılar TextVectorization katmanına sokulacak ve bu katmandan sözcük haznesi kadar sütundan oluşan matris
çıktısı elde edilecektir. Bu çıktıların da sonraki Dense katmana verildiğini görüyorsunuz. Yani TextVectorization katmanı
aldığı bir batch yazıyı o anda vektörel haline getirip sonraki katmana iletmektedir. Ancak Input katmanında girdilerin
yazısal olduğunu belirtmek için Input fonksiyonunun dtype paramatresi 'string' biçiminde girilmelidir. (Defult durumda Keras
girdi katmanındaki değerlerin float türünden olduğunu kabul etmektedir.)
TextVectorization sınıfı diğer katman nesnelerinde olduğu gibi tensorflow.keras.layers modülü içerisinde bulunmaktadır.
Sınıfın __init__ metodunun parametrik yapısı şöyledir:
tf.keras.layers.TextVectorization(
max_tokens=None,
standardize='lower_and_strip_punctuation',
split='whitespace',
ngrams=None,
output_mode='int',
output_sequence_length=None,
pad_to_max_tokens=False,
vocabulary=None,
idf_weights=None,
sparse=False,
ragged=False,
encoding='utf-8',
name=None,
**kwargs
)
Görüldüğü gibi bu parametrelerin hepsi default değer almıştır. max_tokens parametresi en fazla yinelenen belli sayıda
sözüğün vektörel hale getirilmesi için kullanılmaktadır. Yani adeta sözcük haznesi burada belirtilen miktarda sözcük
içeriyor gibi olmaktadır. standardize parametresi yazılardaki sözcüklerin elde edildikten sonra nasıl önişleme sokulacağını
belirtmektedir. Bu parametrenin default değerinin 'lower_and_strip_punctuation' biçiminde olduğuna dikkat ediniz. Bu durumda
yazılardaki sözcükler küçük harflere dönüştürülecek ve sözcüklerin içerisindeki noktalama işaretleri atılacaktır. (Yani
örneğin yazıdaki "Dikkat!" sözcüğü "dikkat" olarak ele alınacaktır.) Bu parametre için "çağrılabilir (callable)" bir nesne
de girilebilmektedir. Girilen fonksiyon eğitim sırasında çağrılıp buradan elde edilen yazılar vektörizasyon işlemine sokulmaktadır.
split parametresi sözcüklerin nasıl birbirinden ayrılacağını belirtmektedir. Default durumda sçzcükler boşluk karakterleriyle
birbirinden ayrılmaktadır. output_mode parametresinin default değeri int biçimindedir. Default durumda yazıdaki sözcükler
sözcük haznesindeki numaralar biçiminde verilecektir. Bu parametrenin "count" biçiminde girilmesi uygundur. Eğer bu parametre
"count" biçiminde girilirse bu durumda yazı bizim istediğimiz gibi frekanslardan oluşan vektörler biçimine dönüştürülecektir.
vocabulary parametresi doğrudan sözcük haznesinin programcı tarafından metoda verilmesini sağlamak için buludurulmuştur.
Bu durumda adapt işleminde sözcük haznesi adapt tarafından oluşturulmaz, burada verilen sözcük haznesi kullamılır.
TextVectorization sınıfının get_vocabulary metodu adapt işleminin sonucunda oluşturulmuş olan sözcük haznesini bize
vermektedir. set_vocabulary metodu ise sözcük haznesini set etmek için kullanılmaktadır.
TextVectorization nesnesi yaratıldıktan sonra sözcük haznesinin ve dönüştürmede kullanılacak sözcük nesnesinin oluşturulması
için sınıfın adapt metodu çağrılmalıdır. Örneğin:
tv = TextVectorization(output_mode='count')
tv.adapt(texts)
Aşağıda sınıfın kullanımına ilişkin basit bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.layers import TextVectorization
texts = ['film çok güzeldi', 'film güzeldi', 'film çok kötüydü', 'film ortalama bir filmdi']
tv = TextVectorization(output_mode='count')
tv.adapt(texts)
result = tv(['film güzeldi, film', 'film kötüydü'])
print(result)
result = tv.get_vocabulary()
print(result)
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de TextVectorization katmanını IMDB veri kümesinde kullanalım. Burada yapmamız gereken şey Input katmanından sonra
bu TextVectoriation katmanını modele eklemektir. Örneğin:
tv = TextVectorization(output_mode='count')
tv.adapt(dataset_x)
model = Sequential(name='IMDB')
model.add(Input((1, ), dtype='string'))
model.add(tv)
model.add(Dense(128, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
Tabii artık kestirim işlemi için manuel vektörizasyon uygulamaya gerek yoktur. Doğrudan kestirim işleminde kullanılacak
yazılar predict metoduna verilebilir. Örneğin:
predict_df = pd.read_csv('predict-imdb.csv')
predict_result = model.predict(predict_df)
Aşağıdaki örnekte IMDB veri kümesine TextVectorization katmanı uygulanmıştır.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
dataset_x = df['review']
dataset_y = (df['sentiment'] == 'positive').astype(dtype='uint8')
from sklearn.model_selection import train_test_split
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(df['review'], dataset_y, test_size=0.2)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense, TextVectorization
tv = TextVectorization(output_mode='count')
tv.adapt(dataset_x)
model = Sequential(name='IMDB')
model.add(Input((1, ), dtype='string'))
model.add(tv)
model.add(Dense(128, activation='relu', 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=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
# prediction
predict_df = pd.read_csv('predict-imdb.csv')
predict_result = model.predict(predict_df)
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
44. Ders - 09/06/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Çok büyük verilerle eğitim, test hatta predict işlemi sorunlu bir konudur. Çünkü örneğin fit işleminde büyük miktarda
veri kümeleriyle eğitim ve test yapılırken bu veri kümeleri bir bütün olarak metotlara verilmektedir. Ancak büyük veri
kümeleri eldeki belleğe sığmayabilir. (Her ne kadar 64 bit Windows ve Linux sistemlerinde prosesin sanal bellek alanı
çok büyükse de bu teorik sanal bellek alanını kullanabilmek için swap alanlarının büyütülmesi gerekmektedir.) Örneğin
IMDB ya da Reuters örneklerinde vektörizasyon işlemi sonucunda çok büyük matrisler oluşmaktadır. Gerçi bu matrislerin
çok büyük kısmı 0'lardan oluştuğu için "seyrek (sparse)" durumdadır. Ancak Keras kütüphanesinin eski sürümlerinde
fit, evaluate ve predict metotları seyrek matrislerle çalışmamaktadır. İşte bu nedenden dolayı Keras'ta modeller parçalı
verilerle eğitilip test ve predict edilebilmektedir. Parçalı eğitim, test ve predict işlemlerinde eğitim, test ve predict
sırasında her batch işleminde fit, evaluate ve predict 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, evaluate ve predict metotlarının birinci parametrelerinde x verileri yerine bir
2024-07-15 13:23:34 +03:00
"üretici fonksiyon (generator)" ya da "PyDataset sınıfından türetilmiş olan bir sınıf nesnesi" girilir. Biz burada önce
üretici fonksiyon yoluyla sonra da PyDataset sınıfından türetilmiş olan sınıf yoluyla parçalı işlemlerin nasıl yapılacağını
göreceğiz. Eskiden Keras'ta normal fit, evaluate ve predict metotlarının ayrı fit_generator, evalute_generator ve
predict_generator biçiminde parçalı eğitim için kullanılan biçimleri vardı. Ancak bu metotlar daha sonra kaldırıldı. Artık
fit, evaluate ve predcit metotları hem bütünsel hem de parçalı işlemler yapabilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Ü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 fit metodunun validation_split parametresinin de bir
anlamı yoktur. Çünkü sınama işlemi de yine parçalı verilerle yapılmaktadır. Dolayısıyla sınama verilerinin parçalı olarak
verilmesi de yine uygulamacının sorumluluğundadır. Ancak bu yöntemde programcının iki parametreyi yine 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 ise epochs parametresidir. Bu da yine toplam epoch sayısını belirtir. (epoch parametresinin default değerinin
1 olduğunu anımsayınız.) 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 next işlemi yaparak üretici fonksiyonun bitmesine yol açmaktadır.
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. Biz bu örneği
rastgele verilerle yalnızca mekanizmayııklamak için veriyoruz. (Rastgele verilerde rastgele sayı üreticisi uygun bir
biçimde oluşturulmuşsa bir kalıp söz konusu olmayacağı için "binary_accuracy" metrik değerinin 0.50 civarında olması
beklenir.)
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense
EPOCHS = 100
NFEATURES = 10
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, NFEATURES))
y = np.random.randint(0, 2, BATCH_SIZE)
yield x, y
model = Sequential(name='Diabetes')
2024-07-15 13:23:34 +03:00
model.add(Input((NFEATURES, )))
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.fit(data_generator(), epochs=EPOCHS, steps_per_epoch=STEPS_PER_EPOCH)
#----------------------------------------------------------------------------------------------------------------------------
Yukarıdaki örnekte bir sınama işlemi yapılmamamıştır. İşte sınama işlemleri için veriler de tek hamlede değil parça parça
verilebilmektedir. Bunun için yine fit metodunun validation_data parametresine bir üretici fonksiyon nesnesi girilir. fit
metodu da her epcoh sonrasında validation_steps parametresinde belirtilen miktarda bu üretici fonksiyon üzerinde iterasyon
yaparak bizden sınama verilerini almaktadır. Böylece biz her epoch sonrasında kullanılacak sınama verilerini fit metoduna
2024-07-15 13:23:34 +03:00
üretici fonksiyon nesnesi yoluyla parça parça vermiş oluruz.
Aşağıda sınama verilerinin parçalı bir biçimde nasıl verildiğine ilişkin bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense
EPOCHS = 100
NFEATURES = 10
STEPS_PER_EPOCH = 20
BATCH_SIZE = 32
VALIDATION_STEPS = 10
def data_generator():
for _ in range(EPOCHS):
for _ in range(STEPS_PER_EPOCH):
x = np.random.random((BATCH_SIZE, NFEATURES))
y = np.random.randint(0, 2, BATCH_SIZE)
yield x, y
def validation_generator():
x = np.random.random((BATCH_SIZE, NFEATURES))
y = np.random.randint(0, 2, BATCH_SIZE)
for _ in range(EPOCHS):
2024-07-15 13:23:34 +03:00
for _ in range(VALIDATION_STEPS):
yield x, y
model = Sequential(name='Diabetes')
2024-07-15 13:23:34 +03:00
model.add(Input((NFEATURES,)))
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
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 nesnesi girilir. evaluate metodunun steps parametresi bizden test verilerinin kaç parça
olarak isteneceğini belirtmektedir. Yani programcının burada belirtilen sayıda yield işlemi yapması gerekir. Aşağıda buna
bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense
EPOCHS = 100
NFEATURES = 10
STEPS_PER_EPOCH = 20
BATCH_SIZE = 32
VALIDATION_STEPS = 10
EVALUATION_STEPS = 15
def data_generator():
for _ in range(EPOCHS):
for _ in range(STEPS_PER_EPOCH):
x = np.random.random((BATCH_SIZE, NFEATURES))
y = np.random.randint(0, 2, BATCH_SIZE)
yield x, y
def validation_generator():
x = np.random.random((BATCH_SIZE, NFEATURES))
y = np.random.randint(0, 2, BATCH_SIZE)
for _ in range(EPOCHS):
for _ in range(VALIDATION_STEPS + 1):
yield x, y
def evaluation_generator():
for _ in range(EVALUATION_STEPS):
x = np.random.random((BATCH_SIZE, NFEATURES))
y = np.random.randint(0, 2, BATCH_SIZE)
yield x, y
model = Sequential(name='Diabetes')
model.add(Input(NFEATURES, ))
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.fit(data_generator(), epochs=EPOCHS, steps_per_epoch=STEPS_PER_EPOCH, validation_data=validation_generator(), validation_steps=VALIDATION_STEPS)
eval_result = model.evaluate(evaluation_generator(), steps=EVALUATION_STEPS)
#----------------------------------------------------------------------------------------------------------------------------
Benzer biçimde predict metodunda da kesitirimi yapılacak veriler bizden parça parça istenebilmektedir. Bunun için yine
predict metodunun x parametresine bir üretici fonksiyon nesnesi girilir. predcit metonunun da kestirim verilerininin kaç
parça olarak isteneceğine yönelik steps parametresi vardır. Tabii sınama işlemi sırasında üretici fonksiyon artık yalnızca
x değerlerini vermelidir. Aşağıda buna ilişkin bir örnek verilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense
EPOCHS = 100
NFEATURES = 10
STEPS_PER_EPOCH = 20
BATCH_SIZE = 32
VALIDATION_STEPS = 10
EVALUATION_STEPS = 15
PREDICTION_STEPS = 5
def data_generator():
for _ in range(EPOCHS):
for _ in range(STEPS_PER_EPOCH):
x = np.random.random((BATCH_SIZE, NFEATURES))
y = np.random.randint(0, 2, BATCH_SIZE)
yield x, y
def validation_generator():
x = np.random.random((BATCH_SIZE, NFEATURES))
y = np.random.randint(0, 2, BATCH_SIZE)
for _ in range(EPOCHS):
for _ in range(VALIDATION_STEPS + 1):
yield x, y
def evaluation_generator():
for _ in range(EVALUATION_STEPS):
x = np.random.random((BATCH_SIZE, NFEATURES))
y = np.random.randint(0, 2, BATCH_SIZE)
yield x, y
def prediction_generator():
for _ in range(PREDICTION_STEPS):
x = np.random.random((BATCH_SIZE, NFEATURES))
yield x
model = Sequential(name='Diabetes')
model.add(Input(NFEATURES, ))
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.fit(data_generator(), epochs=EPOCHS, steps_per_epoch=STEPS_PER_EPOCH, validation_data=validation_generator(), validation_steps=VALIDATION_STEPS)
eval_result = model.evaluate(evaluation_generator(), steps=EVALUATION_STEPS)
predict_result = model.predict(prediction_generator(), steps=PREDICTION_STEPS)
#----------------------------------------------------------------------------------------------------------------------------
45. Ders - 29/06/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de gerçekten bellekte büyük bir yer kaplayan ve vektörizasyon işlemi gerektiren bir örneği parçalı bir biçimde eğitelim
ve test edelim. Bu örnek için IMDB veri kümesini kullanalım. Burada karşılaşacağımız önemli bir sorun "karıştırma (shuffling)"
işleminin nasıl yapılacağına ilişkindir. Bilindiği gibi fit işlemi sırasında her epoch işleminden sonra eğitim veri kümesi
karıştırılmaktadır. (Aslında epoch sonrasında veri kümesinin karıştırılıp karıştırımayacağı fit metodundaki shuffle parametresi
ile belirlenebilmektedir. Bu paramerenin default değeri True biçimindedir.) Parçalı eğitim yapılırken fit metodunun shuffle
parametresinin bir etkisi yoktur. Dolayısıyla veri kümesinin epoch sonrasında karıştırılması programcı tarafından üretici
fonksiyon içerisinde yapılmalıdır. Pekiyi programcı bu karıştırmayı nasıl yapabilir? CSV dosyasının dosya üzerinde karıştırılması
uygun bir yöntem değildir. Bu durumda birkaç yöntem izlenebilir:
1) Veri kümesi yine read_csv fonksiyonu ile tek hamlede belleğe okunabilir. Her batch işleminde bellekteki ilgili batch'lik
kısım verilebilir. Karıştırma da bellek üzerinde yapılabilir. Ancak veri kümesini belleğe okumak yapılmak istenen şeye
2024-07-15 13:23:34 +03:00
bir tezat oluşturmaktadır. Fakat yine de text işlemlerinde asıl bellekte yer kaplayan unsur vektörizasyon işleminden elde
edilen matris olduğu için bu yöntem kullanılabilir.
2) Veri kümesi bir kez okunup dosyadaki kaydın offset numaraları bir dizide saklanabilir. Sonra bu dizi karıştırılıp
dizinin elemanlarına ilişkin kayıtlar okunabilir. Eğer veritabanı üzerinde doğrudan çalışılıyorsa da işlemler benzer biçimde
yürütülebilir.
2024-07-15 13:23:34 +03:00
Pekiyi biz vektörizasyon işlemini TextVectorization sınıfı türünden katman nesnesiyle yapmaya çalışırken tüm verileri yine
belleğe çekmek zorunda değil miyiz? Aslında TextVectorization sınıfının adapt metodu henüz görmediğimiz Tensorflow kütüphanesindeki
Dataset nesnesini de parametre olarak alabilmektedir. Bu sayede biz bir Dataset nesnesi oluşturarak adapt metodunun da
verileri parçalı bir biçimde almasını sağlayabiliriz. Scikit-learn kütüphanesindeki CountVectorizer sınıfının fit metodu
da aslında dolaşılabilir nesneyi parametre olarak alabilmektedir. Dolayısıyla CountVectorizer kullanılırken yine üretici
fonksiyonlar yoluyla biz verileri fit metoduna parça parça verebilmekteyiz. Dosya nesneslerinin de dolaşılabilir nesneler
olduğunu anımsayınız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
46. Ders - 30/06/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Örne üzerinde çalışıldı.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
47. Ders - 06/07/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte IMDB veri kümesi tek hamlede değil parça parça okunarak eğitim yapılmıştır. Her epcoh'tan sonra karıştırma
yapılacağı için asıl dosyanın karıştırılması uygun olmadığından dolayı satırın offset'lerii bir listede toplanıp bu liste
karıştırılmıştır. Ancak orijinal IMDB veri kümesinde maalesef bazı yorumlar birden fazla satırdan oluşmaktadır. Bu yorumların
sayısı çok azdır. Bu nedenle biz orijinal IMDB veri kümesinden bu satırları atan ve bu bu veri kümesini dönüştüren bir program
2024-07-15 13:23:34 +03:00
yazdık. Program şöyldir:
# convert_imdb.py
2024-07-15 13:23:34 +03:00
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv', encoding='latin-1')
for index, text in enumerate(df['review']):
rtext = text.replace('\n', ' ')
rtext = rtext.replace('\r', ' ')
df.iloc[index, 0] = rtext
df.to_csv('imdb.csv', index=False)
Burada program hedef olarak "imdb.csv" dosyasını oluşturmaktadır. Bu dosyadaki satırların offset'lerini aşağıdaki gibi bir liste
biçiminde oluşturabiliriz:
2024-07-15 13:23:34 +03:00
def record_offsets(f, skiprows=1):
offsets = []
for i in range(skiprows):
f.readline()
offsets.append(f.tell())
while f.readline() != '':
offsets.append(f.tell())
offsets.pop()
return offsets
Burada biz her satırın offset numarasını offsets isimli listede toplayıp bu listeyle geri döndük. Ancak offset bilgisini okuma
işleminden sonra sakladığımız için EOF offset'i de listenin sonunda bulunmaktadır. pop işlemi ile bu son elemanının sildiğimize
dikkat ediniz. Programımızdaki üretici fonksiyon şöyle yazılmıştır:
2024-07-15 13:23:34 +03:00
def data_generator(f, epochs, steps_per_epoch, batch_size, offsets):
reader = csv.reader(f)
for _ in range(epochs):
random.shuffle(offsets)
for batch_no in range(steps_per_epoch):
x = []
y = []
for offset in offsets[batch_no * batch_size: batch_no * batch_size + batch_size]:
f.seek(offset, 0)
result = next(reader)
x.append(result[0])
y.append(1 if result[1] == 'positive' else 0)
yield tf.convert_to_tensor(x), tf.convert_to_tensor(y)
Bu örneğimizde sınama veri kümesi ve test veri kümesi kullanılmadığı için yalnızca epoch-loss grafiği çizdirilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
import math
import random
import csv
import pandas as pd
import tensorflow as tf
EPOCHS = 5
BATCH_SIZE = 32
2024-07-15 13:23:34 +03:00
def record_offsets(f, skiprows=1):
offsets = []
for i in range(skiprows):
f.readline()
offsets.append(f.tell())
while f.readline() != '':
offsets.append(f.tell())
offsets.pop()
return offsets
f = open('imdb.csv', encoding='latin-1')
offsets = record_offsets(f)
def data_generator(f, epochs, steps_per_epoch, batch_size, offsets):
reader = csv.reader(f)
for _ in range(epochs):
random.shuffle(offsets)
for batch_no in range(steps_per_epoch):
x = []
y = []
for offset in offsets[batch_no * batch_size: batch_no * batch_size + batch_size]:
f.seek(offset, 0)
result = next(reader)
x.append(result[0])
y.append(1 if result[1] == 'positive' else 0)
yield tf.convert_to_tensor(x), tf.convert_to_tensor(y)
df = pd.read_csv('imdb.csv')
2024-07-15 13:23:34 +03:00
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense, TextVectorization
2024-07-15 13:23:34 +03:00
tv = TextVectorization(output_mode='count')
tv.adapt(df['review'])
2024-07-15 13:23:34 +03:00
model = Sequential(name='IMDB')
2024-07-15 13:23:34 +03:00
model.add(Input((1, ), dtype='string'))
model.add(tv)
model.add(Dense(128, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
2024-07-15 13:23:34 +03:00
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
hist = model.fit(data_generator(f, EPOCHS, math.ceil(len(offsets) / BATCH_SIZE), BATCH_SIZE, offsets),
batch_size=BATCH_SIZE, steps_per_epoch=math.ceil(len(offsets) / BATCH_SIZE), epochs=EPOCHS)
2024-07-15 13:23:34 +03:00
import matplotlib.pyplot as plt
2024-07-15 13:23:34 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.legend(['Loss'])
plt.show()
2024-07-15 13:23:34 +03:00
# prediction
2024-07-15 13:23:34 +03:00
predict_df = pd.read_csv('predict-imdb.csv')
predict_result = model.predict(predict_df)
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Peki parçalı verilerle eğitim yapılırken sınama, test ve kestirim işlemlerini de parçalı bir biçimde yapabilir miyiz?
Evet bu işlemlerin hepsi parçalı bir biçimde yapılabilmektedir. fit metodunda validation_data parametresi bir üretici
nesne olarak (ya da bir PyDataset nesnesi olarak) girilirse bu durumda her epoch'tan sonra sınama verileri bu üretici
fonksiyondan (ya da PyDataset nesnesinden) elde edilecektir. Ancak bu durumda fit metodunun validation_steps parametresinde
kaç dolaşımla (yield işlemi ile) sınama verilerinin elde edileceği de girilmelidir. Örneğin:
2024-07-15 13:23:34 +03:00
model.fit(..., validation_data=validation_generator(), validation_steps=100)
2024-07-15 13:23:34 +03:00
Burada sınama verilerinin elde edilmesi için toplam 100 kez dolaşım (yield işlemi) yapılacaktır. Her epoch sonrasındaki
2024-07-15 13:23:34 +03:00
sınamada sınama veri kümesinin karıştırılmasına gerek yoktur.
Test işlemi de benzer biçimde parçalı olarak yapılabilir. Bunun için Sequential sınıfının evaluate metodunun x parametresine
bir üretici nesne (ya da bir PyDataset nesnesi) girilir. Test işlemi yapılırken kaç kere dolaşım uygulanacağı da steps
parametresiyle belirtilmektedir. Örneğin:
2024-07-15 13:23:34 +03:00
eval_result = model.evalute(test_generator(), steps=32)
Kestirim işleminde parçalı veri kullanılmasına genellikle gereksinim duyulmuyor olsa da kestirim işlemi yine parçalı
verilerle yapılabilir. Bunun için Sequential sınıfının predict metdounda x parametresine bir üretici nesne (ya da
PyDataset nesnesi) nesne gerilir. Yine metodun steps parametresi sınama verileri için kaç kez dolaşım uygulanacağını
(yani yield yapılacağını) beelirtir. Örneğin:
predict_result = model.predict(predict_generator(), steps=32)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda IMDB veri kümesi üzerinde üretici fonksiyonlar yoluyla parçalı üretim, parçalı sınama, parçalı test ve parçalı
kestirim işlemlerine örnek verilmiştir. Sınamaların (validation) her epoch sonrasında yapıldığını anımsayınız. Burada parçalı
sınama yapılırkesn iç içe iki döngü kullanılmıştır. Birinci döngü epoch döngüsü, ikinci döngü batch döngüsüdür:
2024-07-15 13:23:34 +03:00
def training_validation_test_generator(f, epochs, steps, batch_size, offsets, shuffle=False):
reader = csv.reader(f)
for _ in range(epochs):
if shuffle:
random.shuffle(offsets)
for batch_no in range(steps):
x = []
y = []
for offset in offsets[batch_no * batch_size: batch_no * batch_size + batch_size]:
f.seek(offset, 0)
result = next(reader)
x.append(result[0])
y.append(1 if result[1] == 'positive' else 0)
yield tf.convert_to_tensor(x), tf.convert_to_tensor(y)
2024-07-15 13:23:34 +03:00
Fonksiyonda eopchs * steps kadar yield işlemi yapıldığına dikkat ediniz. Bu fonksiyon hem eğitim, hem sınama hemi de test
amacıyla kullanılmıştır. Tabii test işleminde bir epoch kavramı yoktur. Bu nedenle test işleminde burada epochs parametresi
1 olarak grilmiştir. Yukarıda da belirttiğimiz gibi kestirimlerde genellikle parçalı işlemlere gereksinim duyulmamaktadır.
Ancak biz burada parçalı kestirime de bir örnek vermek istedik. Parçalı kestirim için kullanılan üretici fonksiyon şöyledir:
def predict_generator(f, steps, batch_size, offsets):
reader = csv.reader(f)
for batch_no in range(steps):
x = []
for offset in offsets[batch_no * batch_size:batch_no * batch_size + batch_size]:
f.seek(offset, 0)
result = next(reader)
x.append(result[0])
yield tf.convert_to_tensor(x),
Parçalı kestirimde de bir epcoh kavramı yoktur. Dolayısıyla fonksiyonda bir epoch döngüsü oluşturulmamıştır.
#----------------------------------------------------------------------------------------------------------------------------
import math
import random
import csv
import pandas as pd
import tensorflow as tf
EPOCHS = 5
BATCH_SIZE = 32
TEST_RATIO = 0.2
VALIDATION_RATIO = 0.2
def record_offsets(f, skiprows=1):
offsets = []
for i in range(skiprows):
f.readline()
offsets.append(f.tell())
while f.readline() != '':
offsets.append(f.tell())
offsets.pop()
return offsets
f = open('imdb.csv', encoding='latin-1')
offsets = record_offsets(f)
random.shuffle(offsets)
test_split_index = int(len(offsets) * (1 - TEST_RATIO))
validation_split_index = int(test_split_index * (1 - VALIDATION_RATIO))
training_offsets = offsets[:validation_split_index]
validation_offsets = offsets[validation_split_index:test_split_index]
test_offsets = offsets[test_split_index:]
training_steps = math.ceil(len(training_offsets) / BATCH_SIZE)
validation_steps = math.ceil(len(validation_offsets)/BATCH_SIZE)
test_steps = math.ceil(len(test_offsets)/BATCH_SIZE)
def training_validation_test_generator(f, epochs, steps, batch_size, offsets, shuffle=False):
reader = csv.reader(f)
for _ in range(epochs):
if shuffle:
random.shuffle(offsets)
for batch_no in range(steps):
x = []
y = []
for offset in offsets[batch_no * batch_size: batch_no * batch_size + batch_size]:
f.seek(offset, 0)
result = next(reader)
x.append(result[0])
y.append(1 if result[1] == 'positive' else 0)
yield tf.convert_to_tensor(x), tf.convert_to_tensor(y)
def predict_generator(f, steps, batch_size, offsets):
reader = csv.reader(f)
for batch_no in range(steps):
x = []
for offset in offsets[batch_no * batch_size:batch_no * batch_size + batch_size]:
f.seek(offset, 0)
result = next(reader)
x.append(result[0])
yield tf.convert_to_tensor(x),
df = pd.read_csv('imdb.csv')
from tensorflow.keras import Sequential
2024-07-15 13:23:34 +03:00
from tensorflow.keras.layers import Input, Dense, TextVectorization
tv = TextVectorization(output_mode='count')
tv.adapt(df['review'])
model = Sequential(name='IMDB')
2024-07-15 13:23:34 +03:00
model.add(Input((1, ), dtype='string'))
model.add(tv)
model.add(Dense(128, activation='relu', 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'])
2024-07-15 13:23:34 +03:00
hist = model.fit(training_validation_test_generator(f,
EPOCHS, training_steps, BATCH_SIZE, training_offsets, True),
batch_size=BATCH_SIZE,
steps_per_epoch=training_steps,
epochs=EPOCHS,
validation_data=training_validation_test_generator(f, EPOCHS, validation_steps, BATCH_SIZE, validation_offsets),
validation_steps=validation_steps)
import matplotlib.pyplot as plt
2024-07-15 13:23:34 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['loss'])
2024-07-15 13:23:34 +03:00
plt.legend(['Loss'])
plt.show()
2024-07-15 13:23:34 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
2024-07-15 13:23:34 +03:00
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
2024-07-15 13:23:34 +03:00
eval_result = model.evaluate(training_validation_test_generator(f, 1, test_steps, BATCH_SIZE, test_offsets), steps=test_steps)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
2024-07-15 13:23:34 +03:00
# prediction
2024-07-15 13:23:34 +03:00
predict_f = open('predict-imdb.csv')
predict_offsets = record_offsets(predict_f)
predict_steps = int(math.ceil(len(predict_offsets) / BATCH_SIZE))
2024-07-15 13:23:34 +03:00
predict_result = model.predict(predict_generator(predict_f, predict_steps, BATCH_SIZE, predict_offsets), steps=predict_steps)
2024-07-15 13:23:34 +03:00
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
2024-07-15 13:23:34 +03:00
#----------------------------------------------------------------------------------------------------------------------------
48. Ders - 07/07/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Parçalı biçimde eğitim, test ve kestirim işlemi üretici fonksiyon yerine sınıfsal bir biçimde de yapılabilmektedir. Bunun
için tensorflow.keras.utils modülü içerisindeki PyDataset sınıfından türetilmiş sınıflar kullanılmaktadır. (Aslında bir süre
öncesine kadar bu amaçla Sequence isimli bir sınıftan türetme yapılıyordu. Ancak Keras ekibi bunun yerine TenserFlow kütüphanesi
içerisindeki Dataset sınıfını Keras'tan kullanılabilir hale getirdi. Dokümantasyondan da Sequence sınıfı kaldırıldı.)
2024-07-15 13:23:34 +03:00
Parçalı eğitim PyDataset sınıfı yoluyla şöyle yapılmaktadır: Programcı önce PyDataset sınıfındna bir sınıf türetir. türemiş
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 metodu 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ı metoda parametre olarak geçirir. Bu metottan programcı batch
büyüklüğü kadar x ve y verisinden oluşan bir demetle geri dönmelidir. (Tabii aslında metodun geri döndürdüğü x ve y değerleri her
defasında aynı uzunlukta olmak zorunda da değildir.) Sonra programcı fit metodunun training_dataset_x parametresine bu sınıf
türünden bir nesne yaratarak o nesneyi girer. Örneğin:
2024-07-15 13:23:34 +03:00
class DataGenerator(PyDataset):
def __init__(self):
super().__init__()
pass
def __len__(self):
pass
def __getitem__(self, batch_no):
pass
...
model.fit(DataGenrator(...), epochs=100)
Tabii artık bu yöntemde fit metodunun steps_per_epoch parametresi kullanılmamaktadır. Metodun bath_size parametresinin de
bu yöntemde bir anlamı yoktur. Ancak epochs parametresi kaç epoch uygulanacağını belirtmek içn kullanılmaktadır.
2024-07-15 13:23:34 +03:00
Sınama işlemi yine benzer bir biçimde yapılmaktadır. Yani programcı yine PyDataset sınıfından sınıf türetip __len__ ve
__getitem__ metotlarını yazar. Tabii durumda her epoch sonrasında bu sınama verileri __getitem__ metodu yoluyla elde
edilecektir. Programcı yine bunun için validation_data parametresine PyDataset sınıfından türettiği sınıf türünden bir
nesne girer. Örneğin:
model.fit(DataGenrator(...), epochs=100, validation_data=DataGenerator(....))
Ayrıca PyDataset 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 fit tarafından bu metot çağrılmaktadır. Programcı da tipik olarak bu metotta eğitim veri kümesini karıştırır.
Bu biçimdeki parçalı eğitimde artık fit metodunun steps_per_epoch parametresi kullanılmamaktadır. Çünkü zaten bu bilgi fit
metodu tarafından __len__ metodu çağrılarak elde edilmektedir. Benzer biçimde evaluate işleminde de yine PyDataset sınıfından
sınıf türetilerek test edilecek veriler parçalı bir biçimde evaluate metoduna verilebilmektedir.
2024-07-15 13:23:34 +03:00
2024-07-20 14:48:03 +03:00
Bu yöntemde predict işlemi de yine benzer biçimde parçalı olarak gerçekleştirilebilmektedir. Ancak her ne kadar predict
işleminde bir epoch kavramı olmasa da Keras predict işleminin sonunda yine on_epoch_end metodunu çağırmaktadır.
2024-07-15 13:23:34 +03:00
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
2024-07-15 13:23:34 +03:00
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense
from tensorflow.keras.utils import PyDataset
2024-07-15 13:23:34 +03:00
EPOCHS = 2
NFEATURES = 10
BATCH_SIZE = 32
2024-07-15 13:23:34 +03:00
class DataGenerator(PyDataset):
def __init__(self, batch_size, nfeatures, *, steps):
super().__init__()
2024-07-15 13:23:34 +03:00
self.batch_size = batch_size
self.nfeatures = nfeatures
self.steps = steps
def __len__(self):
2024-07-15 13:23:34 +03:00
return self.steps
2024-07-15 13:23:34 +03:00
def __getitem__(self, batch_no):
x = np.random.random((self.batch_size, self.nfeatures))
y = np.random.randint(0, 2, self.batch_size)
return x, y
def on_epoch_end(self):
2024-07-15 13:23:34 +03:00
print('shuffle')
model = Sequential(name='Test')
model.add(Input((NFEATURES, )))
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()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
model.fit(DataGenerator(BATCH_SIZE, NFEATURES, steps=50), epochs=EPOCHS, validation_data=DataGenerator(BATCH_SIZE, NFEATURES, steps=5))
eval_result = model.evaluate(DataGenerator(BATCH_SIZE, NFEATURES, steps=10))
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_result = model.predict(DataGenerator(BATCH_SIZE, NFEATURES, steps=1))
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda IMDB veri kümesinin PyDataset sınıfından türetme yapılarak parçalı eğitilmesine bir örnek verilmiştir. Bu örnekte
biz bir tane DataGenerator sınıfı oluşturkduk. Hem eğitim işleminde, hem sınama işleminde, hem test işleminde hem de kestirim
işleminde aynı sınıfı kullandık. Aslında bu örnekte yapılanlar genel mantık olarak üretici fonksiyon örneğinde yapılanlarla
benzerdir. Yukarıda da belirttiğimiz gibi parçalı eğitim için üretici fonksiyonlar yerine bu yöntemin uygulanmasını tavsiye
etmekteyiz.
#----------------------------------------------------------------------------------------------------------------------------
import math
import random
import csv
import pandas as pd
import tensorflow as tf
from tensorflow.keras.utils import PyDataset
EPOCHS = 5
BATCH_SIZE = 32
TEST_RATIO = 0.2
VALIDATION_RATIO = 0.2
def record_offsets(f, skiprows=1):
offsets = []
for i in range(skiprows):
f.readline()
offsets.append(f.tell())
while f.readline() != '':
offsets.append(f.tell())
offsets.pop()
return offsets
f = open('imdb.csv', encoding='latin-1')
offsets = record_offsets(f)
random.shuffle(offsets)
test_split_index = int(len(offsets) * (1 - TEST_RATIO))
validation_split_index = int(test_split_index * (1 - VALIDATION_RATIO))
training_offsets = offsets[:validation_split_index]
validation_offsets = offsets[validation_split_index:test_split_index]
test_offsets = offsets[test_split_index:]
training_steps = math.ceil(len(training_offsets) / BATCH_SIZE)
validation_steps = math.ceil(len(validation_offsets)/BATCH_SIZE)
test_steps = math.ceil(len(test_offsets)/BATCH_SIZE)
class DataGenerator(PyDataset):
def __init__(self, f, steps, batch_size, offsets, *, shuffle=False, predict=False):
super().__init__()
self.f = f
self.steps = steps
self.batch_size = batch_size
self.offsets = offsets
self.shuffle = shuffle
self.predict = predict
self.reader = csv.reader(f)
def __len__(self):
return self.steps
def __getitem__(self, batch_no):
x = []
if not self.predict:
y = []
for offset in self.offsets[batch_no * self.batch_size: batch_no * self.batch_size + self.batch_size]:
f.seek(offset, 0)
result = next(self.reader)
x.append(result[0])
if not self.predict:
y.append(1 if result[1] == 'positive' else 0)
if not self.predict:
return tf.convert_to_tensor(x), tf.convert_to_tensor(y)
return tf.convert_to_tensor(x),
def on_pech_end(self):
random.shuffle(self.offsets)
df = pd.read_csv('imdb.csv')
from tensorflow.keras import Sequential
2024-07-15 13:23:34 +03:00
from tensorflow.keras.layers import Input, Dense, TextVectorization
2024-07-15 13:23:34 +03:00
tv = TextVectorization(output_mode='count')
tv.adapt(df['review'])
model = Sequential(name='IMDB')
model.add(Input((1, ), dtype='string'))
model.add(tv)
model.add(Dense(128, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
2024-07-15 13:23:34 +03:00
model.summary()
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
2024-07-15 13:23:34 +03:00
hist = model.fit(DataGenerator(f, training_steps, BATCH_SIZE, training_offsets, shuffle=True),
epochs=EPOCHS,
validation_data = DataGenerator(f, validation_steps, BATCH_SIZE, validation_offsets))
import matplotlib.pyplot as plt
2024-07-15 13:23:34 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.legend(['Loss'])
plt.show()
plt.figure(figsize=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
2024-07-15 13:23:34 +03:00
eval_result = model.evaluate(DataGenerator(f, test_steps, BATCH_SIZE, test_offsets))
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
2024-07-15 13:23:34 +03:00
# prediction
predict_f = open('predict-imdb.csv')
predict_offsets = record_offsets(predict_f)
predict_steps = int(math.ceil(len(predict_offsets) / BATCH_SIZE))
predict_result = model.predict(DataGenerator(f, predict_steps, BATCH_SIZE, predict_offsets, predict=True))
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
49. Ders - 13/07/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi biz parçalı eğitimi fit metodunu birden fazla kez çağırarak yapamaz mıyız? Keras'ın orijinal dokümanlarında bu
2024-07-15 13:23:34 +03:00
konuda çok açık örnekler verilmemiştir. Ancak kaynak kodlar incelendiğinde fit işleminin artırımlı bir biçimde yapıldığı
görülmektedir. Yani birden fazla kez fit metodu çağrıldığında eğitim kalınan yerden devam ettirilmektedir. Bu nedenle
biz eğitimi farklı zamanlarda fit işlemlerini birden fazla kez yaparak devam ettirebiliriz. Ancak fit metodunun bu biçimde
birden fazla kez çağrılması işleminde dikkat edilmesi gereken bazı noktalar da olabilmektedir. Keras ekibi bu tür parçalı
eğitimler için daha aşağı seviyeli xxx_on_bath isimli metotlar bulundurmuştur. Programcının birden fazla kez fit metodunu
çağırması yerine bu metotları kullanması tavsiye edilmektedir. Parçalı işlemler için Sequential sınıfının şu metotları
bulundurulmuştur:
2024-07-15 13:23:34 +03:00
train_on_batch
test_on_batch
predict_on_batch
2024-07-15 13:23:34 +03:00
Ancak bu yöntemde sınama işlemleri otomatik olarak train_on_batch içerisinde yapılmamaktadır. Programcının sınamayı kendisinin
yapması gerekmektedir.
2024-07-15 13:23:34 +03:00
train_on_batch metodunun parametrik yapısı şöyledir:
2024-07-15 13:23:34 +03:00
train_on_batch(x, y=None, sample_weight=None, class_weight=None, return_dict=False)
2024-07-15 13:23:34 +03:00
Burada x ve y parametreleri parçalı eğitimde kullanılacak x ve y değerlerini almaktadır. sample_weight ve class_weight
parametreleri ağırlıklandırmak için kullanılmaktadır. return_dict parametresi True geçilirse metot bize geri dönüş değeri
olarak loss değerini ve metrik değerleri bir sözlük nesnesi biçiminde verir.
2024-07-15 13:23:34 +03:00
train_on_batch metodu ile parçalı eğitim biraz daha düşük seviyeli olarak yapılmaktadır. Bu biçimde parçalı eğitimde epoch
döngüsünü ve batch döngüsünü programcı kendisi oluşturmalıdır. Örneğin:
2024-07-15 13:23:34 +03:00
for epoch in range(EPOCHS):
<eğitim veri kümesi karıştırılıyor>
for batch_no in range(NBATCHES):
<bir batch'lik x ve y elde ediliyor>
model.train_on_batch(x, y)
Tabii yukarıda da belirttiğimiz gibi bu biçimde çalışma aşağı seviyelidir. Yani bazı şeyleri programcının kendisinin
yapması gerekir. Örneğin fit metodu bize bir History sınıfı türünden bir callback nesnesi veriyordu. Bu nesnenin içerisinden
de biz tüm epoch'lara ilişkin metrik değerleri elde edebiliyorduk. train_on_batch işlemleriyle eğitimde bu bu değerleri
bizim elde etmemiz gerekmektedir. train_on_batch metodunun return_dict parametresi True geçilirse batch işlemi sonucundaki
loss ve metik değerler bize bir sözlük biçiminde verilmektedir. Biz de bu değerlerden hareketle epoch'taki ortalama loss ve
metrik değerleri hesaplayabiliriz. Örneğin:
2024-07-15 13:23:34 +03:00
for epoch in range(EPOCHS):
<eğitim veri kümesi karıştırılıyor>
for batch_no in range(NBATCHES):
<bir batch'lik x ve y elde ediliyor>
rd = model.train_on_batch(x, y, return_dict=True)
<batch'e ilişkin loss ve metrik değerler epoch için ortalama hesabında kullanılıyor>
2024-07-15 13:23:34 +03:00
Burada her spoch sonrasında değil her batch sonrasında değerlerin elde edildiğine dikkat ediniz. Aslında biz Tensorflow
ya da PyTorch kütüphanelerini aşağı seviyeli olarak kullanacak olsaydık zaten yine işlemleri bu biçimde iki döngü yoluyla
yapmak durumunda kalacaktık. Genellikle uygulamacılar her batch işleminde elde edilen değerlerin bir ortalamasını epoch
değeri olarak kullanmaktadır.
2024-07-15 13:23:34 +03:00
Bu yöntemde epoch sonrasındaki sınama işlemlerinin de programcı tarafından manuel olarak yapılması gerekmektedir.
Yani programcı sınama veri kümesini kendisi oluşturmalı ve sınamayı kendisi yapmalıdır. evaulate metodu aslen test
amaçlı kullanılsa da işlev bakımından sınama amaçlı da kullanılabilmektedir. Bu durumda sınama işlemi şöyle yapılabilir:
2024-07-15 13:23:34 +03:00
for epoch in range(EPOCHS):
<eğitim veri kümesi karıştırılıyor>
for batch_no in range(NBATCHES):
<bir batch'lik x ve y elde ediliyor>
rd = model.train_on_batch(x, y, return_dict=True)
val_result = model.evaluate(...)
Tabii burada evaluate işlemini de parçalı bir biçimde yapabilirsiniz. Bu durumda yine bir döngü oluşturup test_on_batch
fonksiyonunu kullanabilirsiniz. test_on_batch metodunun parametrik yapısı şöyledir:
2024-07-15 13:23:34 +03:00
test_on_batch(x, y=None, sample_weight=None, return_dict=False)
2024-07-15 13:23:34 +03:00
Kullanım tamamen train_batch metodu gibidir.
2024-07-15 13:23:34 +03:00
Test işlemi de tüm epoch'lar bittiğinde yine parçalı bir biçimde test_on_batch metoduyla yapılabilir. Kestirim işlemi de
yine benzer bir biçimde predict_on_batch metoduyla yapılabilmektedir. predict_on_batch metodunun parametrik yapısı şöyledir:
2024-07-15 13:23:34 +03:00
predict_on_batch(x)
Metot kestirim değerleriyle geri dönmektedir. Örneğin:
for batch_no in range(NBATCHES):
<bir batch'lik x elde ediliyor>
predict_result = model.predict_on_batch(x)
2024-07-15 13:23:34 +03:00
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
50. Ders - 14/07/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda IMDB veri kümesi üzerinde xxx_on_batch metotlarıyla parçalı biçimde işlemler yapılmasına bir örnek verilmiştir.
Bu örnekte tüm veri kümesi tek hamlede DataFrame nesnesi olarak okunmuştur. Aslında burada parçalı işlemler için daha önce
yapmış olduğmuz işlemlerin uygulanması daha uygundur. Ancak biz örneği karmaşık hale getirmemek için tüm veri kümesini tek
hamlede okuyup xxx_on_batch metotlarına onu parçalara ayırarak verdik.
2024-07-15 13:23:34 +03:00
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense, TextVectorization
EPOCHS = 5
BATCH_SIZE = 32
TEST_SPLIT_RATIO = .20
VALIDATION_SPLIT_RATIO = .20
def create_model(df):
tv = TextVectorization(output_mode='count')
tv.adapt(df['review'])
2024-07-15 13:23:34 +03:00
model = Sequential(name='IMDB')
2024-07-15 13:23:34 +03:00
model.add(Input((1, ), dtype='string'))
model.add(tv)
model.add(Dense(128, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
return model
def train_test_model(training_df, validation_df, epochs, verbose = 1):
history_loss = []
history_accuracy = []
val_history_loss = []
val_history_accuracy = []
for epoch in range(epochs):
training_df = training_df.sample(frac=1)
2024-07-15 13:23:34 +03:00
print('-' * 30)
2024-07-15 13:23:34 +03:00
mean_loss, mean_accuracy = batch_train(training_df, int(np.ceil(len(training_df) / BATCH_SIZE)), model.train_on_batch, verbose=1)
history_loss.append(mean_loss)
history_accuracy.append(mean_accuracy)
if verbose == 1:
print(f'Epoch: {epoch + 1}')
print(f'Epoch mean loss: {mean_loss}, Epoch Binary Accuracy: {mean_accuracy}')
2024-07-15 13:23:34 +03:00
val_mean_loss, val_mean_accuracy = batch_train(validation_df, int(np.ceil(len(validation_df) / BATCH_SIZE)), model.test_on_batch)
val_history_loss.append(val_mean_loss)
val_history_accuracy.append(val_mean_accuracy)
if verbose == 1:
print(f'Validation Loss: {val_mean_loss}, Validation Binary Accuracy: {val_mean_accuracy}')
return history_loss, history_accuracy, val_history_loss, val_history_accuracy
def batch_train(df, nbatches, batch_method, verbose=0):
loss_list = []
accuracy_list = []
for batch_no in range(nbatches):
x = tf.convert_to_tensor(df['review'].iloc[batch_no * BATCH_SIZE: batch_no * BATCH_SIZE + BATCH_SIZE], dtype='string')
y = tf.convert_to_tensor(df['sentiment'].iloc[batch_no * BATCH_SIZE: batch_no * BATCH_SIZE + BATCH_SIZE])
rd = batch_method(x, y, return_dict=True)
loss_list.append(rd['loss'])
accuracy_list.append(rd['binary_accuracy'])
if verbose:
print(f'Batch No: {batch_no}')
if verbose == 2:
print(f"Batch Loss: {rd['loss']}, Batch Binary Accuracy: {rd['accuracy']}")
mean_loss = np.mean(loss_list)
mean_accuracy = np.mean(accuracy_list)
return mean_loss, mean_accuracy
df = pd.read_csv('IMDB Dataset.csv').iloc[:10000, :]
df['sentiment'] = (df['sentiment'] == 'positive').astype(dtype='uint8')
df = df.sample(frac=1)
test_zone = int(len(df) * (1 - TEST_SPLIT_RATIO))
training_validation_df = df.iloc[:test_zone, :]
test_df = df.iloc[test_zone:, :]
validation_zone = int(len(training_validation_df) * (1 - VALIDATION_SPLIT_RATIO))
training_df = training_validation_df.iloc[:validation_zone, :]
validation_df = training_validation_df.iloc[validation_zone:, :]
model = create_model(df)
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
history_loss, history_accuracy, val_history_loss, val_history_accuracy = train_test_model(training_df, validation_df, EPOCHS)
2024-07-15 13:23:34 +03:00
import matplotlib.pyplot as plt
2024-07-15 13:23:34 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, EPOCHS))
plt.plot(range(EPOCHS), history_loss)
plt.plot(range(EPOCHS), val_history_loss)
plt.legend(['Loss', 'Validation Loss'])
plt.show()
2024-07-15 13:23:34 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, EPOCHS))
plt.plot(range(EPOCHS), history_accuracy)
plt.plot(range(EPOCHS), val_history_accuracy)
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
2024-07-15 13:23:34 +03:00
# evaluation
2024-07-15 13:23:34 +03:00
eval_mean_loss, eval_accuracy = batch_train(test_df, int(np.ceil(len(test_df) / BATCH_SIZE)), model.test_on_batch)
print(f'Test Loss: {eval_mean_loss}, Test Binary Accuracy: {eval_accuracy}')
2024-07-15 13:23:34 +03:00
# prediction
2024-07-15 13:23:34 +03:00
predict_df = pd.read_csv('predict-imdb.csv')
2024-07-15 13:23:34 +03:00
for i in range(int(np.ceil(len(predict_df) / BATCH_SIZE))):
predict_result = model.predict_on_batch(predict_df)
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Kursumuzun bu noktasında sözünü ettiğimiz kütüphanelerin işlevlerini yeniden anımsatmak istiyoruz:
NumPy ---> Vektörel işlemler yapan C'de yazılmış taban bir kütüphanedir. Pek çok proje NumPy kütüphanesini kendi içerisinde
kullanmaktadır.
Pandas ---> Bu kütüphane sütunlu veri yapılarını (yani istatistiksel veri tablolarını) ifade etmek için kullanılmaktadır.
Kütüphanenin en önemli özelliği farklı türlere ilişkin sütunsal bilgilerin DataFrame isimli bir veri yapısı ile temsil
edilmesini sağlamasıdır. Bu kütüphane de NumPy kullanılarak yazılmıştır.
scikit-learn ---> Bir makine öğrennesi kütüphanesidir. Ancak bu kütüphanede yapay sinir ağlarıyla ilgili özellikler yoktur
(minimal düzeydedir). Yani bu kütüphane yapay sinir ağlarının dışındaki makine öğrenmesi yöntemleri için kullanılmaktadır.
scikit-learn kendi içerisinde NumPy, Pandas ve SciPy kütüphanelerini kullanmaktadır.
SciPy ---> Genel amaçlı matematik ve nümerik analiz kütüphanesidir. Bu kütüphanenin doğrudan makine öğrenmesiyle bir ilgisi
yoktur. Ancak matematiğin çeşitli alanlarına ilişkin nümerik analiz işlemleri yapan geniş bir taban kütüphanedir. Bu kütüphane
de kendi içerisinde NumPy ve Pandas kullanılarak yazılmıştır.
TensorFlow ---> Google tarafından yapay sinir ağları ve makine öğrenmesi için oluşturulmuş taban bir kütüphanedir. Bu kütüphane
çok işlemcili ve çekirdekli sistemlerde matrisler (bunlara "tensor" de denilmektedir) üzerinde paralel programlama teknikleriyle
işlemler yapabilmek amacıyla tasarlanmıştır.
Keras ---> Yapay sinir ağı işlemlerini kolaylaştırmak için oluşturulmuş olan yüksek seviyeli bir kütüphanedir. Eskiden bu
kütüphane "backend" olarak farklı kütüphaneleri kullanbiliyordu. Eski hali devam ettirilse de kütüphane taamamen TensorFlow
içerisine dahil edilmiştir ve TensorFlow kütüphanesinin yüksek seviyeli bir katmanı haline getirilmiştir.
PyTorch ---> Tamamen TensorFlow kütüphanesinde hedeflenen işlemleri yapan taban bir yapay sinir ağı ve makine öğrenmesi
kütüphanesidir. Facebook (Meta) tarafından geliştirilmiştir.
Theano --> TensorFlow, PyTorch SciPy benzeri bir taban kütüphanedir. Akademik çevreler tarafından geliştirilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Elemanlarının çok büyük kısmı 0 olan matrislere "seyrek matrisler (sparse matrices)" denilmektedir. Seyreklik (sparsity)
0 olan elemanların tüm elemanlara oranıyla belirlenmektedir. Bit matirisn seyrek olarak ele alınması için herkes tarafından
kabul edilen bir seyreklik oranı yoktur. Seyreklik oranı ne kadar yüksek olursa onların çok boyutlu diziler yerine alternatif
veri yapılarıyla ifade edilmeleri o kadar verimli olmaktadır. Makine öğrenmesinde seyrek matrisle sıkça karşılaşılmaktadır.
Örneğin bir grup yazıyı vektörel hale getirdiğimizde aslında bir seyrek matris oluşmaktadır. Benzer biçimde one-hot encoding
dönüştürmesi de bir seyrek matris oluşturmaktadır. scikit-learn kütüphanesindeki OneHotEncoder sınıfının ve CountVectorizer
sınıfının çıktı olarak seyrek matris verdiğini anımsayınız.
Seyrek matrislerin daha az yer kaplayacak biçimde tutulmasındaki temel yaklaşım matrisin yalnızca sıfırdan farklı elemanlarının
2024-07-15 13:23:34 +03:00
ve onların yerlerinin tutulmasıdır. Örneğin bir milyon elemana sahip bir seyrek matriste yalnızca 100 eleman sıfırdan
faklıysa biz bu 100 elemanın değerlerini ve matristeki yerlerini tutarsak önemli bir yer kazancı sağlayabiliriz.
2024-07-15 13:23:34 +03:00
#----------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
#----------------------------------------------------------------------------------------------------------------------------
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. Bu veri yapısında matrisin yalnızca 0'dan farklı olan elemanları bir sözlükte tutulur. Sözlüğün
biçimi aşağıdaki gibidir:
2024-07-15 13:23:34 +03:00
{(34674,17000): 1, (542001, 170): 4, ...}
Burada sözlüğün anahtarları satır ve sütun numaralarından oluşan demetler biçimindedir. Sözlüğün değerleri ise o satır ve
sütundaki matris değerlerinden oluşmaktadır. Örneğin aşağıdaki gibi bir matris söz konusu olsun:
2024-07-15 13:23:34 +03:00
0 0 5
3 0 0
0 6 0
2024-07-15 13:23:34 +03:00
Buradaki dok sözlüğü şöyle olacaktır:
{(0, 2): 5, (1, 0): 3, (2, 1): 6}
2024-07-15 13:23:34 +03:00
Aşağıda DOK veri yapısı ile seyrek matris oluşturan DokMatrix isimli bir sınıf örneği verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
class DokMatrix:
2024-07-15 13:23:34 +03:00
def __init__(self, nrows, ncols):
self._nrows = nrows
self._ncols = ncols
self._dok = {}
2024-07-15 13:23:34 +03:00
def __getitem__(self, index):
if not isinstance(index, tuple) or len(index) != 2:
2024-07-15 13:23:34 +03:00
raise TypeError('index must have twho dimension')
if index[0] < 0 or index[0] >= self._nrows:
raise IndexError('index out of range')
if index[1] < 0 or index[1] >= self._ncols:
raise IndexError('index out of range')
2024-07-15 13:23:34 +03:00
return self._dok.get(index, 0)
def __setitem__(self, index, val):
if not isinstance(index, tuple) or len(index) != 2:
raise TypeError('index must have twho dimension')
if index[0] < 0 or index[0] >= self._nrows:
raise IndexError('index out of range')
if index[1] < 0 or index[1] >= self._ncols:
raise IndexError('index out of range')
2024-07-15 13:23:34 +03:00
self._dok[index] = val
@property
def shape(self):
return self._nrows, self._ncols
@property
def size(self):
return self._nrows * self._ncols
def __len__(self):
return self._nrows
@staticmethod
def array(a):
if not isinstance(a, list):
raise TypeError('argument must be Python list')
nrows = len(a)
ncols = len(a[0])
for i in range(1, nrows):
if len(a[i]) != ncols:
raise ValueError('matrix rows must have the same number of elements')
dm = DokMatrix(nrows, ncols)
for i in range(nrows):
for k in range(ncols):
if a[i][k] != 0:
dm._dok[(i, k)] = a[i][k]
return dm
def todense(self):
2024-07-15 13:23:34 +03:00
array = np.zeros((self._nrows, self._ncols))
for index, val in self._dok.items():
array[index] = val
return array
def __str__(self):
smatrix = ''
for i in range(self._nrows):
sline = ''
for k in range(self._ncols):
if sline != '':
sline += ' '
sline += str(self._dok.get((i, k), 0))
if smatrix != '':
smatrix += '\n'
smatrix += sline
2024-07-15 13:23:34 +03:00
return smatrix
2024-07-15 13:23:34 +03:00
def __repr__(self):
smatrix = ''
for index, val in self._dok.items():
if smatrix != '':
smatrix += '\n'
smatrix += f'({index[0]}, {index[1]}) ---> {val}'
return smatrix
a = [[1, 0, 3], [0, 0, 1], [5, 0, 0]]
dok = DokMatrix.array(a)
print(dok)
print('-' * 10)
dok[1, 1] = 8
print(dok)
print('-' * 10)
print(repr(dok))
2024-07-15 13:23:34 +03:00
#----------------------------------------------------------------------------------------------------------------------------
NumPy kütüphanesi içerisinde seyrek matrislerle işlem yapan sınıflar ya da fonksiyonlar bulunmamaktadır. Ancak SciPy
kütüphanesi içerisinde seyrek matrislerle ilgili işlemler yapan sınıflar ve fonksiyonlar vardır. scikit_learn kütüphanesi
doğrudan SciPy kütüphanesinin seyrek matris sınıflarını kullanmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
DOK biçimindeki seyrek matrisler SciPy kütüphanesinde scipy.sparse modülü içerisindeki dok_matrix sınıfıyla temsil edilmiştir.
Biz bir dok_marix sınıfı türünden nesneyi yalnızca boyut belirterek yaratabiliriz. Daha sonra bu nesneyi 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
2024-07-15 13:23:34 +03:00
metotları kullanılmaktadır. Örneğin:
from scipy.sparse import dok_matrix
dok = dok_matrix((10, 10), dtype='int32')
dok[1, 3] = 10
dok[3, 5] = 20
a = dok.todense()
print(dok)
print('-' * 20)
print(a)
dok_matrix sınıfının minimum, maximum, sum, mean gibi birtakım faydalı metotları bulunmaktadır. nonzero metodu sıfır
dışındaki elemanların indekslerini vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
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
2024-07-15 13:23:34 +03:00
çarpabiliriz. Ancak bu işlemler yavaş olma eğilimindedir. Örneğin:
from scipy.sparse import dok_matrix
dok1 = dok_matrix((5, 5), dtype='int32')
dok1[1, 2] = 10
dok1[0, 1] = 20
dok2 = dok_matrix((5, 5), dtype='int32')
dok2[3, 2] = 10
dok2[4, 1] = 20
dok2[1, 2] = 20
result = dok1 + dok2
print(result)
result = dok1 * dok2
print(result)
#----------------------------------------------------------------------------------------------------------------------------
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)
#----------------------------------------------------------------------------------------------------------------------------
2024-07-15 13:23:34 +03:00
Diğer bir seyrek matris veri yapısı da "LIL (List of Lists)" 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
2024-07-15 13:23:34 +03:00
tutulmaktadır. LIL matrisler SciPy kütüphanesinde scipy.sparse modülündeki lil_matrix sınıfyla temsil edilmektedir. Bu sınıfın
genel kullanımı dok_matrix sınıfında olduğu gibidir. Sınıfın data ve rows örnek öznitelikleri bize bu bilgileri vermektedir.
Örneğin aşağıdaki gibi bir matrisi lil_matrix yapmış olalım:
[[ 0 0 10 20 0]
[15 0 0 0 40]
[12 0 51 0 16]
[42 0 18 0 16]
[ 0 0 0 0 0]]
Buradaki data listesi şöyle olacaktır:
array([list([10, 20]), list([15, 40]), list([12, 51, 16]), list([42, 18, 16]), list([])], dtype=object)
rows lsistesi de şöyle olacaktır:
array([list([2, 3]), list([0, 4]), list([0, 2, 4]), list([0, 2, 4]), list([])], dtype=object)
LIL matrisler de artimetik işlemlerde yavaştır. Dilimleme işlemleri de bu matrislerde nispeten yavaş yapılmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
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)
#----------------------------------------------------------------------------------------------------------------------------
Aslında uygulamada DOK ve LIL matrisler seyrek kullanılmaktadır. Daha çok CSR ve CSC veri yapıları tercih edilmektedir.
2024-07-15 13:23:34 +03:00
CSR (Compressed Sparse Row), ve CSC (Compressed Sparse Column) matrisleri genel veri yapısı olarak birbirlerine çok
benzemektedir. Bunlar adeta birbirlerinin tersi durumundadır.
Bu veri yapıları seyrek matrislerin karşılıklı elemanlarının işleme sokulması durumunda DOK ve LIL veri yapılarına göre
daha avantajlıdır. CSR satır dilimlemesini CSC ise sütun dilimlemesi hızlı yapabilmektedir. Ancak bu matrislerde seyrek bir
matrisin 0 olmayan bir elemanına atama yapmak nispeten yavaş bir işlemdir.
2024-07-15 13:23:34 +03:00
CSR veri yapısı da SciPy kütüphanesinde scipy.sparse modülünde csr_matrix sınıfıyla temsil edilmektedir. CSR matrislerde
sıfırdan farklı elemanlar üç dizi (liste) halinde tutulmaktadır: data, indices ve indptr. Bu diziler sınıfın aynı isimli örnek
2024-07-15 13:23:34 +03:00
özniteliklerinden elde edilebilmektedir. data dizisi sıfır olmayan elemanların tutulduğu tek boyutlu dizidir. indices dizisi
data dizisindeki elemanların kendi satırlarının hangi sütunlarında bulunduğunu belirtmektedir. indptr dizisi 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. indptr dizisi hep yan yana iki eleman olarak değerlendirilmelidir. Soldaki eleman ilk indeksi, sağdaki eleman
2024-07-15 13:23:34 +03:00
ise son indeksi belirtir.
Ö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
2024-07-15 13:23:34 +03:00
durumlarda DOK ya da LIL matrisler daha hızlı işleme yol açarlar. Bu nedenle bu matrisler kullanılırken sıfır olmayan bir
elemana atama yapıldığında bir uyarı mesajıyla karşılaşabilirsiniz. O halde CSR ve CSC matrisleri işin 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)
2024-07-20 14:48:03 +03:00
#----------------------------------------------------------------------------------------------------------------------------
51. Ders - 20/07/2024 - Cumartesi
2024-07-20 14:48:03 +03:00
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
csr matrislerin data, indices ve indptr örnek öznitelikleri yukarıda açıklanan matris bilgilerini bize vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
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]
#----------------------------------------------------------------------------------------------------------------------------
2024-07-20 14:48:03 +03:00
CSC formatı aslında CSR formatına çok benzerdir. CSR formatı adeta CSC formatının sütunsal biçimidir. Yani iki format
arasındaki tek fark CSR formatında satır indeksleri tutulurken, CSC formatında sütun indekslerinin tutulmasıdır. Yapılan
2024-07-20 14:48:03 +03:00
işlemlerin 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]
#----------------------------------------------------------------------------------------------------------------------------
Yukarıdaki seyrek matris sınıflarının her birinde diğer seyrek matris sınıflarına dönüştürme yapan toxxx biçiminde 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.
#----------------------------------------------------------------------------------------------------------------------------
2024-07-20 14:48:03 +03:00
import numpy as np
from scipy.sparse import csc_matrix
2024-07-20 14:48:03 +03:00
a = np.array([[0, 0, 10, 20, 0], [15, 0, 0, 0, 40], [12, 0, 51, 0, 16], [42, 0, 18, 0, 16], [0, 0, 0, 0, 0]])
print(a)
csc = csc_matrix(a, dtype='int32')
print(csc)
print(csc.todense())
csr = csc.tocsr()
print(csr)
dok = csr.todok()
print(dok)
2024-07-20 14:48:03 +03:00
lil = dok.tolil()
print(lil)
#----------------------------------------------------------------------------------------------------------------------------
Seyrek bir matris train_test_split fonksiyonuyla ayrıştırılabilir. Çünkü zaten train_test_split fonksiyonu dilimleme yoluyla
2024-07-20 14:48:03 +03:00
işlemlerini yapmaktadır. Ancak seyrek matrislere len fonksiyonu uygulanamaz. Fakat seyrek matrislerin boyutları yine shape
örnek özniteliği ile elde edilebilir. train_test_split fonksiyonu seyrek matrisi de karıştırabilmektedir.
Aşağıda bir seyrek matris oluşturulup train_test_split fonksiyonu ile bu matris iki kısma ayrılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
2024-07-20 14:48:03 +03:00
import numpy as np
from scipy.sparse import csr_matrix
from sklearn.model_selection import train_test_split
dense = np.zeros((10, 5))
for i in range(len(dense)):
rcols = np.random.randint(0, 5, 2)
dense[i, rcols] = np.random.randint(0, 100, 2)
sparse_dataset_x = csr_matrix(dense)
dataset_y = np.random.randint(0, 2, 10)
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(sparse_dataset_x, dataset_y, test_size=0.2)
print(training_dataset_x)
print(training_dataset_y)
print(test_dataset_x)
print(test_dataset_y)
#----------------------------------------------------------------------------------------------------------------------------
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
2024-07-20 14:48:03 +03:00
matris işlemlerinde etkin değildir. DOK matrisler dilimleme de de etkin değildir.
- 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.
2024-07-20 14:48:03 +03:00
- CSR matrisler satırsal dilimlemelerde CSC matrisler ise sütunsal dilimlemelerde hızlıdırlar. Ancak CSR sütünsal dilimlemelerde,
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.
2024-07-20 14:48:03 +03:00
O halde biz eğer eleman değerleri değiştirilmeyecekse CSR ya da CSC matris kullanabiliriz. Ancak eleman değerleri
değiştirilecekse önce işlemlemlerimize DOK ya da LIL matrisle başlayıp değişikler yapıldıktan sonra matris işlemlerine
başlamadan önce matrisimizi CSR ya da SCS formatına dönüştürebiliriz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-07-20 14:48:03 +03:00
Aslında biz daha önce bazı konularda seyrek matris kavramıyla zaten karşılaşmıştık. Örneğin scikit-learn içerisindeki
OneHotEncoder sınıfının sparse_output parametresi False geçilmezse bu sınıf bize transform işleminde SCiPy'ın CSR formatında
seyrek matrisini 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)
2024-07-20 14:48:03 +03:00
print(type(result))
print(result.todense())
#----------------------------------------------------------------------------------------------------------------------------
2024-07-20 14:48:03 +03:00
Benzer biçimde scikit-learn kütüphanesindeki CountVectorizer sınıfı da yine bize SCiPy'ın CSR formatında seyrek matrisini
vermektedir.
#----------------------------------------------------------------------------------------------------------------------------
from sklearn.feature_extraction.text import CountVectorizer
2024-07-20 14:48:03 +03:00
texts = ['this film is very very good', 'I hate this film', 'It is good', 'I don\'t like it']
cv = CountVectorizer()
2024-07-20 14:48:03 +03:00
cv.fit(texts)
result = cv.transform(texts)
print(result)
print(result.todense())
#----------------------------------------------------------------------------------------------------------------------------
2024-07-20 14:48:03 +03:00
Seyrek matrisleri karıştırmak için NumPy'da herhangi bir fonksiyon yoktur. (SciPy'ın NumPy'ı kullandığını, NumPy'ın SciPy'ı
kullanmadığını anımsayınız.) Ancak scikit-learn kütüphanesinde utils modülü içerisindeki shuffle fonksiyonu SciPy'ın seyrek
matrislerini karıştırabilmektedir. Tabii bu fonksiyon karıştırma sonucunda karıştırılmış matrisi bize yine seyrek matris
olarak vermektedir. Buradaki shuffle fonksiyonu birden fazla girdi alabilmektedir. Karıştırmayı paralel biçimde yaptığı
için karıştırılmış x matrisiyle y matrisinin karşılıklı elemanları yine aynı olmaktadır. Örneğin:
result_x, result_y = shuffle(x, y)
2024-07-20 14:48:03 +03:00
Burada karıştırma sonrasında x'in aynı satırları yine y'nin aynı satırlarına karşı gelecektir.
2024-07-20 14:48:03 +03:00
#----------------------------------------------------------------------------------------------------------------------------
import numpy as np
from scipy.sparse import csr_matrix
from sklearn.utils import shuffle
a = np.array([[0, 0, 10, 20, 0], [15, 0, 0, 0, 40], [12, 0, 51, 0, 16], [42, 0, 18, 0, 16], [0, 0, 0, 0, 0]])
csr = csr_matrix(a)
y = [10, 20, 30, 40, 50]
result_x, result_y = shuffle(csr, y)
print(csr.todense())
print(y)
print('-' * 15)
print(result_x.todense())
print(result_y)
#----------------------------------------------------------------------------------------------------------------------------
Seyrek matris sınıfları büyük kısmı sıfır olan matrislerin bellekte daha az yer kaplamasını sağlamak için kullanılmaktadır.
Aslında seyrek matrislerle işlemler normal (dense) matrislerle işlemlerden her zaman daha yavaştır. Pekiyi bizim elimizde
bir seyrek matris varsa biz bunu Keras'ta nasıl kullanabiliriz? Örneğin CountVectorizer işleminden büyük bir seyrek matris
elde etmiş olalım ve Keras'ın TextVectorization katmanını kullanmıyor olalım. Bu durumda bu seyrek matrisi sinir ağlarında
nasıl kullanabiliriz?
2024-07-20 14:48:03 +03:00
Seyrek matrislerin Keras sinir ağlarında kullanılmasının temelde iki yöntemi vardır:
1) Parçalı eğitim uygulanırken seyrek matrisin ilgili batch'lik kısımları o anda yoğun matrise dönüştürüp verilebilir.
2) Tensorflow kütüphanesinin Input katmanına sonradan bir sparse parametresi eklenmiştir. Bu parametre True yapılarak artık
doğrudan dataset_x değerleri SciPy sparse matrisi olarak verilebilmektedir.
2024-07-20 14:48:03 +03:00
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şimdi tüm verilerin vektörizasyonunu CountVectorizer sınıfı ile tek hamlede yapıp bir seyrek matris elde ettikten sonra
parçalı eğitim ile seyrek matrisin ilgili batch'lik bölümünü yoğun hale getirerek eğitim, sınama, test ve kestirim işlemlerini
yapalım. Aşağıdaki örnekte IMDB veri kümesi tek hamlede okunup CountVectorizer sınıfı ile seyrek biçimde vektörize edilmiştir.
Sonra DataGenerator sınıfı ile bu seyrek matris batch batch yoğun matrise dönüşürülerek işlemler ypılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
2024-07-20 14:48:03 +03:00
import math
import pandas as pd
2024-07-20 14:48:03 +03:00
from tensorflow.keras.utils import PyDataset
import tensorflow as tf
from sklearn.utils import shuffle
EPOCHS = 5
BATCH_SIZE = 32
df = pd.read_csv('IMDB Dataset.csv')
2024-07-20 14:48:03 +03:00
from sklearn.feature_extraction.text import CountVectorizer
2024-07-20 14:48:03 +03:00
cv = CountVectorizer(dtype='uint8')
2024-07-20 14:48:03 +03:00
dataset_x = cv.fit_transform(df['review'])
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
2024-07-20 14:48:03 +03:00
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.2)
training_dataset_x, validation_dataset_x, training_dataset_y, validation_dataset_y = train_test_split(temp_dataset_x, temp_dataset_y, test_size=0.2)
training_steps = math.ceil(training_dataset_x.shape[0] / BATCH_SIZE)
validation_steps = math.ceil(validation_dataset_x.shape[0] / BATCH_SIZE)
test_steps = math.ceil(test_dataset_x.shape[0] / BATCH_SIZE)
class DataGenerator(PyDataset):
def __init__(self, sparse_x, y, steps, batch_size, predict=False):
super().__init__()
self.sparse_x = sparse_x
self.y = y
self.steps = steps
self.batch_size = batch_size
self.predict = predict
def __len__(self):
return self.steps
def __getitem__(self, batch_no):
x = self.sparse_x[batch_no * self.batch_size: batch_no * self.batch_size + self.batch_size]
if not self.predict:
y = self.y[batch_no * self.batch_size: batch_no * self.batch_size + self.batch_size]
return tf.convert_to_tensor(x.todense()), tf.convert_to_tensor(y)
else:
return tf.convert_to_tensor(x.todense()),
def on_epoch_end(self):
if not self.predict:
self.sparse_x, self.y = shuffle(self.sparse_x, self.y)
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Dense
model = Sequential(name='IMDB')
model.add(Input((training_dataset_x.shape[1], )))
model.add(Dense(128, activation='relu', 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(DataGenerator(training_dataset_x, training_dataset_y, training_steps, BATCH_SIZE),
validation_data = DataGenerator(training_dataset_x, training_dataset_y, validation_steps, BATCH_SIZE), epochs=EPOCHS)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.legend(['Loss'])
plt.show()
plt.figure(figsize=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
eval_result = model.evaluate(DataGenerator(test_dataset_x, test_dataset_y, test_steps, BATCH_SIZE))
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
df_predict = pd.read_csv('predict-imdb.csv')
predict_dataset_x = cv.transform(df_predict['review'])
predict_steps = math.ceil(predict_dataset_x.shape[0] / BATCH_SIZE)
predict_result = model.predict(DataGenerator(predict_dataset_x, None, predict_steps, BATCH_SIZE, True))
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
import math
import pandas as pd
from tensorflow.keras.utils import PyDataset
import tensorflow as tf
from sklearn.utils import shuffle
EPOCHS = 5
BATCH_SIZE = 32
2024-07-20 14:48:03 +03:00
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(dtype='uint8')
dataset_x = cv.fit_transform(df['review'])
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
from sklearn.model_selection import train_test_split
2024-07-20 14:48:03 +03:00
temp_dataset_x, test_dataset_x, temp_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.2)
2024-07-20 14:48:03 +03:00
training_dataset_x, validation_dataset_x, training_dataset_y, validation_dataset_y = train_test_split(temp_dataset_x, temp_dataset_y, test_size=0.2)
2024-07-20 14:48:03 +03:00
training_steps = math.ceil(training_dataset_x.shape[0] / BATCH_SIZE)
validation_steps = math.ceil(validation_dataset_x.shape[0] / BATCH_SIZE)
test_steps = math.ceil(test_dataset_x.shape[0] / BATCH_SIZE)
class DataGenerator(PyDataset):
def __init__(self, sparse_x, y, steps, batch_size, predict=False):
super().__init__()
2024-07-20 14:48:03 +03:00
self.sparse_x = sparse_x
self.y = y
2024-07-20 14:48:03 +03:00
self.steps = steps
self.batch_size = batch_size
2024-07-20 14:48:03 +03:00
self.predict = predict
def __len__(self):
2024-07-20 14:48:03 +03:00
return self.steps
2024-07-20 14:48:03 +03:00
def __getitem__(self, batch_no):
x = self.sparse_x[batch_no * self.batch_size: batch_no * self.batch_size + self.batch_size]
if not self.predict:
y = self.y[batch_no * self.batch_size: batch_no * self.batch_size + self.batch_size]
return tf.convert_to_tensor(x.todense()), tf.convert_to_tensor(y)
else:
return tf.convert_to_tensor(x.todense()),
def on_epoch_end(self):
2024-07-20 14:48:03 +03:00
if not self.predict:
self.sparse_x, self.y = shuffle(self.sparse_x, self.y)
from tensorflow.keras import Sequential
2024-07-20 14:48:03 +03:00
from tensorflow.keras.layers import Input, Dense
model = Sequential(name='IMDB')
2024-07-20 14:48:03 +03:00
model.add(Input((training_dataset_x.shape[1], )))
model.add(Dense(128, activation='relu', 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'])
2024-07-20 14:48:03 +03:00
hist = model.fit(DataGenerator(training_dataset_x, training_dataset_y, training_steps, BATCH_SIZE),
validation_data = DataGenerator(training_dataset_x, training_dataset_y, validation_steps, BATCH_SIZE), epochs=EPOCHS)
import matplotlib.pyplot as plt
2024-07-20 14:48:03 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['loss'])
plt.legend(['Loss'])
plt.show()
2024-07-20 14:48:03 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
2024-07-20 14:48:03 +03:00
eval_result = model.evaluate(DataGenerator(test_dataset_x, test_dataset_y, test_steps, BATCH_SIZE))
2024-07-20 14:48:03 +03:00
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
df_predict = pd.read_csv('predict-imdb.csv')
predict_dataset_x = cv.transform(df_predict['review'])
predict_steps = math.ceil(predict_dataset_x.shape[0] / BATCH_SIZE)
predict_result = model.predict(DataGenerator(predict_dataset_x, None, predict_steps, BATCH_SIZE, True))
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda da belirttiğimiz gibi daha sonraları Tensorflow kütüphanesindeki Input katmanına sparse parametresi de eklenmiştir.
Böylece biz artık doğrudan Input katmanına seyrek matrisin kendisini verebilmekteyiz. Geri kalan işlemleri Keras kendisi
halletmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
EPOCHS = 5
BATCH_SIZE = 32
df = pd.read_csv('IMDB Dataset.csv').iloc[:1000, :]
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(dtype='uint8')
dataset_x = cv.fit_transform(df['review'])
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
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='IMDB')
model.add(Input((training_dataset_x.shape[1],), sparse=True))
model.add(Dense(128, activation='relu', 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=BATCH_SIZE, epochs=EPOCHS, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['loss'])
2024-07-20 14:48:03 +03:00
plt.legend(['Loss'])
plt.show()
2024-07-20 14:48:03 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
2024-07-20 14:48:03 +03:00
plt.legend(['Accuracy', 'Validation Accuracy'])
plt.show()
2024-07-20 14:48:03 +03:00
eval_result = model.evaluate(test_dataset_x, test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
2024-07-20 14:48:03 +03:00
# prediction
df_predict = pd.read_csv('predict-imdb.csv')
predict_dataset_x = cv.transform(df_predict['review'])
predict_result = model.predict(predict_dataset_x)
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
52. Ders - 21/07/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Resimlerle ilgili işlemler yapabilmek için öncelikle resimlerin sayısal biçimde nasıl oluşturulduğu ve görüntülendiği
hakkında temel bazı bilgilerin edinilmiş olması gerekir. Bilgisayar ekranlarında tüm görüntü aslında pixel'lerden oluşmaktadır.
Pixel ("picture element" sözcüklerinden uydurulmuştur) ekranda görüntülenebilen en küçük noktasal öğedir. Yani ekrandaki tüm
görüntü aslında pixel'lerin bir araya getirilmesiyle oluşturulmaktadır. Bilgisayar ekranını bir pixel matrisi olarak düşünebiliriz.
Örneğin ekranımızın çözünürlüğü 1920x1080 ise (ilk yazılan sütun ikinci yazılan satır) bu ekranımızın her satırında 1920 tane
pixel olduğu ve toplam 1080 tane satırın bulunduğu anlamına gelmektedir. (Yani bu durumda ekranımızda 1920 * 1080 = 2073600
pixel vardır.) Ekrandaki her pixel programlama yoluyla diğerlerinden bağımsız bir biçimde renklendirilebilmektedir. Eskiden
teknoloji zayıfken bilgisayar ekranlarının çözünürlükleri de çok düşüktü. Çünkü o zamanlar pixel'leri oluşturan ve renklendiren
grafik kartları gelişkin değildi. Günümüzde grafik kartları çok gelişmiştir dolayısıyla çözünürlükler de geçmişe göre çok
daha yüksektir.
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
oluşturulmaktadır. Yani belli bir renk kırmızının 0-255 arasındaki bir değeri, yeşilin 0-255 arasındaki bir değeri ve mavinin
0-255 arasındaki bir değeri ile oluşturulmaktadır. Örneğin R=255, G=0, B=0 ise bu tam kırmızı olan renktir. Kırmızı ile
yeşil ışınsal olarak bir araya getirilirse sarı renk elde edilmektedir. Bu biçimde bütün renkler aslında bu üç ana rengin
tonal birleşimleriyle elde edilmektedir. (Burada bir uyarıda bulunmak istiyoruz: Işıkların girişimiyle elde edilen renklerle
boyaların karıştırılması ile elde edilen renkler aynı değildir. Çünkü bunlar farklı fiziksel etkileşimlere girmektedir.) Yani
örneğin Biz kırmızı ışık ile yeşil ışığı giriştirirsek sarı ışık elde ederiz. Ancak kırmızı boya ile yeşil boyayı karıştırırsak
elde ettiğimiz renk sarı olmaz.) Pekiyi böyle bir sistemde bir pixel kaç farklı renge boyaaabilir? R, G ve B için toplam 256
farklı değer olduğuna göre elde edilebilecek toplam renk sayısı 256 * 256 * 256'dır. Bunu 2 ** 8 * 2 ** 8 * 2 ** 8 biçiminde
de ifade edebiliriz. 2 ** 24 değeri yaklaşık 16 milyon (16777216) civarındadır. Her ne kadar doğada bu 16 milyon rengin dışında
renkler borsa da insanın donanımsal özelliği nedeniyle algılayabildiği renklerin de bir sınırı vardır. Yani örneğin pixel
renklerinin milyar düzeyine çıkarılması bizim daha iyi bir görüntü algılamamıza yol açmayacaktır.
Ekranda iki pixel arasında bir doğru çizdiğimizde bu doğru kırıklı gibi görünebilir. Bunun nedeni doğrunun sınırlı sayıda
pixel ile oluşturulmasıdır. Kartezyen koordinat sisteminde sonsuz tane nokta vardır. Yani çözünürlük sonsuzdur. Ancak ekran
koordinat sisteminde sınırlı sayıda pixel vardır. Bu nedenle ekranda doğru gibi, daire gibi en temel geometrik şekiller bile
kırıklı gözükebilmektedir. Şüphesiz çözünürlük artırıldığı zaman bu kırıklı görünüm azalacaktır. Ancak çözünürlüğün çok
artırılması da başka dezavantajlar doğurabilmektedir. Örneğin notebook'larda ve mobil cihazlarda CPU dışındaki en önemli
güç tüketimi LCD ekranda oluşmaktadır. Çözünürlük artırıldıkça ekran kartları ve LCD birimleri daha fazla güç harcar hale
gelmektedir.
Pekiyi ekran boyutunu sabit tutup çözünürlüğü küçültürsek ne olur? Bu durumda pixel'ler büyür ve görüntü daha büyür ancak
netlik azalır. Çözünürlüğü sabit tutup ekranımızı büyütürsek de benzer durum oluşacaktır. O halde göz için belli bir büyüklükte
ekran ve çözünürlük daha önemlidir. İşte buna DPI (Dot Per Inch) denilmektedir. DPI bir inch'te kaç pixel olduğunu belirtmektedir.
Çözünürlük sabit tutulup ekran büyütülürse DPI düşer, ekran küçültülürse DPI yükselir. Bugün kullandığımız akıllı cep
telefonlarında DPI oldukça yüksektir. Buna "retinal çözünürlük" de denilmektedir. Gözümüzün de donanımsal (fizyolojik) bir çöznürlüğü vardır.
Belli bir DPI'dan daha yüksek çözünürlük sağlamanın bizim için bir faydası kalmamaktadır.
Bilgisayar bilimlerinin sınırlı sayıda pixelle geometrik şekillerin ve resimlerin nasıl oluşturulduğunu inceleyen ve bunlar
üzerinde işlemlerin nasıl yapılabileceğini araştıran bölümüne İngilizce "computer graphics" denilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Bilgisayar ortamındaki en doğal resim formatları "bitmap (ya da raster)" formatlardır. Örneğin BMP, GIF, TIF, PNG gibi
formatlar bitmap formatlardır. Bitmap dosya formatlarında 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ülemektir. Ancak bitmap formatlar
çok yer kaplama eğilimindedir. Örneğin 100x100 pixellik 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 bitmap format üzerinde bir çeşit kayıplı
sıkıştırmanın uygulandığı bir formattır. Yani bir BMP dosyasını JPG dosyasına dönüştürdüğümüzde resim çok bozulmaz. Ama
aslında biraz bozulur. Sonra onu yeniden BMP dosyasına dönüştürdüğümüzde orijinal resmi elde edemeyiz. Ancak JPEG gibi
farmatlar resimleri çok az bozup çok iyi sıkıştırabilmektedir. O halde aslında doğal formatlar BMP formatı ve benzerleridir.
2024-08-22 19:31:50 +03:00
JPEG gibi formatlar doğal formatlar değildir. Sıkıştırılmış formatlardır. Bitmap formatlardaki resimler orijinal boyutuyla
görüntülenmeidir. Çünkü bu resimlerin büyütülüp küçültülmesinde (scale edilmesinde) görüntü bozulabilmektedir.
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ş gibi bir etki yaratılabilmektedir. Bu etki aslında ön plandaki pixel ile arka plandaki pixel'in
bit operasyonuna sokulmasıyla sağlanmaktadır. Bu operasyon bugünkü grafik kartlarında grafik kartının kendisi tarafından
yapılmaktadır. Ancak bu saydamlılık (transparency) özelliğinin de derecesi söz konusu olmaktadır. Programcı bu saydamlılık
derecesini grafik kartına RGB değerleriyle birlikte birlikte verebilmektedir. RGB değerlerinin yanı sıra saydamlılılık
belirten bu değere "alpha channel" denilmetedir.
Bazı bitmap formatlar pixel renklerinin yanı sıra her pixel için saydamlılık bilgilerini de tutmaktadır. Böylece dikdörtgensel
resim başka bir resmin üztüne basıldığında ön plan resmin bazı kısımlarının görüntülenmesi engellenebilmektedir. Örneğin
PNG formatı bu biçimde transparanlık bilgisi de tutulmaktadır. Ancak örneğin BMP formatında böyle bir transparanlık
bilgisi tutulmamaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Siyah beyaz demek her pixel'in yalnızca siyah ya da beyaz olabildiği resim demektir. Böyle bir resimde bir pixel bir bit
ile ifade edilebilir. Ancak siyah beyaz resimlerde resim bir siluet gibi gözükmektedir. Gri tonlamalı (gray scale) resimlerde
ise her pixel siyahın (grinin) bir tonu biçiminde renkledirilmektedir. Eski siyah-beyaz fotoğraflar aslında gri tonlamalı
fotoğraflardır. Gri tonlamalı resimlerde aslında her pixelin RGB renkleri aynıdır. Yani R = G = B biçimindedir. Gri tonlamalı
resimlerde grinin 256 tonu görüntülenebilmektedir. Dolayısıyla gri tonlamalı bir resimde her pixel bir byte ile ifade
edilebilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
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. Söz konusu resim renkli ir resim olduğu için elde edilen dizinin de shape
demeti (100, 1500, 3) biçimindedir. Yani söz konusu resim 1000x1500 pixel'lik bir resimdir, ancak resmin her pixel'i RGB
değerlerinden oluşmaktadır. Biz buarada image_data[i, j] biçiminde matrisin bir elemanına erişmek istersek aslında resmin
i'inci satır j'inci sütunundaki pixel'in RGB renklerini bir NumPy dizisi olarak elde ederiz.
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. Örneğin:
image_data = plt.imread('AbbeyRoad.jpg')
plt.imshow(image_data)
plt.show()
Matplotlib bir resim üzerinde ondan parça almak, onu büyütmek, 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. Bir resmin pixel'leri üzerinde aşağı seviyeli çalışma yapmak için Matplotlib
ve NumPy iyi araçlardır. Örneğin:
image_data = plt.imread('AbbeyRoad.jpg')
plt.imshow(image_data)
plt.show()
result_image = np.flip(image_data, axis=1)
plt.imshow(result_image)
plt.show()
Burada pixel verileri yatay eksende döndürülmüştür.
#----------------------------------------------------------------------------------------------------------------------------
image_data = plt.imread('AbbeyRoad.jpg')
plt.imshow(image_data)
plt.show()
result_image = np.flip(image_data, axis=1)
plt.imshow(result_image)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
NumPy'da rot90 fonksiyonu resmim pixellerini 90 derece döndürmektedir. Fonksiyon resmin pixel verileriyle saat yönününün
tersinde kaç defa 90 derece döndürüleceğini (default değeri 1) bizden istemektedir. Örneğin:
mage_data = plt.imread('AbbeyRoad.jpg')
plt.imshow(image_data)
plt.show()
result_image = np.rot90(image_data, 3)
plt.imshow(result_image)
plt.show()
Burada resim 3 kere 90 derece döndürülmüştür.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Renkli resimleri imread fonksiyonu ile okuduğumuzda "row x col x 3" boyutunda bir matris elde ederiz. Gri tonlamalı resimleri
aynı fonksiyonla okuduğumuzda ise row x col x 1 boyutunda bir matris elde ederiz. Tabii aslında "row x col" biçiminde iki
boyutlu bir matrisin eleman sayısı ile "row x col x 1" biçiminde üç boyutlu bir matrisin eleman sayısı arasında bir farklılık
yoktur. Bazen gri tonlamalı resimler "row x col x 1" yerine "row x col" biçiminde de karşımıza çıkabilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
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 parametresini kullanarak sağlayabiliriz.
Örneğin:
import numpy as np
import matplotlib.pyplot as plt
image_data = plt.imread('AbbeyRoad.jpg')
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 uygulanır. 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
resim gri tonlamalı hale getirilmiştir. (NumPy'ın mean fonksiyonunda ağırlıklandırma parametresi yoktur. Ancak average
isimli fonksiyon ağırlıklı ortalama için kullanılabilmektedir.)
#----------------------------------------------------------------------------------------------------------------------------
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()
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
53. Ders - 27/07/2024 - Cumartesi -
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Resim tanıma üzerinde en sık kullanılan popüler bir veri kümelerinden biri 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 zip'lenmiş CSV dosyaları biçiminde aşağıdaki bağlantıdan indirebilrsiniz:
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
2024-08-22 19:31:50 +03:00
edildiğinde %97 civarında bir başarı elde edilmektedir. Daha sonra 28x28'lik kendi oluşturduğumuz bitmap resimlerle 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" özelliği bulunmaktadır. Biz de Microsoft Paint ile fırça kullanarak anti-aliasing eşliğinde
kestirilecek resimleri oluşturduk. Pixel verileri eğitime sokulmadan önce min-max ölçeklemesi de 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.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
2024-08-22 19:31:50 +03:00
training_df = pd.read_csv('mnist_train.csv')
test_df = pd.read_csv('mnist_test.csv')
training_dataset_x = training_df.iloc[:, 1:].to_numpy(dtype='uint8')
training_dataset_y = training_df.iloc[:, 0].to_numpy(dtype='uint8')
test_dataset_x = test_df.iloc[:, 1:].to_numpy(dtype='uint8')
test_dataset_y = test_df.iloc[:, 0].to_numpy(dtype='uint8')
# one hot encoding for y data
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)
2024-08-22 19:31:50 +03:00
# minmax scaling
2024-08-22 19:31:50 +03:00
scaled_training_dataset_x = training_dataset_x / 255
scaled_test_dataset_x = test_dataset_x / 255
import matplotlib.pyplot as plt
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(5, 13))
for i in range(50):
plt.subplot(10, 5, i + 1)
2024-08-22 19:31:50 +03:00
plt.title(str(training_dataset_y[i]), fontsize=12, fontweight='bold')
picture = training_dataset_x[i].reshape(28, 28)
plt.imshow(picture, cmap='gray')
plt.show()
2024-08-22 19:31:50 +03:00
"""
seven_x = training_dataset_x[training_dataset_y == 7]
for i in range(50):
plt.figure(figsize=(1, 1))
# plt.title(str(training_dataset_y[i]), fontsize=12, fontweight='bold')
picture = seven_x[i].reshape(28, 28)
plt.imshow(picture, cmap='gray')
plt.show()
"""
2024-08-22 19:31:50 +03:00
"""
for i in range(50):
plt.figure(figsize=(1, 1))
plt.title(str(training_dataset_y[i]), fontsize=12, fontweight='bold')
picture = training_dataset_x[i].reshape(28, 28)
plt.imshow(picture, cmap='gray')
plt.show()
"""
2024-08-22 19:31:50 +03:00
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Input
2024-08-22 19:31:50 +03:00
model = Sequential(name='MNIST')
model.add(Input((training_dataset_x.shape[1], ), name='Input'))
model.add(Dense(128, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
2024-08-22 19:31:50 +03:00
model.compile('rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(scaled_training_dataset_x, ohe_training_dataset_y, batch_size=32, epochs=20, validation_split=0.2)
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=10)
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(14, 6))
plt.title('Categorcal Accuracy - Validation Categorical Accuracy', fontsize=14, pad=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()
2024-08-22 19:31:50 +03:00
eval_result = model.evaluate(scaled_test_dataset_x , ohe_test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
2024-08-22 19:31:50 +03:00
# prediction
import numpy as np
2024-08-22 19:31:50 +03:00
import os
import glob
2024-08-22 19:31:50 +03:00
for path in glob.glob('Predict-Pictures/*.bmp'):
image = plt.imread(path)
gray_scaled_image = np.average(image, axis=2, weights=[0.3, 0.59, 0.11]).reshape(1, 28 * 28)
gray_scaled_image /= 255
model_result = model.predict(gray_scaled_image, verbose=0)
predict_result = np.argmax(model_result)
print(f'Real Number: {os.path.basename(path)[0]}, Prdicted Result: {predict_result}, Path: {path}')
#----------------------------------------------------------------------------------------------------------------------------
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
2024-08-22 19:31:50 +03:00
iki boyutlu matrise dönüştürmemiz gerekir. Aşağıda aynı işlemler doğrudan Keras'ın içerisindeki mnist veri kümesi kullanılarak
yapılmıştı
#----------------------------------------------------------------------------------------------------------------------------
from tensorflow.keras.datasets import mnist
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = mnist.load_data()
2024-08-22 19:31:50 +03:00
training_dataset_x = training_dataset_x.reshape(-1, 28 * 28)
test_dataset_x = test_dataset_x.reshape(-1, 28 * 28)
2024-08-22 19:31:50 +03:00
# one hot encoding for y data
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)
2024-08-22 19:31:50 +03:00
# minmax scaling
2024-08-22 19:31:50 +03:00
scaled_training_dataset_x = training_dataset_x / 255
scaled_test_dataset_x = test_dataset_x / 255
2024-08-22 19:31:50 +03:00
import matplotlib.pyplot as plt
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(5, 13))
for i in range(50):
plt.subplot(10, 5, i + 1)
plt.title(str(training_dataset_y[i]), fontsize=12, fontweight='bold')
picture = training_dataset_x[i].reshape(28, 28)
plt.imshow(picture, cmap='gray')
plt.show()
2024-08-22 19:31:50 +03:00
"""
seven_x = training_dataset_x[training_dataset_y == 7]
for i in range(50):
plt.figure(figsize=(1, 1))
# plt.title(str(training_dataset_y[i]), fontsize=12, fontweight='bold')
picture = seven_x[i].reshape(28, 28)
plt.imshow(picture, cmap='gray')
plt.show()
"""
2024-08-22 19:31:50 +03:00
"""
for i in range(50):
plt.figure(figsize=(1, 1))
plt.title(str(training_dataset_y[i]), fontsize=12, fontweight='bold')
picture = training_dataset_x[i].reshape(28, 28)
plt.imshow(picture, cmap='gray')
plt.show()
"""
2024-08-22 19:31:50 +03:00
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Input
model = Sequential(name='MNIST')
model.add(Input((training_dataset_x.shape[1], ), name='Input'))
model.add(Dense(128, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
2024-08-22 19:31:50 +03:00
model.compile('rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(scaled_training_dataset_x, ohe_training_dataset_y, batch_size=32, epochs=20, validation_split=0.2)
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=10)
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(14, 6))
plt.title('Categorcal Accuracy - Validation Categorical Accuracy', fontsize=14, pad=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()
2024-08-22 19:31:50 +03:00
eval_result = model.evaluate(scaled_test_dataset_x , ohe_test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
2024-08-22 19:31:50 +03:00
# prediction
import numpy as np
2024-08-22 19:31:50 +03:00
import os
import glob
2024-08-22 19:31:50 +03:00
for path in glob.glob('Predict-Pictures/*.bmp'):
image = plt.imread(path)
gray_scaled_image = np.average(image, axis=2, weights=[0.3, 0.59, 0.11]).reshape(1, 28 * 28)
gray_scaled_image /= 255
model_result = model.predict(gray_scaled_image, verbose=0)
predict_result = np.argmax(model_result)
print(f'Real Number: {os.path.basename(path)[0]}, Prdicted Result: {predict_result}, Path: {path}')
2024-08-22 19:31:50 +03:00
#----------------------------------------------------------------------------------------------------------------------------
54. Ders - 28/07/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
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şlemleri ile oluşturulan yapay sinir ağlarına "Evrişimsel Sinir Ağları (Convolutional Neural
2024-08-22 19:31:50 +03:00
Network)" denilmektedir ve İngilizce CNN biçiminde kısaltılmaktadır.
Resimlerde evrişim işlemi pixel'lerin birbirleri ile ilişkili hale gelmesini sağlamaktadır. Evrişim sayesinde pixel'ler
bağımsız birbirinden kopuk durumdan çıkıp komşu pixellerle ilişkili hale gelir. Aynı zamanda evrişim bir filtreleme
2024-08-22 19:31:50 +03:00
etkisi oluşturmaktadır. Görüntü işlemede filtreleme işlemleri evrişimlerle sağlanmaktadır.
Bir resmin 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
2024-08-22 19:31:50 +03:00
işlemine sokulur. (Yani kernel'ın asıl resimle çakıştırıldığı pixel değerleri birbiriyle çarpılıp toplanır.)
Buradan bir değer elde edilir. Bu değer yeni resmin kernel ile çakıştırılan orta noktasındaki pixel'i olur. 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 aşağıdaki gibi 5x5'lik gri tonlamalı bir resim söz konusu 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
2024-08-22 19:31:50 +03:00
Kullandığımız kernel da aşağıdaki gibi 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ğıdaki gibi bir C matrisi (resmi) elde ederiz:
c11 c12 c13
c21 c22 c23
c31 c32 c33
2024-08-22 19:31:50 +03:00
Eğer işlemler yukarı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
2024-08-22 19:31:50 +03:00
Örneğin ana resim 5X5'lik ve kernel'da 3X3'lik ise evrişim işleminin sonucunda elde edilecek resim 3X3'lük olacaktır.
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
2024-08-22 19:31:50 +03:00
asıl resmin soluna, sağına, yukarısına ve aşağısına eklemeler yaparız. Bu eklemelere "padding" denilmektedir. Bu eklemelere
İngilizce "padding" denilmektedir. Hedef resmin asıl resimle aynı büyüklükte olması için asıl resme (kernel genişliği ya da yükseliği - 1)
kadar padding uygulanmalıdır.
2024-08-22 19:31:50 +03:00
Toplam Padding Genişliği = Kernel Genişliği - 1
Toplam Padding Yüksekliği = Kernel Yüksekliği - 1
2024-08-22 19:31:50 +03:00
Tabii bu toplam pading genişliği ve yüksekliği iki yöne eşit bir biçimde yaydırılmalıdır. Yani başka bir deyişle asıl resim
evrişim işlemine sokulmadan önce dört taraftan büyütülmelidir. Ö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
2024-08-22 19:31:50 +03:00
Asl resmin padding'li hali şu biçimde görünecektir:
2024-08-22 19:31:50 +03:00
pad pad pad pad pad pad pad
pad a11 a12 a13 a14 a15 pad
pad a21 a22 a23 a24 a25 pad
pad a31 a32 a33 a34 a35 pad
pad a41 a42 a43 a44 a45 pad
pad a51 a52 a53 a54 a55 pad
pad pad pad pad pad pad pad
2024-08-22 19:31:50 +03:00
Pekiyi padding'ler asıl 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 ise ilk ve son n tane satır ya da sütunu tekrarlamaktır. Genellikle bu ikinci yöntem
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
2024-08-22 19:31:50 +03:00
farklı bir hale gelmektedir. Görüntü işlemede resmin bazı yönlerini açığa çıkartmak için amaca uygun çeşitli filtreler
kullanılabilmektedir. Ö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 görüntü işleme" ile ilgildir. Detayları
çeşitli kaynaklardan edinilebilir.
Evirişim işlemini padding uygulamadan yapan basit bir fonksiyonu şöyle yazabiliriz:
2024-08-22 19:31:50 +03:00
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
Evrişim işleminin bu biçimde uygulanması yavaş bir yöntemdir. Bu tür işlemlerde mümkün olduğunca NumPy içerisindeki
fonksiyonlardan faydalanılmalıdır. Çünkü NumPy'ın fonksiyonlarının önemli bir bölümü C'de yazılmıştır ve manuel Python
kodlarına göre çok daha hızlı çalışmaktadır.
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. Görüntü işlemede blur filtresi resmi bulanıklaştırmakta, sobel filtresi ise nesnelerin sınır çizgilerini belirgin
hale getirmekte kullanılmaktadır. Blur filtrelemesinde eğer resminizin pixel boyutları büyükse kernel matrisi daha büyük
2024-08-22 19:31:50 +03:00
tutmalısınız.
#----------------------------------------------------------------------------------------------------------------------------
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 filtreleme örneğinin tersi
2024-08-22 19:31:50 +03:00
olacak biçimde yürütülmektedir. 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 ağın bulmasını sağlarız.
Yani ağ yalnızca filtreyi uygulamaz bizzat filtrenin kendisini de bulmaya çalışır. Ancak resmin yalnızca filtreden geçirilmesi
yeterli değildir. Resim filtreden geçirildikten sonra yine Dense katmanlara sokulur. Yani filtreleme genellikle ön katmanlarda
yapılan bir işlemdir. Filtreleme katmanlarından 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 ağırlık değerleriyle çarpılıp toplanması işlemi ile aynıdır. Örneğin:
2024-08-22 19:31:50 +03:00
a11 a12 a13 a14 a15 a16
a21 a22 a23 a24 a25 a26
a31 a32 a33 a34 a35 a36
a41 a42 a43 a44 a45 a46
a51 a52 a53 a54 a55 a56
a61 a62 a63 a64 a65 a66
2024-08-22 19:31:50 +03:00
Burada aşağıdaki gibi bir kernel 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
2024-08-22 19:31:50 +03:00
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. Bu örnekte hedef resim 4x4'lüktür. Bu durumda
katmandaki nöron sayısı da 4 * 4 = 16 tane olacaktır. Bu 16 nöronun sonraki katmana girdi olarak verileceğine dikkat ediniz.
Pekiyi bu katmandaki 16 nöronun her birine kaç tane xi değeri uygulanmaktadır? Tabii ki kernel boyutu kadar. Yani örneğimizde
3 * 3 = 9 tane. O halde bu katmanda toplam 16 nöron vardır ve her bir nörona 9 girdi uygulanmaktadır. Fakat burada dikkat
edilmesi gereken nokta bu 16 nörona uygulanan girdilerin aslında 3 * 3 = 9 ağırlık değeri ile işleme sokulmasıdır. Yani
Dense katmanlarda olduğu gibi her xi için farklı bir wi ağırlık değeri yoktur. Bu katmanda her nöronda aynı 9 tane ağırlık
değeri kullanılmaktadır. Pekiyi bu örneğimizde toplam eğitilebilir parametrelerin (trainable parameters) sayısı kaç tanedir?
İşte 3 * 3 = 9 tane w değerinin konumlandırılması gerekmektedir. Ancak 1 tane de bias değeri dot product işleminin sonucunda
toplama işlemine sokulacaktır. Fakat burada her nöron için farklı bir bias değeri kullanılmamaktadır. Toplamda hep aynı w
değerleri dot product işlemine sokulduğu için toplamda tek bir bias değeri söz konusu olmaktadır. Bu durumda örneğimizde
eğitilebilir parametrelerin sayısı 3 * 3 + 1 = 10 tane olacaktır. Tabii pixel'lerle kernel matris değerlerinin dot product
işlemine sokulup bias değeriyle toplanmasıyla elde edilen değer yine diğer katmanlarda olduğu gibi aktivasyon fonksiyonuna
sokulmaktadır.
Şimdi yukarıdaki açıklamaları somut bir örnek üzerinde gözden geçirelim. Aşağıdaki gibi 3x3'lük gri tonlamalı bir resim olsun:
x11 x12 x13
x21 x22 x23
x31 x32 x33
Biz de 2x2'lik aşağıdaki kernel'ı evrişimde uygulayalım:
2024-08-22 19:31:50 +03:00
w11 w12
w21 w22
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:
2024-08-22 19:31:50 +03:00
activation(x11 * w11 + x12 * w12 + x21 * w21 + x22 * w22 + b) --->
activation(x12 * w11 + x13 * w12 + x22 * w21 + x23 * w22 + b) --->
activation(x21 * w11 + x22 * w12 + x31 * w21 + x32 * w22 + b) --->
activation(x22 * w11 + x23 * w12 + x32 * w21 + x33 * w22 + b) --->
Burada toplam 4 nöron çıktısı vardır. Bu nöronların hepsinin bias değerleri aynıdır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
55. Ders - 03/08/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
Biz yukarıda evrişim işleminin gri tonlamalı resimlerde nasıl yapıldığınııkladık. 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ğerleri olur. Ancak sinir ağlarında
genel olarak 3 farklı kernel her bir kanala uygulandıktan sonra elde edilen değerler toplanarak teke düşürülmektedir. 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 bu işlemde kaç tane bias değeri kullanılacaktır? Her kanal (channel) için ayrı bir bias değeri
kullanılmamaktadır. Bias değeri bu kanallardan evrişim sonucunda elde edilen üç değerin toplanmasından sonra işleme sokulmaktadır.
Dolayısıyla bias değeri yalnızca bir tane olacaktır.
Örneğin biz 10x10'luk bir RGB resme evrişim uygulamak isteyelim. Kullanacağımız filtre matrisi (kernel) 3x3'lük olsun.
Burada her kanal için ayrı bir 3x3'lük filtre matrisi kullanılacaktır. Bu durumda evrişim katmanında eğitilebilir
parametrelerin sayısı 3 * 3 * 3 + 1 = 28 tane olacaktır. Eğer biz bu örnekte padding kullanmıyorsak ve stride değeri de 1 ise
(yani kaydırma birer birer yapılıyorsa) bu durumda elde edilen hedef resim 8x8x1'lik olacaktır. Uygulanan evrişim sonucunda
resmin RGB olmaktan çıkıp adreta gray scale hale getirildiğine dikkat ediniz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
Aslında uygulamada resim tek bir filtreye de sokulmamaktadır. Birden fazla filteye sokulmaktadır. Örneğin biz bir resimde
3x3'lük 32 farklı filtre kullanabiliriz. Bu durumda 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 10x10'luk
resmi 3x3'lük filtre kullanarak padding uygulamadan 32 farklı filtreye sokmuş olalım. Biz bu resmi tek bir filtreye soktuğumuzda
8x8x1'lik bir hedef resim elde ediyorduk. İşte 32 farklı filtreye soktuumuzda her filtreden 8x8x1'lik bir resim elde edileceğine
göre toplamda 8x8x32'lik bir resim elde edilmiş olur.
Şimdi de 32 filtre kullandığımız durumda 10x10x3'lük RGB resim için eğitilebilir parametrelerin sayısını hesaplayalım.
Bir tane filtre için yukarıda toplam eğitilebilir parametrelerin sayısını 3 * 3 * 3 + 1 olarak hesaplamıştık. Bu filtrelerden
32 tane olduğuna göre toplam eğitilebilir parametrelerin sayısı 32 * (3 * 3 * 3 + 1) = 32 * 27 + 32 = 32 * 28 = 896 tane
olacaktır.
2024-08-22 19:31:50 +03:00
Pekiyi 10x10'luk resmimiz gri tonlamalı olsaydı 32 filtre ve 3x3'lük kernel için toplam eğitilebilir parametrelerin sayısı
ne olurdu? Bu durumda 3x3'lük toplam 32 farklı filtre kullanılacağı için ve her filtrede bir tane bias değeri söz konusu
olacağı için toplam eğitilebilir parametrelerin sayısı da 32 * (3 * 3 + 1) = 32 * 10 = 320 tane olacaktır.
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi evrişimsel sinir ağlarında tek bir evrişim katmanı mı bulunmalıdır? Aslında evrişim işlemi komşu pxelleri birbirleriyle
ilişkilendirmektedir. Yani onlara bir bağlam kazandırmaktadır. Evrişim işlemiyle pixel'ler birbirinden bağımsız değil
komşu pixel'lerle ilişkili hale gelmektedir. Evrişimin çıktısının yeniden evrişime sokulması pixel'lerin daha uzak pixel'lerle
ilişkilendirilmesini sağlar. İşte bu nedenle genel olarak evrişim katmanları birden fazla katman olarak bulundurulur. Bu da
ın derinleşmesine yol açmaktadır. Anımsanacağı gibi ara katmanların sayısı 2'den fazla ise böyle ağlara "derin ağlar (deep neural
network)" denilmektedir. Bu durumda ağa evrişim katmanlarını eklediğimizde artık derin ağlar yani derin öğrenme uygulaması
yapmış oluruz. Pek çok uygulamacı evrişim katmanlarındaki filtre sayısını önceki evrişimin iki katı olacak biçimde artırmaktadır.
Ancak uygulamacılar bu değerleri modelden modele kalbire edebilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de evrişimsel ağların Keras'ta nasıl oluşturulacağı üzerinde duralım. Keras'ta evrişimsel ağların oluşturulması için
tipik olarak Conv2D isimli bir sınıf kullanılmaktadır. Conv2D sınıfı resim girdisini iki boyutla bizden ister. Zaten 2D soneki
bu anlama gelmektedir. Aslında benzer işlemi yapan Conv1D isimli bir sınıf da vardır. Tabii resimsel uygulamalarda resimler
iki boyutlu olduğu için Conv2D katmanı kullanılmaktadır.
Conv2D sınıfının __init__ metodunun parametrik yapısı şöyledir:
2024-08-22 19:31:50 +03:00
tf.keras.layers.Conv2D(
filters,
kernel_size,
strides=(1, 1),
padding='valid',
data_format=None,
dilation_rate=(1, 1),
groups=1,
activation=None,
use_bias=True,
kernel_initializer='glorot_uniform',
bias_initializer='zeros',
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
**kwargs
)
Metodun 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:
2024-08-22 19:31:50 +03:00
conv2 = Conv2D(32, (3, 3), padding='same', activation='linear')
2024-08-22 19:31:50 +03:00
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. Padding yapıldığı durumda padding satırları ve sütunları 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 yatayda ve düşeyde
birer birer yapılacaktır. Evrişim katmanlarındaki aktivasyon fonksiyonları da Dense katmanlarda olduğu gibi genellikle "relu"
alınmaktadır. Eğer aktivasyon fonksiyonu hiç girilmezse sanki "linear" girilmiş gibi bir işlem söz konusu olur.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
Evrişim katmanlarından sonra modele genellikle yine Dense katmanlar eklenmektedir. Ancak Conv2D katmanın çıktısı çok boyutlu
olduğu için ve Dense katmanı da girdi olarak tek boyut istediği için Conv2D çıktısının Dense katmana verilmedne önce tek boyuta
indirgenmesi gerekmektedir. Çok boyutlu girdileri tek boyuta indirgemek için Keras'ta Flatten isimli bir katman bulundurulmuştur.
Örneğin:
model = Sequential(name='MNIST')
2024-08-22 19:31:50 +03:00
model.add(Input((28, 28, 1)))
model.add(Conv2D(32, (3, 3), 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()
2024-08-22 19:31:50 +03:00
Burada giri tonlamalı resim için tepik bir evirişim katmanının kullanım örneğini görüyorsunuz. Nodelin girdisi 28x28'lik
gri tonlamalı resimlerden oluşmaktadır. Sonra bu resimler üzerinde 3x3'lük filtreler uygulanmıştır. İlk Conv2D katmanında
32 filtre sonraki Conv2D katmanında ise 64 filtre kullanılmıştır. Daha sonra Flatten katmanıyla çok boyutlu çıktının tek
boyuta indirgendiğini görüyorsunuz. Aslında Flatten katmanı yerine genel Reshape katmanı da çok boyutlu verileri tek boyuta
dönüştürmek için eReshape((-1, )) biçiminde kullanılabilmektedir.
2024-08-22 19:31:50 +03:00
Bu modelden şöyle bir özet elde edilmiştir:
2024-08-22 19:31:50 +03:00
Model: "MNIST"
┌─────────────────────────────────┬────────────────────────┬───────────────┐
│ Layer (type) │ Output Shape │ Param # │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Conv2D-1 (Conv2D) │ (None, 26, 26, 32) │ 320 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Conv2D-2 (Conv2D) │ (None, 24, 24, 64) │ 18,496 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Flatten (Flatten) │ (None, 36864) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Hidden-1 (Dense) │ (None, 128) │ 4,718,720 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Hidden-2 (Dense) │ (None, 128) │ 16,512 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Output (Dense) │ (None, 10) │ 1,290 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 4,755,338 (18.14 MB)
Trainable params: 4,755,338 (18.14 MB)
Non-trainable params: 0 (0.00 B)
Burada birinci evrişim katmanında 32 filtre uygulandığı için bu evrişim katmanının çıktısı 26x26x32 tane nörondan oluşacaktı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ış
gibi olur. Birinci katmandaki eğitilebilir parametrelerin sayısı şöyle hesaplanmaktadır: Bu katmanda 3x3'lük kernel kullanılmıştır.
Toplamda 1 tane bias değeri evrişim sonucundaki değerle toplanacağından bir filtre için eğitilebilir parametrelerin sayısı
3 * 3 + 1 = 10 tane olacaktır. Bu katmanda 32 filte kullanıldığına göre bu katmandaki toplam eğitilebilir parametrelerin sayısı
(3 * 3 + 1) * 32 = 320 tane olacaktır. Yukarıda da belirtildiği gibi ikinci evrişim katmanının girdisi sanki 26x26'lık 32
kanallı resim gibidir. Buna evrişim uygularken her kanal için ayrı bir filtre matrisi kullanılır. Bu durumda 32 tane 3 x 3'lük
filtreye ihtiyaç duyulacaktır. En nihayetinde bu 32 filtereden elde edilen değer tek bias değeri ile toplanacağı için bir filtre
söz konusu olduğunda ikinci evrişim katmanındaki eğitilebilir parametrelerin sayısı 3 * 3 * 32 + 1 tane olacaktır. İkinci
evrişim katmanında 64 tane filtre kullanıldığına göre ikinci evirişim katmanındaki toplam eğitilebilir parametrelerin sayısı
64 * (3 * 3 * 32 + 1) = 18496 olur. İkinci evirişim katmanının çıktısının 24x24x64'lük bir matris olduğuna dikkat ediniz. Yani
adeta ikinci evirişm katmanı bize sanki 24x24'lük 64 kanallı resimler vermektedir. Yukarıda da belirttiğimiz gibi evrişim
katmanlarından sonra bu çok boyutlu çıktının tek boyuta indirgenmesi gerekir. Bunun için Flatten karmanı kullanılmıştır.
Flatten katmanında hiç eğitilebilir parametre yoktur. Flatten katmanının çıktısının 24 * 24 * 64 = 36864 nörondan oluştuğuna
dikkat ediniz. Artık bu nöronlar ilk Dense katmana girdi yapılacaktır. Bu durumda ilk Dense katmandaki eğitilebilir parametrelerin
sayısı 36864 * 128 + 128 = 4718720 tane olacaktır. Birinci Dense katmanın çıktısında 128 nöron vardır. Bu 128 nöron ikinci
Dense katmana girdi yapılmıştır. Dolayısıyla ikinci katmandaki eğitilebilir parametrelerin sayısı 128 * 128 + 128 = 16512
tane olacaktır. Nihayet son Dense katmana 128 nöron girip bu katmandan 10 nöron çıktısı elde edilecektir. Bu durumda bu
son katmandaki eğitilebilir parametrelerin sayısı 128 * 10 + 10 = 1290 olacaktır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda daha önce yapmış olduğumuz MNIST örneğini evrişimsel katmanlar kullanarak yeniden yapıyoruz.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
2024-08-22 19:31:50 +03:00
training_df = pd.read_csv('mnist_train.csv')
test_df = pd.read_csv('mnist_test.csv')
2024-08-22 19:31:50 +03:00
training_dataset_x = training_df.iloc[:, 1:].to_numpy(dtype='uint8')
training_dataset_y = training_df.iloc[:, 0].to_numpy(dtype='uint8')
2024-08-22 19:31:50 +03:00
test_dataset_x = test_df.iloc[:, 1:].to_numpy(dtype='uint8')
test_dataset_y = test_df.iloc[:, 0]
2024-08-22 19:31:50 +03:00
# one hot encoding for y data
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)
2024-08-22 19:31:50 +03:00
# reshape as 28x28x1
training_dataset_x = training_dataset_x.reshape(-1, 28, 28, 1)
test_dataset_x = test_dataset_x.reshape(-1, 28, 28, 1)
2024-08-22 19:31:50 +03:00
import matplotlib.pyplot as plt
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(5, 13))
for i in range(50):
plt.subplot(10, 5, i + 1)
plt.title(str(training_dataset_y[i]), fontsize=12, fontweight='bold')
picture = training_dataset_x[i]
plt.imshow(picture, cmap='gray')
plt.show()
# minmax scaling
scaled_training_dataset_x = training_dataset_x / 255
scaled_test_dataset_x = test_dataset_x / 255
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Conv2D, Flatten
model = Sequential(name='MNIST')
model.add(Input((28, 28, 1), name='Input'))
model.add(Conv2D(32, (3, 3), activation='relu', name='Conv2D-1'))
model.add(Conv2D(64, (3, 3), activation='relu', name='Conv2D-2'))
model.add(Flatten(name='Flatten'))
model.add(Dense(128, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
2024-08-22 19:31:50 +03:00
model.compile('rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(scaled_training_dataset_x, ohe_training_dataset_y, batch_size=32, epochs=20, validation_split=0.2)
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=10)
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(14, 6))
plt.title('Categorcal Accuracy - Validation Categorical Accuracy', fontsize=14, pad=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()
2024-08-22 19:31:50 +03:00
eval_result = model.evaluate(scaled_test_dataset_x , ohe_test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
2024-08-22 19:31:50 +03:00
# prediction
import numpy as np
2024-08-22 19:31:50 +03:00
import os
import glob
2024-08-22 19:31:50 +03:00
for path in glob.glob('Predict-Pictures/*.bmp'):
image = plt.imread(path)
gray_scaled_image = np.average(image, axis=2, weights=[0.3, 0.59, 0.11])
gray_scaled_image = gray_scaled_image.reshape(1, 28, 28, 1)
gray_scaled_image /= 255
model_result = model.predict(gray_scaled_image, verbose=0)
predict_result = np.argmax(model_result)
print(f'Real Number: {os.path.basename(path)[0]}, Prdicted Result: {predict_result}, Path: {path}')
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
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ı 4.5 milyon civarındadır. Üstelik bu örnekteki resimler 28x28'lik gri tonlamalı resimlerdir.
Pratikte 28x28 gibi resimler çok küçük olduğundan kullanılmazlar. Yani resimler pratikte 28x28'den çok daha büyük olma
eğilimindedir. 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.
2024-08-22 19:31:50 +03:00
- Eğitim sonucunda eğitim 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.
2024-08-22 19:31:50 +03:00
Bunun için ilk akla gelecek yöntem evrişim katmanlarındaki kaydırma değerlerini (strides) artırmaktır. Ancak kaydırma değerlerinin
artırılması resmin tanınması için dezavantaj da oluşturmaktadır. Nöron sayılarını azaltmak için diğer bir yöntem ise "pooling"
denilen yöntemdir. Bu bağlamda 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. (Tabii
aslında pooling yöntemi yalnızca resimsel verilerde kullanılmamaktadır. Ancak biz burada pooling işlemiin resimler üzerinde
uyguladığımız için pixel terimini kullanıyoruz.) Pooling işleminin İki önemli biçimi vardır: "Max Pooing" ve "Average Pooling".
"Max Pooling" yönteminde dikdörtgensel bölgedeki en büyük eleman alınır. Average Pooling yönteminde ise dikdörtgensel bölgedeki
elemanların ortalamaları alınmaktadır. Uygulamada daha çok Max Pooling yöntemi tercih edilmektedir. MaxPooling yöntemi ilgili
dikdörtgensel bölgedeki en belirgin özelliğin elde edilmesine yol açmaktadır. Örneğin 4x4'lük pixel'lerden oluşan gri tonlamalı
resimdeki pixel değerleri aşağıdaki gibi olsun:
112 62 41 52
200 15 217 21
58 92 81 117
0 21 45 89
2024-08-22 19:31:50 +03:00
Pooling uygulayacağımız ç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
2024-08-22 19:31:50 +03:00
Görüldüğü gibi pooling işleminde kaydırma (yani stride) genellikle 1 değil pooling çerçevesi kadar yapılmaktadır. İş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
2024-08-22 19:31:50 +03:00
Bu işlemin sonucunda elde edilen matrisin ilkinin karekökü kadar olduğuna dikkat ediniz. Yani pooling çerçevesi resmi
üstel olarak küçültmektedir.
2024-08-22 19:31:50 +03:00
Tabii pooling işlemleri üç boyutlu matrisler üzerinde de uygulanabilir. Örneğin evrişim katmanının çıktısı birden fazla
filtre kullanıldığı için genel olarak N kanallı resim gibidir. Bu durumda her kanal için ayrı ayrı pooling uygulanacaktır.
Örneğin 26x26x32'lik bir matris üzerinde 2x2 çerçeveli pooling işlemi yapıldığında hedef matris 13x13x32'lik olur.
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Resimsel verilerde pooling işlemleri Keras'ta tipik olarak MaxPooling2D ve AveragePooling2D sınıflarıyla temsil edilmiştir.
Sınıfların __init__ metotlarının parametrik yapıları şöyledir:
MaxPooling2D(pool_size=(2, 2), strides=None, padding='valid', data_format=None, **kwargs)
AveragePooling2D(pool_size=(2, 2), strides=None, padding='valid', data_format=None, **kwargs)
2024-08-22 19:31:50 +03:00
Metotların pool_size parametreleri çerçevenin büyüklüğünü belirtmektedir. Default olarak 2x2'lik çerçeve kullanılmaktadır.
2x2'lik çerçeve kullanımı tipiktir. Metotların strides parametreleri yine kaydırma miktarını belirtir. Default durumda
kaydırma pools_size parametresiyle aynı değerdedir. Yani bu parametreye None geçersek aslında stride değerinin pool_size
ile aynı olmaktadır. padding parametresi yine "valid" ya da "same" olabilir. "valid" padding yapılmayacağı, "same" ise padding
yapılacağı anlamına gelmektedir. Örneğin elimizdeki resim 5x5'lik olsun. Biz padding kullanmazsak çerçevimiz 2x2 ise toplamda
yatayda ve düşeyde 2 kaydırma yapabiliriz. Hedef resim de 2x2'lik olur. Ancak padding uygularsak artık 3 yatayda ve düşeyde
3 kaydırma yapabiliriz. Hedef resmizimde 3x3'lük olur. Yani padding işlemi sonda kalan artık alanların da kullanılmasına yol
açmaktadır.
2024-08-22 19:31:50 +03:00
Tipik olarak Pooling katmanları her evrişim katmanından sonra uyhulanmaktadır. Yani tipik olarak her Conv2D katmanından
sonra bir tane de MaxPooling2D ya da AveragePooling2D katmanı bulundurulur. Örneğin:
model = Sequential(name='MNIST')
model.add(Input((28, 28, 1), name='Input'))
model.add(Conv2D(32, (3, 3), activation='relu', name='Conv2D-1'))
model.add(MaxPooling2D(name='MaxPooling2D-1'))
model.add(Conv2D(64, (3, 3), activation='relu', name='Conv2D-2'))
model.add(MaxPooling2D(name='MaxPooling2D-2'))
model.add(Flatten(name='Flatten'))
model.add(Dense(128, activation='relu', name='Hidden-1'))
model.add(Dense(128, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
2024-08-22 19:31:50 +03:00
Modelden şöyle bir özet bilgi edilmiştir:
2024-08-22 19:31:50 +03:00
Model: "MNIST"
┌─────────────────────────────────┬────────────────────────┬───────────────┐
│ Layer (type) │ Output Shape │ Param # │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Conv2D-1 (Conv2D) │ (None, 26, 26, 32) │ 320 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ MaxPooling2D-1 (MaxPooling2D) │ (None, 13, 13, 32) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Conv2D-2 (Conv2D) │ (None, 11, 11, 64) │ 18,496 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ MaxPooling2D-2 (MaxPooling2D) │ (None, 5, 5, 64) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Flatten (Flatten) │ (None, 1600) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Hidden-1 (Dense) │ (None, 128) │ 204,928 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Hidden-2 (Dense) │ (None, 128) │ 16,512 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Output (Dense) │ (None, 10) │ 1,290 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 241,546 (943.54 KB)
Trainable params: 241,546 (943.54 KB)
Non-trainable params: 0 (0.00 B)
Burada katmanlardaki eğitilebilir parametrelerin sayısının ve katmanların çıktılarındaki toplam nöron sayısının nasıl
hesaplandığı üzerinde duralım:
- Burada yine birinci Conv2D katmanındaki eğitilebilir parametrelerin sayısı (3 * 3 + 1) * 32 = 320 olacaktır. Bu katmanın
çıktısı 26x26x32'lik bir nöron matrisidir. Birinci MaxPooling2D katmanının girdisi de bu biçimde olacaktır.
- Birinci MaxPoolin2D katmanında eğitilebilir parametre yoktur. Bu katmanın amacı zaten nöron sayılarını düşürmektir.
Pooling işlemindeki pencere genişliği 2x2 olduğu için bu katmanın çıktısı 13x13x32'lik bir nöron matrisidir.
- İkinci Conv2D katmanında yine (3 * 3 * 32 + 1) * 64 = 18496 tane eğitilebilir parametre vardır. Bu katmanın çıktısı da
11x11x64'lük bir nöron matrisidir (padding uygulanmadığına dikkat ediniz).
- İkinci MaxPooling2D katmanının giridisi 11x11x64 nöron matrisinden çıktısı ise 5x5x64'lük nöron matrisinden oluşmaktadır.
Tabiibu katmanda da eğitilebilir parameteler yoktur.
- Birinci Dense katmanın girdisi Flatten işleminden sonra artık 5 * 5 * 64 = 1600 nörondan oluşmaktadır. Bu katmanda 128
nöron vardır. Bu durumda birinci Dense katmandaki eğitilebilir parametrelerin sayısı 1600 * 128 + 128 = 204928 tanedir.
- İkinci Dense katmana 128 nöron girmekte ve bu katmandan 128 nöron çıkmaktadır. Bu durumda ikinci Dense katmandaki eğitilebilir
parametrelerin sayısı 128 * 128 + 128 = 16512 tane olacaktır.
- Çıktı katmanına giren nöron sayısı 128, çıktı nöron sayısı ise 10 tanedir. Bu durumda çıktı katmanındaki eğitilebilir
parametrelerin sayısı 128 * 10 + 10 = 1290 tane olacaktır.
Toplam eğitilebilir parametrelerin sayısı da 241546 tanedir. Bu değeri pooling işlemini uygulamadığımız örnekteki 4755338
değeri ile karşılaştırdığımızda uyguladığımız MaxPooling işleminin bu örnekte eğitilebilir parametrelerin sayısını 20
kat civarında düşürdüğü görülmektedir. Resimler büyüdükçe bu parametrelerin sayısının azaltılmasının etkisi çok daha iyi
anlaşılacaktır.
MNIST örneğinin pooling uygulanmış hali ile pooling uygulanmamış hali karşılaştırıldığında pooling uygulanmış halinin
her bakımdan biraz daha iyi performans gösterdiği görülmektedir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
56. Ders - 04/08/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
MNIST veri kümesi için evrişim ve pooling uygulanmış sinir ağı modeli aşağıda bir bütün olarak verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
EPOCHS = 5
training_df = pd.read_csv('mnist_train.csv')
test_df = pd.read_csv('mnist_test.csv')
training_dataset_x = training_df.iloc[:, 1:].to_numpy(dtype='uint8')
training_dataset_y = training_df.iloc[:, 0].to_numpy(dtype='uint8')
test_dataset_x = test_df.iloc[:, 1:].to_numpy(dtype='uint8')
test_dataset_y = test_df.iloc[:, 0]
# one hot encoding for y data
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)
# reshape as 28x28x1
training_dataset_x = training_dataset_x.reshape(-1, 28, 28, 1)
test_dataset_x = test_dataset_x.reshape(-1, 28, 28, 1)
import matplotlib.pyplot as plt
plt.figure(figsize=(5, 13))
for i in range(50):
plt.subplot(10, 5, i + 1)
plt.title(str(training_dataset_y[i]), fontsize=12, fontweight='bold')
picture = training_dataset_x[i]
plt.imshow(picture, cmap='gray')
plt.show()
# minmax scaling
scaled_training_dataset_x = training_dataset_x / 255
scaled_test_dataset_x = test_dataset_x / 255
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Conv2D, MaxPooling2D, Flatten
model = Sequential(name='MNIST')
model.add(Input((28, 28, 1), name='Input'))
model.add(Conv2D(32, (3, 3), activation='relu', name='Conv2D-1'))
model.add(MaxPooling2D(name='MaxPooling2D-1'))
model.add(Conv2D(64, (3, 3), activation='relu', name='Conv2D-2'))
model.add(MaxPooling2D(name='MaxPooling2D-2'))
model.add(Flatten(name='Flatten'))
model.add(Dense(128, 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('rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(scaled_training_dataset_x, ohe_training_dataset_y, batch_size=32, epochs=EPOCHS, validation_split=0.2)
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=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=(14, 6))
plt.title('Categorcal Accuracy - Validation Categorical Accuracy', fontsize=14, pad=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 , ohe_test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
# prediction
import numpy as np
import os
import glob
for path in glob.glob('Predict-Pictures/*.bmp'):
image = plt.imread(path)
gray_scaled_image = np.average(image, axis=2, weights=[0.3, 0.59, 0.11])
gray_scaled_image = gray_scaled_image.reshape(1, 28, 28, 1)
gray_scaled_image /= 255
model_result = model.predict(gray_scaled_image, verbose=0)
predict_result = np.argmax(model_result)
print(f'Real Number: {os.path.basename(path)[0]}, Prdicted Result: {predict_result}, Path: {path}')
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi toplamda pooling işleminin bize sağladığı katkı nedir? İşte eğitilebilir parametrelerin sayısı pooling işlemi
ile oldukça azaltılmaktadır. Bu da eğitimin daha hızlı gerçekleşmesini ve overfitting olgusunun daha az düzeye çekilmesini
sağlamaktadır. Eğitilebilir parametrelerin fazla olması ağdaki nöronların ağırlıklarının daha uzun eğitim sürecinde
konumlandırılmasına yol açar. Aynı zamanda yüksek sayıda parametre modelin yanlış şeyleri öğrenmesine (overfitting) zemin
hazırlamaktadır. Bu nedenle resim tanıma gibi işlemlerde uygulamacılar evirişim katmanından sonra hemen her zaman bir
pooling katmanı kullanırlar.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi MaxPooling işlemi ile AveragePooling işlemi arasında ne fark vardır? Hangi durumlarda hangisi tercih edilmelidir?
Aslında bu tür tercihlerin sezgisel yapılması iyi bir yöntem olmayabilir. Bu tür durumlarda daha çok deneme yöntemi uygulanmaktadır.
Genel olarak MaxPooling işleminin resim içeisindeki belirgin öğeleri daha iyi tespit ettiği söylenebilir. Bu da pek çok
resim tanıma işleminde fayda sağlamaktadır. AveragePooling ise pixel'lerin ortalamasını aldığı için daha pürüssüz ve daha
ortalama bilginin elde edilmesini sağlamaktadır. Ancak MaxPooling işlemi resimdeki belirgin özellikleri alırken diğer özellikleri
kaybetme eğilimindedir. MaxPoooling işleminin işlem maliyetinin AveragePooling işleminden daha az olduğuna da dikkat ediniz.
Uygulamada genellikle MaxPooling işlemi tercih edilmektedir. Ancak yukarıda da belittiğimiz gibi bu durum amaca bağlı olarak
değişebilir. Eğer mümkünse deneme yönteminin uygulanması önerilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
MaxPooling2D ve AveragePooling2D katmanlarının dışında bu işlemleri global düzeyde yapan GlobalAveragePooling2D ve
GlobalMaxPooling2D katman sınıfları da bulunmaktadır. Bu katman sınıfları ile yapılan pooling işlemlerinde toplamda tek
bir değer elde edilmektedir. Yani bu katmanlar girdi olarak aldığı tüm değerlerden tek bir değeri çıktı olarak vernmektedir.
Dolayısıyla bu sınıfların __init__ metotlarının parametrelerinde onlara verilecek bir şey yoktur:
tf.keras.layers.GlobalAveragePooling2D(
data_format=None, keepdims=False, **kwargs
)
tf.keras.layers.GlobalMaxPool2D(
data_format=None, keepdims=False, **kwargs
)
Tabii bu katman nesnelerinden önce Conv2D katmanı kulanılmışsa ve o katmanda N tane filtre belirtilmişse bu katmanın
çıktısının da 1 değil N olması gerekir. Uygulamada GLobalMaxPooling ve GlobalAvreagePooling işlemleri nihai olarak
tek bir değerin elde edilmesi gerektiğinde kullanılmaktadır. Tabii bu katmanlar evrişim katmanlarının en sonunda hemen
Dense katmanlardan önce uygulanmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi pooling çerçevesi hangi büyüklükte olmalıdır? Aslında bu da üzerinde çalıştığımız resimlerin büyüklüklerine ve
onların niteliklerine bağlı olarak değişebilir. Keras'ın default pooling çerçevesinin 2x2'lik olduğunu belirtmiştik.
Bu çerçevenin artırılması bazı uygulamalarda daha iyi sonuçların elde edilmesini sağlayabilmektedir. Genel olarak bu
çerçeve genişliğinin de üzerinde çalışılan veri kümesi eşliğinde deneme yoluyla belirlenmesi uygun olmaktadır. Fakat
eğer bu yönteme sapmayacaksanız 2x2'lik default çerçeve büyüklüğünü kullanabilirsiniz. Resimler büyüdükçe 2x2 yerine
3x3'lük ya da 4x4'lik çerçeveleri tercih edebilirsiniz. Çünkü büyük resimlerde eğitilebilir parametrelerin sayısı ciddi
boyuta gelebilmektedir. Büyük çerçeveler bunların daha fazla azaltılmasına katkı sağlayacaktır. Çerçeve büyütüldükçe
ayrıntıların daha fazla göz ardı edileceğine dikkat ediniz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Renkli resimlerin sınıflandırılması için sık kullanılan deneme veri kümelerinden biri CIFAR-10 (Canadian Institute for
Advanced Research) isimli veri kümesidir. Bu veri kümesi tensorflow.keras.datasets paketi içerisindeki cifar10 modülünde de
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ılmıştır. Sınıflar şunlardır:
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
2024-08-22 19:31:50 +03:00
Veri kümesinin orijinal https://www.cs.toronto.edu/~kriz/cifar.html adresinden indirilebilir. (Bu bağlantıya tıklandığında
veri kümesinin farklı programlama dilleri için farklı versiyonlarının bulunduğunu göreceksiniz. Burada Python'a ilşkin veri
kğmesini indirebilirsiniz.) Veri kümesinin CSV biçimi de https://www.kaggle.com/datasets/fedesoriano/cifar10-python-in-csv
bağlantısından indirilebilir. Veri kümesinin ilk bağlantıda bulunan orijinalinde 5 dosya eğitim verilerini, bir dosya da
test verilerini bulundurmaktadır. (Veri kümesini kullanıma sunanlar dosyalar çok büyümesin diye bunları 5 dosyaya bölmüş
olabilirler. Ya da verileri 5 dosyaya bölmelerinin nedeni az veriyle denemeler yapacak kişilerin küçük bir dosyayı kullanmalarını
sağlamak da olabilir.) Ancak bu dosyalar Python'un pickle modülü ile seri hale getirilmiştir. Bu dosyalar pickle.load ile
deserialize yapıldığında 5 tane anahtardan oluşan sözlük nesneleri elde edilmektedir. Sözlüğün 5 anahtarı şöyledir:
2024-08-22 19:31:50 +03:00
dict_keys([b'batch_label', b'labels', b'data', b'filenames'])
Bizim bu 5 eğitim dosyasındaki x ve y verilerini ayrı ayrı elde edip birleştirmemiz gerekir. Bu işlemi şöyle yapabiliriz:
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']
2024-08-22 19:31:50 +03:00
Buradan elde ettiğimiz matrisler iki boyutludur. Evrişim katmanları için bunların üç boyutlu hale getirilmesi gerekir.
Ancak resmin orijinalleri maalesef tek boyutlu hale getirilirken standrat bir eksen sistemi uygulanmamış aşağıdaki gibi
boyutlar uç uca eklenmiştir:
Tek boyutlu resmin 0'ınci boyutu ---> Gerçek resmin 2'üncü boyutu
Tek boyutlu resmin 1'inci boyutu ---> Gerçek resmin 0'ıncı boyutu
Tek boyutlu resmin 2'inci boyutu ---> Gerçek resmin 1'inci boyutu
Bu nedenle tek boyut olarak elde ettiğmiz resimlerin klasik RGB boyutlarına dönüştürülmesi için NumPy'ın transpose
fonksiyonundan faydalanılması gerekmektedir. Dönüştürme işlemi şöyle yapılabilir:
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])
Burada transpose işleminde 3 boyut değil 4 boyut kullanıldığına dikkat ediniz. Çünkü aslında matrisler resimlerden
oluşmaktadır. Bu işlemlerden sonra bir grup resmi fikir vermesi için aşağıdaki gibi çizdirebiliriz:
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
import matplotlib.pyplot as plt
plt.figure(figsize=(4, 20))
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()
Tabii yine resimler üzerinde minmax ölçeklemesinin yapılması uygundur:
training_dataset_x = training_dataset_x / 255
test_dataset_x = training_dataset_x / 255
Artık modelimizi kurup eğitebiliriz. Modeli MNIST örneğinde olduğu gibi oluşturulabiliriz. Ancak burada resim 3 kanallı
olduğu için ve biraz daha büyük olduğu için iki yerine üç evrişim katmanı kullanabiliriz. Filtre sayılarını da artırabiliriz.
Dense katmanlardaki nöronları da artırmak daha iyi sonucun elde edilmesine yol açabilecektir:
model = Sequential(name='CIFAR10')
model.add(Input((32, 32, 3), name='Input'))
model.add(Conv2D(32, (3, 3), activation='relu', name='Conv2D-1'))
model.add(MaxPooling2D(name='MaxPooling2D-1'))
model.add(Conv2D(64, (3, 3), activation='relu', name='Conv2D-2'))
model.add(MaxPooling2D(name='MaxPooling2D-2'))
model.add(Conv2D(128, (3, 3), activation='relu', name='Conv2D-3'))
model.add(MaxPooling2D(name='MaxPooling2D-3'))
model.add(Flatten(name='Flatten'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
Kestirim işlemini Internet'ten rastgele resimler bulup onları 32x32'lik boyuta getirerek yapabiliriz. Örneğimizde kestirilecek
resimler "Predict-Pictures" isimli bir dizinine yerleştirilmiştir. Bulunan resimlerin 32x32'lik boyuta ölçeklenmesi hazır
programlarla yapılabilir. (Microsoft'un Paint programı bunun biraz zahmetledir.) Resimler üzerinde bu türlü manipülasyonlar
yapmak için sık kullanılan kütüphanelerden biri "PIL (Python Image Library)" denilen kütüphanedir. Kütüphane aşağıdaki
gibi kurulabilir:
pip install pillow
Kütüphanenin dokümantasyonuna aşağıdaki bağlantıdan ulaşabilirsiniz:
https://pillow.readthedocs.io/en/stable/
PIL kütüphanesini kullanarak bir resmin ölçeklendirilip save edilmesi kabaca şöyle yapılmaktadır:
# rescale-image.py
from PIL import Image
import glob
for path in glob.glob('Predict-Pictures/*.*'):
image = Image.open(path)
resized_image = image.resize((32, 32))
image.close()
resized_image.save(path)
Burada önce glob fonksiyonu ile dizindeki tüm resim dosyaları elde edilmişl sonra PIL ile yeniden boyutlandırılmış,
sonra da yeni boyuttaki resim orijinal formatta save edilmiştir.
Aşağıda tüm örneği bir bütün olarak veriyoruz.
#----------------------------------------------------------------------------------------------------------------------------
import pickle
import glob
2024-08-22 19:31:50 +03:00
EPOCHS = 100
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.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])
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
import matplotlib.pyplot as plt
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(4, 20))
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()
2024-08-22 19:31:50 +03:00
scaled_training_dataset_x = training_dataset_x / 255
scaled_test_dataset_x = test_dataset_x / 255
2024-08-22 19:31:50 +03:00
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)
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Conv2D, MaxPooling2D, Flatten
model = Sequential(name='CIFAR10')
model.add(Input((32, 32, 3), name='Input'))
model.add(Conv2D(32, (3, 3), activation='relu', name='Conv2D-1'))
model.add(MaxPooling2D(name='MaxPooling2D-1'))
model.add(Conv2D(64, (3, 3), activation='relu', name='Conv2D-2'))
model.add(MaxPooling2D(name='MaxPooling2D-2'))
model.add(Conv2D(128, (3, 3), activation='relu', name='Conv2D-3'))
model.add(MaxPooling2D(name='MaxPooling2D-3'))
model.add(Flatten(name='Flatten'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
2024-08-22 19:31:50 +03:00
model.compile('rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(scaled_training_dataset_x, ohe_training_dataset_y, batch_size=32, epochs=EPOCHS, validation_split=0.2)
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=10)
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(14, 6))
plt.title('Categorcal Accuracy - Validation Categorical Accuracy', fontsize=14, pad=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()
2024-08-22 19:31:50 +03:00
eval_result = model.evaluate(scaled_test_dataset_x , ohe_test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
2024-08-22 19:31:50 +03:00
# prediction
import numpy as np
import os
count = 0
hit_count = 0
for path in glob.glob('Predict-Pictures/*.*'):
image = plt.imread(path)
scaled_image = image / 255
model_result = model.predict(scaled_image.reshape(-1, 32, 32, 3), verbose=0)
predict_result = np.argmax(model_result)
fname = os.path.basename(path)
real_class = fname[:fname.index('-')]
predict_class = class_names[predict_result]
print(f'Real class: {real_class}, Predicted Class: {predict_class}, Path: {path}')
if real_class == predict_class:
hit_count += 1
count += 1
print('-' * 20)
print(f'Prediction accuracy: {hit_count / count}')
#----------------------------------------------------------------------------------------------------------------------------
57. Ders - 10/08/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aslında CIFAR-10 veri kümesi tensorflow.keras.datasets paketi içerisinde cifar10 modülü biçiminde hazır olarak da bulunmaktadır.
2024-08-22 19:31:50 +03:00
Keras'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.
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
import glob
EPOCHS = 5
from tensorflow.keras.datasets import cifar10
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = cifar10.load_data()
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']
2024-08-22 19:31:50 +03:00
import matplotlib.pyplot as plt
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(4, 20))
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()
2024-08-22 19:31:50 +03:00
scaled_training_dataset_x = training_dataset_x / 255
scaled_test_dataset_x = test_dataset_x / 255
2024-08-22 19:31:50 +03:00
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)
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Conv2D, MaxPooling2D, Flatten
model = Sequential(name='CIFAR10')
model.add(Input((32, 32, 3), name='Input'))
model.add(Conv2D(32, (3, 3), activation='relu', name='Conv2D-1'))
model.add(MaxPooling2D(name='MaxPooling2D-1'))
model.add(Conv2D(64, (3, 3), activation='relu', name='Conv2D-2'))
model.add(MaxPooling2D(name='MaxPooling2D-2'))
model.add(Conv2D(128, (3, 3), activation='relu', name='Conv2D-3'))
model.add(MaxPooling2D(name='MaxPooling2D-3'))
model.add(Flatten(name='Flatten'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
2024-08-22 19:31:50 +03:00
model.compile('rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(scaled_training_dataset_x, ohe_training_dataset_y, batch_size=32, epochs=EPOCHS, validation_split=0.2)
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=10)
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(14, 6))
plt.title('Categorcal Accuracy - Validation Categorical Accuracy', fontsize=14, pad=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()
2024-08-22 19:31:50 +03:00
eval_result = model.evaluate(scaled_test_dataset_x , ohe_test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
2024-08-22 19:31:50 +03:00
# prediction
import numpy as np
2024-08-22 19:31:50 +03:00
import os
2024-08-22 19:31:50 +03:00
count = 0
hit_count = 0
for path in glob.glob('Predict-Pictures/*.*'):
image = plt.imread(path)
scaled_image = image / 255
model_result = model.predict(scaled_image.reshape(-1, 32, 32, 3), verbose=0)
predict_result = np.argmax(model_result)
fname = os.path.basename(path)
real_class = fname[:fname.index('-')]
predict_class = class_names[predict_result]
print(f'Real class: {real_class}, Predicted Class: {predict_class}, Path: {path}')
if real_class == predict_class:
hit_count += 1
count += 1
print('-' * 20)
print(f'Prediction accuracy: {hit_count / count}')
#----------------------------------------------------------------------------------------------------------------------------
CIFAR-10 vei kümesinin 100 sınıf içeren CIFAR-100 isimli başka bir versiyonu da vardır. 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 olduğunu 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
2024-08-22 19:31:50 +03:00
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
yine 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'
]
Bu örnekte de yine kestirim için kullanılacak dosyalar "Predict-Pictures" isimli dizinde bulunmalıdır. Resimleri o
dizine çektikten sonra aşağıdaki programı çalıştırarak onları 32x32x3 olarak yeniden boyutlandırabilirsiniz:
# rescale-image.py
from PIL import Image
import glob
for path in glob.glob('Predict-Pictures/*.*'):
image = Image.open(path)
resized_image = image.resize((32, 32))
image.close()
resized_image.save(path)
Aşağıda CIFAR-100 örneği bütün olarak verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
import pickle
import numpy as np
2024-08-22 19:31:50 +03:00
EPOCHS = 5
2024-08-22 19:31:50 +03:00
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']
2024-08-22 19:31:50 +03:00
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']
2024-08-22 19:31:50 +03:00
training_dataset_x = training_dataset_x.reshape(-1, 3, 32, 32)
training_dataset_x = np.transpose(training_dataset_x, [0, 2, 3, 1])
2024-08-22 19:31:50 +03:00
test_dataset_x = test_dataset_x.reshape(-1, 3, 32, 32)
test_dataset_x = np.transpose(test_dataset_x, [0, 2, 3, 1])
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',
2024-08-22 19:31:50 +03:00
'tulip', 'turtle', 'wardrobe', 'whale', 'willow_tree', 'wolf', 'woman', 'worm'
]
2024-08-22 19:31:50 +03:00
import matplotlib.pyplot as plt
plt.figure(figsize=(4, 20))
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()
scaled_training_dataset_x = training_dataset_x / 255
scaled_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)
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Conv2D, MaxPooling2D, Flatten
model = Sequential(name='CIFAR100')
model.add(Input((32, 32, 3), name='Input'))
model.add(Conv2D(32, (3, 3), activation='relu', name='Conv2D-1'))
model.add(MaxPooling2D(name='MaxPooling2D-1'))
model.add(Conv2D(64, (3, 3), activation='relu', name='Conv2D-2'))
model.add(MaxPooling2D(name='MaxPooling2D-2'))
model.add(Conv2D(128, (3, 3), activation='relu', name='Conv2D-3'))
model.add(MaxPooling2D(name='MaxPooling2D-3'))
model.add(Flatten(name='Flatten'))
model.add(Dense(512, activation='relu', name='Hidden-1'))
model.add(Dense(512, activation='relu', name='Hidden-2'))
model.add(Dense(100, activation='softmax', name='Output'))
model.summary()
model.compile('rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(scaled_training_dataset_x, ohe_training_dataset_y, batch_size=32, epochs=EPOCHS, validation_split=0.2)
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=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=(14, 6))
plt.title('Categorcal Accuracy - Validation Categorical Accuracy', fontsize=14, pad=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 , ohe_test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
# prediction
import numpy as np
import glob
import os
2024-08-22 19:31:50 +03:00
count = 0
hit_count = 0
for path in glob.glob('Predict-Pictures/*.*'):
image = plt.imread(path)
scaled_image = image / 255
model_result = model.predict(scaled_image.reshape(-1, 32, 32, 3), verbose=0)
predict_result = np.argmax(model_result)
fname = os.path.basename(path)
real_class = fname[:fname.index('-')]
predict_class = class_names[predict_result]
print(f'Real class: {real_class}, Predicted Class: {predict_class}, Path: {path}')
if real_class == predict_class:
hit_count += 1
count += 1
print('-' * 20)
print(f'Prediction accuracy: {hit_count / count}')
#----------------------------------------------------------------------------------------------------------------------------
58. Ders - 10/08/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aslında CIFAR-100 veri kümesi de tensorflow.keras.datasets paketi içerisindeki cifar100 modülünde hazır bir biçimde
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()
2024-08-22 19:31:50 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Verilerin Artırılması (Data Augmentation) makine öğrenmesi ve genel olarak veri bilimi için önemli yardımcı konulardan
biridir. Elimizdeki eğitim veri kümesi kısıtlı olabilir. Biz de elimizdeki veri kümesinden hareketle veri kümemizi
büyütmek isteyebiliriz. Bu konuya genel olarak "verilerin artırılması (data augmentation)" denilmektedir.
Verilerin artırılması değişik veri grupları için farklı tekniklerle gerçekleştirilmektedir. Yani bu bakımdan genel tekniklerle
değil ilgili konuya özgü tekniklerle veri artırımı yapılmaktadır. Örneğin resimsel verilerin artıırılması ile metinsel
verilerin artırılması farklı tekniklerle yapılmaktadır. O halde verilerin artırılması için tipik şu alt gruplar sıralanabilir:
- Resimsel verilerin artırılması
- Metinsel verilerin artırılması
- İşitsel (audio) verilerin artırılması
- Hareketli görüntü verilerinin artırılması
- Veri tabloları biçimindeki (Boston Hausing Price veri kümesinde olduğu gibi) verilerin artırılması
- Zamansal (temporal/time series) verilerinin artırılması
Verilerin artırılması için ilgili framework'ler ve kütüphaneler özel sınıflar ve fonksiyonlar bulundurabilmektedir. Örneğin
sinir ağları için kullandığımız Keras kütüphanesi ve dolayısıyla Tensorflow kütüphanesi veri artırımı için çeşitli fonksiyonlar
ve katman sınıfları bulundurmaktadır. Aynı durum PyTorch kütüphanesi için de geçerlidir.
Verilerin artırılması sırasında bazı genel unsurlara dikkat edilmesi gerekir. Örneğin artırım sırasındaki "yanlılık (bias)"
önemli sorunlardan biridir. Veriler artırılırken onların özellikleri belli bir yöne kaydırılmamalıdır.
Biz bu bölümde resimsel verilerin artırılması üzerinde duracağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Örneğin Cifar-100 veri kümesinde eğitim için kullanabileceğimiz toplam 50000 resim vardır. Resimlerin sınıfları 100 tane
olduğuna göre her sınıf için ortalama 500 resim söz konusudur. Pekiyi bu 500 resim ilgili resim sınıfı için genelleme
yapabilir mi? Örneklerimizde "categorical accuracy" değerinin %30 ile %40 arasında değişebildiğini gördük. Bu da her 100
resmin 30 ile 40 arasındaki kısmının doğru sınıflandırıldığı diğerlerinin yanlış sınıflandırıldığı anlamına gelmektedir.
Bu veri kümesindeki "yengeç (crab)" resimlerini dikkate alalım. Buradai yengeçlerin bize doğru konumu değişebilmektedir.
Burada ters dönmüş bir yengeç yoktur. Buradaki yengeç resimleri hep dik bir açıdan elde edilmiş resimlerdir. Ancak
kestirim yapılırken gerçek resimlerin eğitimdeki resimlerle aynı koşulda oluşturulması mümkün olamayabilir. İşte biz bu
yengeç resimleri üzerinde manipülasyonlar yaparak garklı özelliklere sahip yengeç resimleri oluşturabiliriz. Veri kümesine
bu resimleri de dahil edebiliriz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
Verilerin artırılması konusu genellikle kitapların belli bölümlerinde karışık bir biçimde ele alınmaktadır. Konusu tam
olarak bu olan kitapların sayısı çok azdır. Ancak bu alanda yazılmış akademik olan ve akademik olmayan çok sayıda makale
bulmak mümkündür. Bu konuya odaklanmış az sayıda kitaptan biri "Data Augmentatiın in Python (Packt Yayınevi), Duc Haba
(2023)" isimli kitaptır. Buradaki notlarda bu kitaptaki konu başlıklarından alıntı yapacağız. Ancak bu kitap uygulamalı
bir kitap değildir.
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Resimsel verilerin artırılması için pek çok teknik kullanılmaktadır. Önemli teknikler şunlardır:
2024-08-22 19:31:50 +03:00
- Resmin Çevrilmesi (Flipping): Resimlerin yatay ve düşey yönde çevrilmesiyle yeni resimlerin elde edilmesi tekniğidir.
Örneğin bir resimde bir kişi sola bakarken o resmi yatay biçimde çevirirsek o kişi sağa bakar hale gelir.
- Resmin Kırpılması (Cropping): Bir resmin bir bölgesinin alınarak yeni bir resim haline getirilmesine
ilişkin bir tekniktir. Crop işlemi genellikle merkeze yönelik yapılır. Ancak diğer bölgeler üzerinde (özellikle merkezden
kayıklık yaratarak) crop işlemleri de yapılabilmektedir.
- Yeniden Boyutlandırma (Resizing): Resmin yatay düşey oranını (aspect ratio) değiştirerek başka resimler elde edilmesine
yönelik tekniklerdir. Örneğin böylece bir insan daha uzun boylu, daha zayıf hale getirilebilmektedir.
- Resmi Tamamlama (Padding): REsmin kenarlarına ekler yaparak resmi farklılaştırma tekniğidir.
- Resmi Döndürme (Rotating): Resmi belirli bir açıyla döndürerek yeni resimler elde etme tekniğidir.
- Resmin Transpose Edilmesi (Translation): Resmin eksenlere göre değişik bir biçime dönüştürülmesi tekniğidir. Burada
geometrik dönüştürmeler yapılmaktadır.
- Gürültü Eklemesi (Noise Injection): Resme resimde olmayan gürültülerin eklenmesi tekdiğidir. Örneğin resim sanki
bir sis içerisinde çekilmiş gibi bir etki oluşturulabilir. Resme dumanlar eklenebilir. Resimdeki netliğin bozulması
sağlanabilir.
- Resmin Zoom Edilmesi (Zooming): Resmin zoom-in ya da zoom-out yapılarak başka resimlerin elde edilmesine ilişkin tekniklerdir.
- Resmin Karanlık ya da Aydınlık Hale Getirilmesi (Darken and Lighten): Resmi sanki daha karanlık bir ortamda ya da
faha aydınlık bir ortamda çekilmiş gibi değiştirme tekniğini belirtmektedir.
- Resmin Saturasyonun Değiştirilmesi (Color Sturation): Resimdeki renk dougunluklarının değiştirilmesi tekniğidir. Yani örneğin
kırmızılar dah akırmızı, siyahlar daha siyah hale getirilebilir.
- Resimdeki Renklerin Ötelenmesi (Hue Shifting): Resimdeki renklerin frekanslarını değiştirip b-aşka renkler haline
getirilmesine ilişkin tekniklerdir.
- Resimdeki Bazı Renklerin Değiştirilmesi (Color Casting): Resimdeki bazı renkler başka renklerle yer değiştirilebilir.
Örneğin koyu beyaz daha açık beyaz yapılabilir. REsimdeki yeşil alanlar gri olarak değiştirilebilir.
- Resimdeki Bazı Kısımların Rastgele Silinmnesi (Random Erasing): Resimdeki bazı alanların silinerek onlar yerine başka
dolguların kullanılmasına ilişkin tekniklerdir.
- Resimlerin Birleştirilmesi (Combining): Farklı küçük resimlerin bir araya egtirilerek farklı bir resim haline getirilmesine
ilişkin tekniklerdir.
Resimsel verilerin artırılmasında burada belirttiğimiz tekniklerin hepsinin uygulanması gerekmemektedir. Genellikle
uygulamacılar yalnızca birkaç tekniği kullanmaktadır. Bu teknikler uygulanırken abartıya kaçılmamalıdır. Örneğin resim
zoom edilirken çok küçük büyütme küçültme uygulanabilir. Resme gürültü eklenirken küçük gürültüler tercih edilebilir.
Resim döndürülürken küçük döndürmeler uygulanabilir. Abartılı işlemler gerçekle bağlantının kesilmesine yol açıp modelin
performasnını düşürebilmektedir.
Genellikle uygulamacılar resimleri üzt üste birden fazla kez yukarıda belirttiğimiz işlemlere sokarlar. Örneğin önce bir
flip işlemi arkaından bir zoom işlemi arkasından bir döndürme işlemi peşi sıra yapılabilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Pekiyi bir resim tanıma problemi söz konusu olduğunda veri artırmayı nasıl uygulamalıyız? Önce resimleri yukarıdaki
2024-08-22 19:31:50 +03:00
tekniklerle çoğaltıp onları saklamak mı yoksa eğitime sokarken onları hiç saklamadan o anda resimleri çoğaltmak mı daha
iyi bir yöntemdir? İşte genellikle ikinci yöntem tercih edilmektedir. Yani çoğaltma işlemi eğitimin bir önişlemi olarak
eğitim sırasında yapılmaktadır. Çaoğaltılmış verilerin saklanması fazlaca disk hacmi gerekterirebilmektedir. Yalnızca
orijinal resimlerin saklanması daha uygun bir yöntem olabilir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Keras'ta resimlerin artırımına ilişkin Tensorflow kütüphanesinden gelen fonksiyonlar ve sınıflar bulunmaktadır. Bu amaçla
keras.layers modülünde bulundurulmuş olan katman nesneleri şunlardır:
2024-08-22 19:31:50 +03:00
RandomFlip
RandomRotation
RandomZoom
RandomContrast
RandomCrop
RandomBrightness
RandomTranslation
Resize
Rescaling
2024-08-22 19:31:50 +03:00
RandomFlip katmanı "horizontol", "vertical" ya da "horizontal_and_vertical" değerlerini parametre olarak almaktadır. Resmi
rastgele yatay, düşey ya da her iki yönde tam çevirmektedir. RandomRtotation katmanı parametre olarak maksimum radyan cinsinden
dönüş açısı alır. Resmi ratgele bu maksimum açıyı geçmeyecek biçimde döndürür. RandomZoom makismum zoom faktörünü parametre
olarak almaktadır. Sıfırdan büyük değerler zoom-in sıfırdan küçük değerler zoom-out anlamına gelir. Bu katman resmi bu
maksimum değeri dikkate alarak rastgele biçimde zoom eder. RandomContrast resmin kontrastını rastgele bir biçimde değiştirmek
için kullanılmaktadır. RandomCrop ise resmin rastgele bir bölgesini elde etmekte kullanılmaktadır. Ancak RandomCrop belli bir
em-boy parametresi almaktadır. Resim rastgele bir biçimde bizim istediğimiz en-boy halinde crop edilmektedir. Tabii bizim
bu işlem sonucunda resmi yeniden Resize sınıfı ile eski büyüklüğüne getirmemiz gerekir. RandomBrightness katmanı ise resmin
ıklık-koyuluk durumunu rastgele değiştirmektedir. Yani bu katman sayesinde resin sanki farklı ışık şiddetleri altında (gece,
akşam, öğleni sabah) çekilmiş gibi bir etki oluşmaktadır. RandormTranslation koordinat eksininde rastgele dönüüştürmeler yapmaktadır.
Resize ve Rescaling sınıfları sırasıyla resmin boyutunu değiştirmek için ve ölçekleme yapmak için kullanılmaktadır. Tabii
biz ölçeklemeyi resmin tüm pizellerini 255'e bölerek daha önceden yapmışsak bu Rescaling katmanına gereksinim duymayız.
Ancak bu işlem bu sınıfla bir katman nesnesi olarak da gerçekleştirilebilmektedir.
2024-08-22 19:31:50 +03:00
Aşağıda bu sınıfların yarattığı etkileri gösteren bir örnek verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
2024-08-22 19:31:50 +03:00
import matplotlib.pyplot as plt
2024-08-22 19:31:50 +03:00
picture = plt.imread('Sample-Pictures/AbbeyRoad.jpg')
plt.figure(figsize=(9, 16))
plt.imshow(picture);
plt.show()
2024-08-22 19:31:50 +03:00
from tensorflow.keras.layers import RandomFlip, RandomRotation, RandomZoom, Rescaling, RandomCrop, Resizing,
RandomContrast, RandomBrightness
rf = RandomFlip('horizontal')
result = rf(picture).numpy()
plt.figure(figsize=(9, 16))
plt.imshow(result.astype('uint8'));
plt.show()
rr = RandomRotation(0.1)
result = rr(picture).numpy()
plt.figure(figsize=(9, 16))
plt.imshow(result.astype('uint8'));
plt.show()
rz = RandomZoom(0.2)
result = rz(picture).numpy()
plt.figure(figsize=(9, 16))
plt.imshow(result.astype('uint8'));
plt.show()
rs = Rescaling(0.50)
result = rs(picture).numpy()
plt.figure(figsize=(9, 16))
plt.imshow(result.astype('uint8'));
plt.show()
rc = RandomCrop(500, 500)
result = rc(picture).numpy()
plt.figure(figsize=(9, 16))
rs = Resizing(1000, 1000)
result = rs(result).numpy()
plt.figure(figsize=(9, 16))
plt.imshow(result.astype('uint8'));
plt.show()
rc = RandomContrast(0.2)
result = rc(picture).numpy()
plt.figure(figsize=(9, 16))
plt.imshow(result.astype('uint8'));
plt.show()
rc = RandomBrightness(0.7)
result = rc(picture).numpy()
plt.figure(figsize=(9, 16))
plt.imshow(result.astype('uint8'));
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
59. Ders - 18/08/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Şimdi de yukarıdaki augmentation katman nesnelerini daha önce yapmış olduğumuz CIFAR-100 veri kümesinde kullanalım. Aslında
tek yapacağımız şey Input katmanından sonra bu katman nesnelerini modele eklemektir. Örneğin:
model = Sequential(name='CIFAR100')
model.add(Input((32, 32, 3), name='Input'))
model.add(RandomFlip('horizontal'))
model.add(RandomRotation(0.1))
model.add(RandomZoom(0.2))
model.add(Conv2D(32, (3, 3), activation='relu', name='Conv2D-1'))
model.add(MaxPooling2D(name='MaxPooling2D-1'))
model.add(Conv2D(64, (3, 3), activation='relu', name='Conv2D-2'))
model.add(MaxPooling2D(name='MaxPooling2D-2'))
model.add(Conv2D(128, (3, 3), activation='relu', name='Conv2D-3'))
model.add(MaxPooling2D(name='MaxPooling2D-3'))
model.add(Flatten(name='Flatten'))
model.add(Dense(512, activation='relu', name='Hidden-1'))
model.add(Dense(512, activation='relu', name='Hidden-2'))
model.add(Dense(100, activation='softmax', name='Output'))
model.summary()
Burada Input katmanından sonra aşağıdaki üç augmentation katmanı modele eklenmiştir:
model.add(RandomFlip('horizontal'))
model.add(RandomRotation(0.1))
model.add(RandomZoom(0.2))
Böylece aslında her epoch'ta her resim rastgele bir biçimde çevrilip, döndürülüp zoom edilmektedir. Tabii bu biçimdeki
uygulamalarda artık eğitimdeki epoch sayısını artırmalıyız. Çünkü artık her epoch'ta aslında aynı veri kümesi işleme
sokulmamaktadır. Rastgelelikten dolayı farklı veri kğmeleri işleme sokulmaktadır. Bu tür veri artırma işlemlerinde
artık veri kümesine çok fazla epoch uygulamalıyız. Çünkü epoch'lar sırasında aslında gerçek veri kümesinin aynısı değil
rastgele biçimleri işleme sokulmaktadır. Eğer bu tür modellere az epoch uygularsak modelin başrısını büyütmek bir yana
muhtemelen düşürmüş oluruz.
Aşağıda CIFAR-100 örneğinin augmentation uygulanmış biçimini veriyoruz. Burada EPOCHS sayısını 1000 olarak ayarladık.
Ancak bu örneği denerken bu sayıyı düşürebilirsiniz. Bu tür modellerede eğitim zamanı çok uzayacağı için bulut (cloud)
sistemlerinden faydalanılabilmektedir.
#----------------------------------------------------------------------------------------------------------------------------
import glob
EPOCHS = 5
from tensorflow.keras.datasets import cifar100
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = cifar100.load_data()
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)
2024-08-22 19:31:50 +03:00
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',
2024-08-22 19:31:50 +03:00
'tulip', 'turtle', 'wardrobe', 'whale', 'willow_tree', 'wolf', 'woman', 'worm'
]
import matplotlib.pyplot as plt
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(4, 20))
for i in range(30):
plt.subplot(10, 3, i + 1)
2024-08-22 19:31:50 +03:00
plt.title(class_names[training_dataset_y[i, 0]], pad=10)
plt.imshow(training_dataset_x[i])
plt.show()
2024-08-22 19:31:50 +03:00
scaled_training_dataset_x = training_dataset_x / 255
scaled_test_dataset_x = test_dataset_x / 255
2024-08-22 19:31:50 +03:00
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)
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, RandomFlip, RandomRotation, RandomZoom, Dense, Conv2D, MaxPooling2D, Flatten
model = Sequential(name='CIFAR100')
model.add(Input((32, 32, 3), name='Input'))
model.add(RandomFlip('horizontal'))
model.add(RandomRotation(0.1))
model.add(RandomZoom(0.2))
model.add(Conv2D(32, (3, 3), activation='relu', name='Conv2D-1'))
model.add(MaxPooling2D(name='MaxPooling2D-1'))
model.add(Conv2D(64, (3, 3), activation='relu', name='Conv2D-2'))
model.add(MaxPooling2D(name='MaxPooling2D-2'))
model.add(Conv2D(128, (3, 3), activation='relu', name='Conv2D-3'))
model.add(MaxPooling2D(name='MaxPooling2D-3'))
model.add(Flatten(name='Flatten'))
model.add(Dense(512, activation='relu', name='Hidden-1'))
2024-08-22 19:31:50 +03:00
model.add(Dense(512, activation='relu', name='Hidden-2'))
model.add(Dense(100, activation='softmax', name='Output'))
model.summary()
2024-08-22 19:31:50 +03:00
model.compile('rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
hist = model.fit(scaled_training_dataset_x, ohe_training_dataset_y, batch_size=32, epochs=EPOCHS, validation_split=0.2)
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=10)
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
2024-08-22 19:31:50 +03:00
plt.figure(figsize=(14, 6))
plt.title('Categorcal Accuracy - Validation Categorical Accuracy', fontsize=14, pad=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()
2024-08-22 19:31:50 +03:00
eval_result = model.evaluate(scaled_test_dataset_x , ohe_test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
2024-08-22 19:31:50 +03:00
# prediction
import numpy as np
import os
count = 0
hit_count = 0
for path in glob.glob('Predict-Pictures/*.*'):
image = plt.imread(path)
scaled_image = image / 255
model_result = model.predict(scaled_image.reshape(-1, 32, 32, 3), verbose=0)
predict_result = np.argmax(model_result)
fname = os.path.basename(path)
real_class = fname[:fname.index('-')]
predict_class = class_names[predict_result]
print(f'Real class: {real_class}, Predicted Class: {predict_class}, Path: {path}')
if real_class == predict_class:
hit_count += 1
count += 1
print('-' * 20)
print(f'Prediction accuracy: {hit_count / count}')
#----------------------------------------------------------------------------------------------------------------------------
Aslında Keras'ta veri artırma (data augmentation) işlemleri programcı tarafından özelleştirilerek (customize edilerek) de
gerçekleştirilebilmektedir. Ancak bu işlemlerde Tensorflow kütüphanesinin başka özelliklerinin de kullanılması gerekmektedir.
Biz henüz Tensorflow kütüphanesinin taban kullanımını görmediğimiz için burada bu konu üzerinde durmayacağız. Bu özelleştirme
sürecinde Keras'ın tensorflow.keras.preprocessing.image modülündeki fonksiyonlardan faydalanılmaktadır. Uygulamacı bu fonksiyonları
manuel biçimde kullanabilir ya da bu fonksiyonlardan kendi özelleştirilmiş (custom) katman nesneleri oluşturabilir. Bu
fonksiyonların doküsmantasyonuna aşağıdaki bağlantıdan erişebilirsiniz:
https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/image
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aslında Keras'ta uzunca bir süredir hem dizin içerisindeki resimlerden önişlem yapan, veri artırıında da kullanılabilecek
ImageDataGenerator isimli bir sınıf bulunuyordu. Ancak Keras'ın yeni versiyonlarında bu sınıf "deprecated" yapılmıştır.
Yani "artık uygulamacıların bu sınıfı kullanmaması gerektiği ileride bu sınıfın tamamen kütüphaneden kaldırılabileceği"
belirtilmiştir. Bu nedenle biz artık kursumuzda bu sınıf üzerinde ayrıntılı bir biçimde durmayacağız. Ancak eski kodları
incelerken bu sınıfla karşılaşabilirsiniz. Burada sınıfın temel kullanım abacı üzerinde bazı şeyler söylemek istiyoruz.
Sınıfın __init__ metodunun parametrik yapısı şöyledir:
tf.keras.preprocessing.image.ImageDataGenerator(
featurewise_center=False,
samplewise_center=False,
featurewise_std_normalization=False,
samplewise_std_normalization=False,
zca_whitening=False,
zca_epsilon=1e-06,
rotation_range=0,
width_shift_range=0.0,
height_shift_range=0.0,
brightness_range=None,
shear_range=0.0,
zoom_range=0.0,
channel_shift_range=0.0,
fill_mode='nearest',
cval=0.0,
horizontal_flip=False,
vertical_flip=False,
rescale=None,
preprocessing_function=None,
data_format=None,
validation_split=0.0,
interpolation_order=1,
dtype=None
)
Bu sınıfın kabaca kullanımı şöyledir:
1) Uygulamacı önce ImageDataGenerator sınıfı türünden bir nesne yaratır. Bu nesneyi yaratırken yapılacak artırım (augmentation)
işlemlerini parametrelere argümanlar girerek belirler. Örneğin:
idg = ImageDataGenerator(
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest'
)
2) Daha sonra sınıfın flow metotları çağrılır. Aslında asıl dönüştürme işlemleri bu flow metotları yoluyla yapılmaktadır.
Yani flow metodu her çağrıldığında ona verdiğimiz x ve y değerleri yukarıda belirttiğimiz biçimde dönüştürmeye sokulmaktadır.
flow metotları üretici fonksiyon gibi çalışmaktadır. Yani her next işleminde sıradaki batch'lik veriyi işleme sokarak
vermektedir. üç floaw metodu vardır:
flow
floaw_from_dataframe
flow_from_directory
3) Artık model kurulup fit işleminde x verileeri olarak ve sınama verileri olarak bu flow metotlarından elde edilen
üretici fonksiyonlar verilir. Örneğin:
hist = model.fit(idg.flow(x_train, y_train, batch_size=64), epochs=50)
Pekiyi bu sınıf neden deprecated yapılmıştır? Bu sınıf tek hamlede pek çok veri artırımını yapmaya çalışmaktadır. Bunun
yerine bu işlemlerin tek tek katmanlara yaptırılması daha modüler bir yaklaşımdır. Yukarıda açıkladığımız veri artırımına
yönelik katman nesneleri ve tensorflow.keras.preprocessing.image modülündeki fonksiyonlar zaten bu sınıfın yaptıklarını
daha modüler biçimde yapabilmektedir. Ancak ImageDataGenerator sınıfının flow_from_directory metodu doğrudan bir dizindeki
resimlerden önişlem yapabilmektedir. İşte bu işlemi bağımsız yapabilen image_dataset_from_directory isimli bir fonksiyon
da tensorflow.keras.preprocessing modülüne eklenmiştir. Sonraki paragrafta bu fonksiyonun kullanımııklanmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
tensorflow.keras.preprocessing modülündeki image_dataset_from_directory isimli fonksiyon bir dizinden hareketle oradaki
resimleri kullanıma hazır hale getirmektedir. Uygulamacı bulduğu resimleri bir dizin içerisine belli bir düzende saklar.
Sonra da bu fonksiyonu çağırır. Fonksiyon da bu resimleri önişleme sokarak bize Tensorflow Dataset nesnesi olarak verir.
Anımsanacağı gibi fit işleminde biz x, y verileri yerine üretici fonksiyonları ve Tensorflow Dataset nesnelerini kullanabiliyorduk
Anımsanacağı gibi Dataset sınıfı istenildiği zaman sıradaki batch'i tıpkı üretici fonksiyonlar gibi verebilen bir sınıftır.
image_dataset_from_directory fonksiyonunun parametrik yapısı şöyledir:
tensorflow.keras.preprocessing.image_dataset_from_directory(
directory,
labels='inferred',
label_mode='int',
class_names=None,
color_mode='rgb',
batch_size=32,
image_size=(256, 256),
shuffle=True,
seed=None,
validation_split=None,
subset=None,
interpolation='bilinear',
follow_links=False,
crop_to_aspect_ratio=False,
pad_to_aspect_ratio=False,
data_format=None,
verbose=True
)
Uygulamacı resimleri bir dizin içerisine yerleştirmelidir. Eğer bir sınıflandırma problemi söz konusu ise her sınıftaki resimler
ayrıca bir alt dizine yerleştirilmelidir. Örneğin biz elmalarla portakalları sınıflandıran bir ikili sınıflandırma problemi
üzerinde çalışacak olalım. Bu durumda oluşturacağımız dizin yapısı şöyle olmalıdır:
Images
Apple
Orange
Tabii burada dizinlere istediğimiz isimleri verebiliriz. Ancak sınıflara ilişkin anlamlı isimlerin kullanılması tavsiye
edilmektedir. İşte uygulamacı bulduğu elma resimlerini Apple dizinine, portakal resimlerini Orange dizinine yerleştirir.
Fonksiyonun birinci parametresi resimlerin bulunduğu dizin'in yol ifadesini almaktadır. İkinci parametre sınıf belirten
etiketlerin nasıl oluşturulacağını belirtmektedir. Buradaki default 'inferred' değeri etiketlerin otomatik olarak dizin
2024-09-16 10:49:44 +03:00
yapısından oluşturulacağı anlamına gelmektedir. Bu parametre birkaç biçimde daha geçilebilmektedir. Bunun için dokümanlara
2024-08-22 19:31:50 +03:00
başvurabilirsiniz. label_mode parametresi default olarak 'int' biçimdedir. Bu durumda her bir kategori bir int değerle
temsil edilmektedir. (Yani y değeri olarak int değerler elde edilecektir.) Eğer bu parametreye 'categorical' girilirse
2024-09-16 10:49:44 +03:00
burada y değerleri "one-hot encoding" biçiminde oluşturulur. Eğer bu parametreye 'binary' girilirse y değerleri 0, 1
2024-08-22 19:31:50 +03:00
biçiminde oluşturulmaktadır. İkili sınıflandırma problemleri için bu parametreye 'binary', çoklu sınıflandırma problemleri
için 'categorical' girilmelidir. class_names parametresi sınıfların yazısal isimlerini belirtmektedir. Default durumda
sınıfların isimleri alt dizin isimlerinden elde edilmektedir. color_mode parametresi dizinlerdeki resimlerin renk durumlarının
nasıl ele alınacağını belirtmektedir. Bu parametrenin default değeri 'rgb' biçimindedir. Ancak duruma göre bu parametre
'grayscale' ya da 'rgba' biçiminde de girilebilir. batch_size parametresi bir batch'lik resmin kaç resimden oluşacağını
belirtmektedir. Bu sınıf kullanılarak fit işlemi yapılırken artık fit metodunun batch_size parametresi girilmez. Bu
batch_size değeri bu nesnede belirtilmektedir. Fonksiyonun image_size parametresi dizinlerdeki resimlerin hangi boyuta
çekileceğini belirtmektedir. Bu parametrenin default değeri (256, 256) biçimindedir. shuffle parametresi dizinlerden
elde edilen resimlerin her epoch'ta karıştırılıp karıştırılmayacağını belirtmektedir. Fonksiyonun diğer parametreleri
2024-09-16 10:49:44 +03:00
için dokümanlara başvurulabilir.
Örneğin yukarıdaki gibi bir dizin yapısı olsun:
Images
Apple
Orange
Biz bu dizinden resimleri aşağıdaki gibi Dataset biçiminde oluşturabiliriz:
dataset = image_dataset_from_directory('Images', label_mode='binary', image_size=(128, 128), batch_size=1)
2024-08-22 19:31:50 +03:00
2024-09-16 10:49:44 +03:00
Artık image_dataset_from_dreictory fonksiyonuyla elde ettiğimiz Dataset nesnesini daha önce görmüş olduğumuz parçalı verilerle
eğitimde kullanabiliriz. Bir Dataset nesnesi içerisindeki bilgiler sınıfın take isimli metoduyla elde edilebilmektedir.
take metodunun parametrik yapısı şöyledir:
2024-08-22 19:31:50 +03:00
2024-09-16 10:49:44 +03:00
take(count, name=None)
Metodun count parametresi Dataset nesnesinden kaç elemanın alınacağını belirtmektedir. Bu parametre -1 girilirse tüm elemanlar
elde edilmektedir. Bu count parametresinin image_dataset_from_directory fonunda girilen batch_size parametresi ile doğrudan
bir ilgisisi yoktur. batch_size parametresi dizinden bilgilerin kaçarlı bir biçimde alınacağını belirtmektedir. Aşağıda
ilgili dizinlerdeki resimleri görüntüleyen bir örnek verilmiştir.
Biz image_dataset_from_directory fonksiyonunu yalnız fit işlemlerinde değil, test ve kestirim işlemlerinde de kullanabiliriz.
2024-08-22 19:31:50 +03:00
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
import tensorflow
from tensorflow.keras.preprocessing import image_dataset_from_directory
dataset = image_dataset_from_directory('Images', label_mode='binary', image_size=(128, 128), batch_size=1)
batch_data = dataset.take(2)
import matplotlib.pyplot as plt
for x, y in batch_data:
image = tensorflow.cast(x[0], 'uint8')
plt.title(str(int(y)))
plt.imshow(image)
plt.show()
#----------------------------------------------------------------------------------------------------------------------------
60. Ders - 07/09/2024 - Cumartesi
2024-08-22 19:31:50 +03:00
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
Bir modeli eğtirken ne kadar epoch uygulamak gerekir? Epoch uygularken şu durumları göz önüne almalıyız?
- Modeldeki loss ya da metrik değerler iyileşmedikten sonra (örneğin loss değeri düşmedikten sonra) fazla epoch uygulamanın
bir yararı olmadığı gibi zararı olabilmektedir. Yani ne kadar çok epoch ygulanırsa daha iyi sonucun elde edileceği gibi
bir yargı doğru değildir. Modeli iyileştirmeyen epoch'ların uygulanması overfitting sorunlarına yol açabilmektedir.
2024-09-16 10:49:44 +03:00
- Modeli eğitirken eğitimdeki loss ya da metrik değerlerin sınamadaki loss ya da metrik değerlerden kopması (yani biri
iyileşirken diğerinin iyileşmemesi) epoch kaynaklı bir overfitting oluşumuna yol açabilmektedir.
- Modelin eğitilmesi sırasında loss ya da metrik değerler dalgalanabilmektedir. Bu dalgalanmanın kötü bir noktasında
epoch'lar bittiğinden dolayı eğitimin sonlanması da arzu edilen bir durum değildir. Çünkü modelde geçmiş epoch'larda daha
iyi değerler oluştuğu halde son durumda daha kötü değerler oluşmuş durumdadır.
Pekiyi bu durumda uygun epoch sayısı nasıl belirlenmelidir? Yöntemlerden biri modeli yüksek bir epoch sayısı ile eğitip
loss ve metirk değerleri gözle inceleyerek uygun epoch değerinin ne olacağına gözle karar vermek olabilir. Tabii bu yöntemin
kusurları vardır. Bu yöntemde epoch sayısı gözle tespit edilip modelin eğitilmesi uzun eğitim zamanına yol açabilir. Dalgalı
durumlarda bu yöntem genellikle çalışmaz. Çünkü her eğitimde birtakım değerlerin rastgele alınması nedeniyle dalgalanmalar
değişebilmektedir. Gözle belirleme yöntemi yerine her epoch'ta callback mekanizması yoluyla uygulamacının değerlere bakıp
modeli manuel bir biçimde sonlandırması daha iyi bir yöntemdir. Biz Keras'taki callback mekanizmalarını daha önce görmüştük.
Ancak bu işlemler için kullanılabilecek iki hazır callback sınıfı da bulundurulmuştur. Bu sınıflar EarlyStopping ve
ModelCheckpoint isimli sınıflardır. İzleyen paragraflarda bu sınıfların kullanımları üzerinde duracağız.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
EarlyStopping callback sınıfının amacı loss ya da metrik değerlerde istenilen kadar iyileşmenin sağlanmadığı durumlarda
eğitimin otomatik sonlandırılmasını sağlamaktır. Normal olarak epoch'lar sırasında loss ve metrik değerlerin iyileşmesi
beklenir. Yukarıda da belirttiğimiz gibi bu değerlerin iyileşmemesi durumunda eğitime devam etmek iyi bir fikir değildir.
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,
2024-09-16 10:49:44 +03:00
restore_best_weights=False,
start_from_epoch=0
)
2024-09-16 10:49:44 +03:00
Burada monitor parametresi izlenecek metrik değeri belirtir. Loss ya da metrik değerin başında "val_" öneki varsa bunun
sınamaya ilişkin değer olduğu kabul edilmektedir. Örneğin bu parametreye "loss" değeri girilirse bu eğitimdeki loss değerini
"val_loss" girilirse bu dasınamadaki loss değerini belirtmektedir. Örneğin sınamadaki accuracy metrik değeri için bu
parametreye "val_accuracy" girilmelidir. min_delta parametresi iyileşme için minimum aralığı belirtmektedir. (Örneğin bu
değer "val_loss" için 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. verbose parametresi 0 ya da 1 biçiminde girilebilir. Eğer
bu parametre 1 olarak girilirse ekrana daha fazla bilgi yazısı çıkartılmaktadır. mode parametresi ise "min", "max" ya da
"auto" biçiminde girilebilir. "min" iyileşmenin düşüşle sağlandığını, "max" iyileşmenin yükselişle sağlandığını belirtir.
"auto"" ise monitor parametresine göre bunun otomatik belirleneceği anlamına gelmektedir. baseline parametresi sonlandırma
için eşik değerin belirlenmesini sağlamaktadır. restore_best_weights parametresi True geçilirse eğitim sonlandırılana kadar
en iyi loss ya da metrik değerin bulunduğu epoch'a ilişkin nöron ağırklık değerleri modele set edilir. Bu parametre False
geçilirse (default durum) modelin sonlandırılması sırasındaki değerler model nesnesinde bırakılır. start_from_epoch
parametresi yeni versiyonlarda eklenmiştir. Bu parametre bu mekanizmanın kaçıncı epoch'tan itibaren başlatılacağını
belirtmektedir. Örneğin:
esc = EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True)
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=EPOCHS,
Burada "val_loss" değerinde üst üste 3 kez iyileşme olmadığnda eğitim otomatik sonlandırılacaktır.
2024-09-16 10:49:44 +03:00
Aşağıdaki örnekte Boston Housing Price veri kümesinde "val_loss" metrik değeri üst üste 3 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. Bu programı çalıştırdığımızda aşağıdaki gibi bir çıktı elde edilmiştir:
2024-09-16 10:49:44 +03:00
...
val_loss: 16.5277 - val_mae: 2.9099
Epoch 17/200
12/12 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - loss: 15.6860 - mae: 2.7044 - val_loss: 16.0524 - val_mae: 2.8168
Epoch 18/200
12/12 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - loss: 15.7328 - mae: 2.6971 - val_loss: 16.0958 - val_mae: 2.8007
Epoch 19/200
12/12 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - loss: 19.8981 - mae: 2.8945 - val_loss: 15.7974 - val_mae: 2.7772
Epoch 20/200
12/12 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 13.1439 - mae: 2.5510 - val_loss: 17.2526 - val_mae: 2.8939
Epoch 21/200
12/12 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 14.5947 - mae: 2.5541 - val_loss: 15.7460 - val_mae: 2.7190
Epoch 22/200
12/12 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 11.9902 - mae: 2.4701 - val_loss: 17.3226 - val_mae: 2.8476
Epoch 23/200
12/12 ━━━━━━━━━━━━━━━━━━━━ 0s 1ms/step - loss: 12.7140 - mae: 2.4168 - val_loss: 17.7918 - val_mae: 2.8993
Epoch 24/200
12/12 ━━━━━━━━━━━━━━━━━━━━ 0s 2ms/step - loss: 15.0356 - mae: 2.5725 - val_loss: 16.6013 - val_mae: 2.6930
Epoch 24: early stopping
Restoring model weights from the end of the best epoch: 21.
Burada epoch'lardaki "val_loss" değerlerini inceleyiniz. Bu "val_loss" değerleri üst üste 3 kez iyileşmediğinde eğitim
sonlandırılmıştır ve en iyi değere ilişkin ağırlıklar modele yüklenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
2024-09-16 10:49:44 +03:00
EPOCHS = 200
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
2024-09-16 10:49:44 +03:00
highway_class = df.iloc[:, 8].to_numpy()
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse_output=False)
ohe_highway = ohe.fit_transform(highway_class.reshape(-1, 1))
dataset_y = df.iloc[:, -1].to_numpy()
df.drop([8, 13], axis=1, inplace=True)
dataset_x = pd.concat([df, pd.DataFrame(ohe_highway)], axis=1).to_numpy()
from sklearn.model_selection import train_test_split
2024-09-16 10:49:44 +03:00
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.1)
2024-09-16 10:49:44 +03:00
from sklearn.preprocessing import StandardScaler
2024-09-16 10:49:44 +03:00
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)
2024-09-16 10:49:44 +03:00
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Input
from tensorflow.keras.callbacks import EarlyStopping
2024-09-16 10:49:44 +03:00
model = Sequential(name='Boston-Housing-Prices')
model.add(Input((training_dataset_x.shape[1], ), name='Input'))
model.add(Dense(64, activation='relu', name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
2024-09-16 10:49:44 +03:00
model.compile('rmsprop', loss='mse', metrics=['mae'])
2024-09-16 10:49:44 +03:00
esc = EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True, verbose=1)
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=EPOCHS, validation_split=0.2, callbacks=[esc])
import matplotlib.pyplot as plt
2024-09-16 10:49:44 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=10)
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
2024-09-16 10:49:44 +03:00
plt.figure(figsize=(14, 6))
plt.title('Mean Absolute Error - Validation Mean Absolute Error Graph', fontsize=14, pad=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()
2024-09-16 10:49:44 +03:00
eval_result = model.evaluate(scaled_test_dataset_x , test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
2024-09-16 10:49:44 +03:00
predict_df = pd.read_csv('predict-boston-housing-prices.csv', delimiter=r'\s+', header=None)
2024-09-16 10:49:44 +03:00
highway_class = predict_df.iloc[:, 8].to_numpy()
ohe_highway = ohe.transform(highway_class.reshape(-1, 1))
predict_df.drop(8, axis=1, inplace=True)
predict_dataset_x = pd.concat([predict_df, pd.DataFrame(ohe_highway)], axis=1).to_numpy()
scaled_predict_dataset_x = ss.transform(predict_dataset_x )
predict_result = model.predict(scaled_predict_dataset_x)
for val in predict_result[:, 0]:
print(val)
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
Aşağıdaki örnekte CIFAR-10 veri kümesinde "val_categorical_accuracy" metirk değeri üst üste 5 kez 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.
Programı çalıştırdığımızda aşağıdaki gibi bir çıktı elde edilmiştir:
...
Epoch 11/200
1250/1250 ━━━━━━━━━━━━━━━━━━━━ 14s 11ms/step - categorical_accuracy: 0.8262 - loss: 0.5247 - val_categorical_accuracy: 0.6999 - val_loss: 1.1722
Epoch 12/200
1250/1250 ━━━━━━━━━━━━━━━━━━━━ 15s 12ms/step - categorical_accuracy: 0.8330 - loss: 0.5094 - val_categorical_accuracy: 0.7127 - val_loss: 1.0260
Epoch 13/200
1250/1250 ━━━━━━━━━━━━━━━━━━━━ 14s 11ms/step - categorical_accuracy: 0.8380 - loss: 0.4906 - val_categorical_accuracy: 0.6487 - val_loss: 1.5351
Epoch 14/200
1250/1250 ━━━━━━━━━━━━━━━━━━━━ 14s 11ms/step - categorical_accuracy: 0.8349 - loss: 0.4990 - val_categorical_accuracy: 0.7041 - val_loss: 1.2637
Epoch 15/200
1250/1250 ━━━━━━━━━━━━━━━━━━━━ 14s 11ms/step - categorical_accuracy: 0.8427 - loss: 0.4849 - val_categorical_accuracy: 0.7015 - val_loss: 1.2270
Epoch 16/200
1250/1250 ━━━━━━━━━━━━━━━━━━━━ 14s 11ms/step - categorical_accuracy: 0.8454 - loss: 0.4817 - val_categorical_accuracy: 0.6969 - val_loss: 1.2923
Epoch 17/200
1250/1250 ━━━━━━━━━━━━━━━━━━━━ 14s 12ms/step - categorical_accuracy: 0.8418 - loss: 0.4924 - val_categorical_accuracy: 0.6973 - val_loss: 1.7678
Epoch 17: early stopping
Restoring model weights from the end of the best epoch: 12.
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
import glob
EPOCHS = 200
from tensorflow.keras.datasets import cifar10
(training_dataset_x, training_dataset_y), (test_dataset_x, test_dataset_y) = cifar10.load_data()
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']
2024-09-16 10:49:44 +03:00
import matplotlib.pyplot as plt
2024-09-16 10:49:44 +03:00
plt.figure(figsize=(4, 20))
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()
2024-09-16 10:49:44 +03:00
scaled_training_dataset_x = training_dataset_x / 255
scaled_test_dataset_x = test_dataset_x / 255
2024-09-16 10:49:44 +03:00
from tensorflow.keras.utils import to_categorical
2024-09-16 10:49:44 +03:00
ohe_training_dataset_y = to_categorical(training_dataset_y)
ohe_test_dataset_y = to_categorical(test_dataset_y)
2024-09-16 10:49:44 +03:00
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Input, Dense, Conv2D, MaxPooling2D, Flatten
from tensorflow.keras.callbacks import EarlyStopping
2024-09-16 10:49:44 +03:00
model = Sequential(name='CIFAR10')
model.add(Input((32, 32, 3), name='Input'))
model.add(Conv2D(32, (3, 3), activation='relu', name='Conv2D-1'))
model.add(MaxPooling2D(name='MaxPooling2D-1'))
model.add(Conv2D(64, (3, 3), activation='relu', name='Conv2D-2'))
model.add(MaxPooling2D(name='MaxPooling2D-2'))
model.add(Conv2D(128, (3, 3), activation='relu', name='Conv2D-3'))
model.add(MaxPooling2D(name='MaxPooling2D-3'))
model.add(Flatten(name='Flatten'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, activation='relu', name='Hidden-2'))
model.add(Dense(10, activation='softmax', name='Output'))
model.summary()
2024-09-16 10:49:44 +03:00
model.compile('rmsprop', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
2024-09-16 10:49:44 +03:00
esc = EarlyStopping(monitor='val_categorical_accuracy', patience=5, restore_best_weights=True, verbose=1)
hist = model.fit(scaled_training_dataset_x, ohe_training_dataset_y, batch_size=32, epochs=EPOCHS, validation_split=0.2, callbacks=[esc])
2024-09-16 10:49:44 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=10)
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
2024-09-16 10:49:44 +03:00
plt.figure(figsize=(14, 6))
plt.title('Categorcal Accuracy - Validation Categorical Accuracy', fontsize=14, pad=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()
2024-09-16 10:49:44 +03:00
eval_result = model.evaluate(scaled_test_dataset_x , ohe_test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
2024-09-16 10:49:44 +03:00
# prediction
import numpy as np
import os
count = 0
hit_count = 0
for path in glob.glob('Predict-Pictures/*.*'):
image = plt.imread(path)
scaled_image = image / 255
model_result = model.predict(scaled_image.reshape(-1, 32, 32, 3), verbose=0)
predict_result = np.argmax(model_result)
fname = os.path.basename(path)
real_class = fname[:fname.index('-')]
predict_class = class_names[predict_result]
print(f'Real class: {real_class}, Predicted Class: {predict_class}, Path: {path}')
if real_class == predict_class:
hit_count += 1
count += 1
print('-' * 20)
print(f'Prediction accuracy: {hit_count / count}')
#----------------------------------------------------------------------------------------------------------------------------
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',
2024-09-16 10:49:44 +03:00
save_freq='epoch',
initial_value_threshold=None
)
2024-09-16 10:49:44 +03:00
Metodun birinci parametresi modelin save edileceği dosyanın yol ifadesini alır. Bu parametredeki isim formatlı (yani kalıp
içeren biçimde) olabilmektedir. Metot birden fazla save işlemi yapacaksa bu parametrede dosya ismi kalıp içeren biçimde
kullanılmalıdır. İkinci parametre yine izlenecek loss ya da metrik değeri belirtmektedir. Yani bu parametre save işleminin
hangi loss ya da metrik değere dayalı olarak yapılacağını belirtmektedir. Yine verbose parametresi 1 geçilirse daha fazla
bilgi ekrana yazdırılmaktadır. Metodun 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 katmanlardaki nöron ağırlıkları save edilir. Ö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:
2024-09-16 10:49:44 +03:00
mcp = ModelCheckpoint('boston-checkpoint.keras', monitor='val_loss', save_best_only=True)
2024-09-16 10:49:44 +03:00
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. Ancak tabii bu callback sınıfı EarlyStopping callback
sınıfıyla birlikte de kullanılabilir. Metot aslında birden fazla save işlemi yapabilecek biçimde tasarlanmıştır. Ancak bunun
için metodun birinci parametresine bir kalıp girilmelidir. Eğer metodun birinci parametresine bir kalıp girilirse ve metodun
save_best_only parametresi False geçilirse tüm epoch'lardaki ağırlıklar formatlama kalıba uygun dosya isimleri ile save edilir.
Eğer save_best_only parametresi True geçilirse bu durumda yalnızca daha öncekine göre daha iyi olan epoch değerleri kalıba
uygun dosya isimleriyle save edilmektedir. Eğer dosya isminde bir kalıp kullanılmazsa bu durumda save_best_only parametresi
False geçilirse son epoch'taki değerler save edilir. Eğer dosya isminde kalıp 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 kalıp aslında "save işlemini
başka bir dosya üzerinde yap" anlamına gelmektedir. Bu durumu özetle şöyle ifade edebiliriz:
2024-09-16 10:49:44 +03:00
- Metodun birinci parametresine kalıp girilirse ve save_best_only parametresi False geçilirse: Bu durumda her epoch'ta
kalıba uygun save işlemi yapılmaktadır.
2024-09-16 10:49:44 +03:00
- Metodun birinci parametresine kalıp girilirse ve save_best_only parametresi True geçilirse: Bu durumda yalnızca daha
öncekine göre daha iyi olan epoch değerleri kalıba uygun dosya isimleriyle save edilmektedir.
2024-09-16 10:49:44 +03:00
- Metodun birinci parametresine kalıp girilmezse ve save_best_only parametresi False geçilirse: Bu durumda son epoch'taki
değerler save edilir.
2024-09-16 10:49:44 +03:00
Metodun birinci parametresine kalıp girilmezse ve save_best_only parametresi True geçilirse: Bu durumda yalnızca en iyi
monitor değerleri save edilir.
2024-09-16 10:49:44 +03:00
Kalıp olulştururken "{epoch}" ifadesi o andaki epoch değerini temsil eder. Örneğin "{epoch:03d}" gibi bir kalıp epoch
değerini iki basamak olarak (tek basamaksa 0 ile doldurarak)" oluşturma anlamına gelir. Diğer kalıp ifadeleri için sınıfın
dokümanlarına başvurabilirsiniz. Örneğin:
2024-09-16 10:49:44 +03:00
mcp = ModelCheckpoint('boston-checkpoint-{epoch:03d}.keras', monitor='val_loss', save_best_only=True)
2024-09-16 10:49:44 +03:00
Burada "val_loss" metrik değeri daha öncekilere göre iyi olan epoch ile karşılaşıldığında "boston-checkpoint-NNN" gibi
(burada NN epoch numarasını belirtir) bir dosyaya save işlemi yapılacaktır.
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
#----------------------------------------------------------------------------------------------------------------------------
61. Ders - 08/09/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Aşağıdaki örnekte "Boston Haousing Prices" veri kümesinde EarlyStopping ve ModelCheckpoint sınıfları bir arada kullanılmıştır.
Kodun ilgili kısmı şöyledir:
2024-09-16 10:49:44 +03:00
mcp = ModelCheckpoint('Boston-Housing-{epoch:03d}.keras', monitor='val_loss', save_best_only=True)
esc = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True, verbose=1, mode='min')
2024-09-16 10:49:44 +03:00
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=EPOCHS,
validation_split=0.2, callbacks=[mcp, esc])
2024-09-16 10:49:44 +03:00
Burada "val_loss" değerinde her yeni iyileşmede model "Boston-Housing-NNN.keras" ismiyle save edilecektir. Aynı zamanda
5 kez üst üste "val_loss" değeri iyileşmediği takdirde eğitim sonlandırılacaktır.
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
import pandas as pd
2024-09-16 10:49:44 +03:00
EPOCHS = 200
2024-09-16 10:49:44 +03:00
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
2024-09-16 10:49:44 +03:00
highway_class = df.iloc[:, 8].to_numpy()
2024-09-16 10:49:44 +03:00
from sklearn.preprocessing import OneHotEncoder
2024-09-16 10:49:44 +03:00
ohe = OneHotEncoder(sparse=False)
ohe_highway = ohe.fit_transform(highway_class.reshape(-1, 1))
2024-09-16 10:49:44 +03:00
dataset_y = df.iloc[:, -1].to_numpy()
df.drop([8, 13], axis=1, inplace=True)
dataset_x = pd.concat([df, pd.DataFrame(ohe_highway)], axis=1).to_numpy()
2024-09-16 10:49:44 +03:00
from sklearn.model_selection import train_test_split
2024-09-16 10:49:44 +03:00
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.1)
2024-09-16 10:49:44 +03:00
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.models import Sequential
from tensorflow.keras.layers import Dense, Input
from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping
model = Sequential(name='Boston-Housing-Prices')
model.add(Input((training_dataset_x.shape[1], ), name='Input'))
model.add(Dense(64, activation='relu', name='Hidden-1'))
model.add(Dense(64, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='linear', name='Output'))
model.summary()
model.compile('rmsprop', loss='mse', metrics=['mae'])
mcp = ModelCheckpoint('Boston-Housing-{epoch:03d}.keras', monitor='val_loss', save_best_only=True)
esc = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True, verbose=1, mode='min')
hist = model.fit(scaled_training_dataset_x, training_dataset_y, batch_size=32, epochs=EPOCHS, validation_split=0.2, callbacks=[mcp, esc])
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=10)
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
2024-09-16 10:49:44 +03:00
plt.figure(figsize=(14, 6))
plt.title('Mean Absolute Error - Validation Mean Absolute Error Graph', fontsize=14, pad=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()
2024-09-16 10:49:44 +03:00
eval_result = model.evaluate(scaled_test_dataset_x , test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
2024-09-16 10:49:44 +03:00
predict_df = pd.read_csv('predict-boston-housing-prices.csv', delimiter=r'\s+', header=None)
2024-09-16 10:49:44 +03:00
highway_class = predict_df.iloc[:, 8].to_numpy()
ohe_highway = ohe.transform(highway_class.reshape(-1, 1))
predict_df.drop(8, axis=1, inplace=True)
predict_dataset_x = pd.concat([predict_df, pd.DataFrame(ohe_highway)], axis=1).to_numpy()
scaled_predict_dataset_x = ss.transform(predict_dataset_x )
predict_result = model.predict(scaled_predict_dataset_x)
2024-09-16 10:49:44 +03:00
for val in predict_result[:, 0]:
print(val)
2024-09-16 10:49:44 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Kursumuzun bu noktasında "Aktarım Öğrenmesi (Transfer Learning)" konusuna bir giriş yapacağız. Sonraki bölümlerde aktarım
öğrenmesini çeşitli biçimlerde kullanacağız.
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Aktarım öğrenmesi (transfer learning) psikolojiden aktarılmış bir terimdir. Psikolojide aktarım öğrenmesi "daha önce öğrenilmiş
olan şeylerin başka öğrenmeleri etkilemesi sürecini" belirtmektedir. Örneğin İngilizce bilen bir kişi Almanca'yı (farklı
diller olduğu halde) daha kolay öğrenebilmektedir. Psikolojide aktarım öğrenmesi pozitif ya da negatif olabilmektedir.
Eğer önceden öğrenilen malzemeler sonradan öğrenilecekleri olumlu biçimde destekliyorsa buna "pozitif aktarım" olumsuz
bir biçimde etkiliyorsa buna da "negatif aktarım" denilmektedir. Örneğin Q-Klavyede yazan bir kişinin F-Klavyeye geçmesi
hiç klavye kullanmamış kişilere göre daha zor olabilmektedir. İşte makine öğrenmesinde "aktarım öğrenmesi" de psikolojide
olduğu gibi önceden öğrenilmiş malzemenin sonraki öğrenmede olumu bir biçimde kullanılması anlamına gelmektedir. Aktarım
öğrenmesi sayesinde önceden eğitilmiş (pretrained) ağların başka amaçlarla kullanılması sağlanmaktadır. Örneğin çok geniş
bir resim veritabanı kullanılarak sınıflandırma amacıyla bir eğitim yapılmış olabilir. Bu eğitimdeki nöron ağırlıkları
save edilmiş olabilir. Biz de kendi resim sınıflandırmamızda bu eğitilmiş modelden faydalanabiliriz. Tabii buradaki
eğitilmiş modelin bizim hedefimize yönelik eğitilmiş olması da aslında gerekmemektedir. Örneğin eğitilmiş model resimleri
100 farklı sınıfa ayırmak üzere eğitilmiş olabilir. Biz bu modeli farklı sınıflar için de yine kullanabiliriz. Çünkü
bu tür modellerde aslında gerekli olan pek çok faaliyet (filtreleme, evirişim gibi) zaten yapılmış durumdadır. Her ne kadar
eğitilmiş model bizim hedeflerimiz için eğitilmemiş olsa da yine bizim modelimizde önemli faydalar sağlayabilecektir.
2024-09-16 10:49:44 +03:00
Aktarım öğrenmesi resimsel uygulamalarda, metinsel uygulamalarda, işitsel uygulamalarda yaygın bir biçimde kullanılmaktadır.
Şüphesiz aktarım öğrenmesi konusu "önceden eğitilmiş (pre-trained)" modeller konusuyla iç içe girmiş bir konudur. Tabii biz
Keras'ta önceden eğitilmiş modelleri kullanamdan da başkalarının oluşturdğu modelleri kendi modelimize monte ederek
kullanabiliriz.
Tipik olarak önceden eğitilmiş modellerle aktarım öğrenmesi şu aşamalardan geçilerek gerçekleştirilmektedir:
2024-09-16 10:49:44 +03:00
1) Aktarım öğrenmesi için uygun eğitilmiş modelin belirlenmesi: Çeşitli kurumlar tarafından farklı amaçlarla farklı modeller
kullanılarak önceden eğitilmiş modeller oluşturulmuştur. Bunlardan uygun olanını uygulamacının seçmesi gerekmektedir.
2024-09-16 10:49:44 +03:00
2) Önceden eğitilmiş modelin çıktısının uygulamacının özel modeline bağlanması: Genellikle önceden eğitilmiş modeller
sinir ağının ilk katmanları olarak kullanılmaktadır. Uygulamacı kendi modeli için kendi sinir ağı katmanlarını oluşturup
önceden eğitilmiş modelin çıktısını kendi modeline bağlamalıdır.
2024-09-16 10:49:44 +03:00
Girdiler ---> önceden eğitilmiş model ---> uygulamacının kendi amaçları için oluşturduğu model ---> çıktılar
2024-09-16 10:49:44 +03:00
3) Modelin uygulamacının hedeflerine yönelik eğitilmesi: Her ne kadar uygulamacı modelinin önüne önceden eğitilmiş modeli
eklemiş olsa da modelin yine uygulamacının hedeflerine yönelik eğitilmesi gerekmektedir. Yani uygulamacı yine modelini
kendi verileriyle ayrıca eğitmelidir. Tabii şüphesiz eğer önceden eğitilmiş model zaten uygulamacının hedefleriyle tam
örtüşüyorsa ayrıca böyle bir eğitimin yapılmasına gerek de kalmaz.
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
#----------------------------------------------------------------------------------------------------------------------------
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
ın girdisi hem bir resim hem de bir yazı hem de birtakım sayısal verilerden oluşabilmektedir. Benzer biçimde ağın çıktı da
hem bir kategorik değer hem de gerçek bir değerden oluşabilmektedir. Örneğin 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 bulunuyor olması gerekmektedir. Halbuki Sequential modelde modelin tek
bir girdi katmanı olmak zorundadır. 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ı tespit 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 bu tür uygulamalarda daha aşağı seviyeli olan "fonksiyonel model" tercih edilmektedir. Fonksiyonel
model aslında Tensorflow'daki gerçek modeldir. Yani aslında Tensorflow zaten bu biçimde tasarlanmıoş olan temel (base) bir
kütüphanedir. Sequential model aslında bazı işlemleri kolaylaştırmak için düşünülmüş olan yüksek seviyeli bir tasarımdır.
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
#----------------------------------------------------------------------------------------------------------------------------
62. Ders - 14/09/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Asında Tensorflow'daki katman nesneleri girdiyi işleme sokup çıktı oluşturmaktadır. Bu katman nesnelerinde bu işlem ilgili
katman sınıfının fonksiyon çağırma operatör metodu ile (yani __call__ metodu ile) yapılmaktadır. Örneğin:
2024-09-16 10:49:44 +03:00
dense1 = Dense(256, activation='relu', name='Dense-1')
dense2 = Dense(256, activation='relu', name='Dense-2')
2024-09-16 10:49:44 +03:00
Burada aslında asıl nöron işlemlerini Dense sınıfının __call__ metodu yapmaktadır. Bu __call__ metoduna nöronların girdi
değerleri verilir. Metot da onları nöral işlemlere sokarakbir çıktı verir. Örneğin:
2024-09-16 10:49:44 +03:00
result = dense1(data)
2024-09-16 10:49:44 +03:00
Şimdi bu çıktıyı biz diğer Dense katman nesnesine girdi olarak verebiliriz:
2024-09-16 10:49:44 +03:00
result = dense2(result)
2024-09-16 10:49:44 +03:00
Yani aslında Sequential model yukarıdaki gibi bir katmanın çıktısı diğer katmana girdi yapılarak oluşturulmuştur. Örneğin:
2024-09-16 10:49:44 +03:00
inp = Input(...)
d1 = Dense(...)
d2 = Dense(...)
d3 = Dense(...)
d4 = Dense(...)
2024-09-16 10:49:44 +03:00
result = d1(inp)
result = d2(result)
result = d3(result)
out = d4(result)
2024-09-16 10:49:44 +03:00
Yukarıdaki işlemleri daha kompakt olarak aşağıdaki gibi de yapabiliriz:
2024-09-16 10:49:44 +03:00
inp = Input(...)
result = Dense(...)(inp)
result = Dense(...)(result)
result = Dense(...)(result)
out = Dense(...)(result)
2024-09-16 10:49:44 +03:00
Burada önemli bir nokta üzerinde durmak istiyoruz. Tensorflow ve PyTorch gibi kütüphaneler bir çeşit "meta programlama"
kütüphaneleridir. Yani bu programlama modelinde önce işlemi yapacak kodlar oluşturulur. Sonra onlar çalıştırılır. Biz yukarıda
hangi işlemlerin yapılacağını tanımlamış olduk. Ancak gerçekte henüz bu modele bir veri verip çıktısını almadık. Başka bir
deyişle biz yukrıda istediğimiz işlemleri yapan bir program oluşturmuş olduk. Fakat henüz onu çalıştırmadık. Tensorflow
kütüphanesinin 2'li versiyonlarıyla birlikte "eager tensor" adı altında doğrudan çalıştırmalı tensör modeli de kütüphaneye
eklenmiştir. Bu konuların ayrıntıları Tensorflow kütüphanesinin anlatıldığı bölümde ele alınacaktır.
2024-09-16 10:49:44 +03:00
Örneğin biz bir Dense katmanı tamamen ayrı bir biçimde işletmek isteyelim. Bu durumda Tensorflow'un 2'li versiyonlarından
sonra artık biz bu işlemi sanki Dense nesnesiyle fonksiyon çağırıyormuş gibi yapabiliriz. Örneğin:
2024-09-16 10:49:44 +03:00
import numpy as np
from tensorflow.keras.layers import Dense
2024-09-16 10:49:44 +03:00
data = np.random.random((32, 8))
2024-09-16 10:49:44 +03:00
d = Dense(16, activation='relu', name='Dense')
result = d(data).numpy()
2024-09-16 10:49:44 +03:00
Katman nesnelerinin bir grup satırı (batch) alıp işlem yaptığını anımsayınız. Yani biz Dense katmana tek bir satırı değil
bir grup satırı girdi olarak vermeliyiz. Yukarıdaki örnekte her biri 8 sütundan 32 satırdan oluşan rastgele bir NumPy dizisi
oluşturulup bu dizi Dense katmana verilmiştir. Tensorflow'da katman nesneleri Tensor alıp Tensor vermektedir. Ancak Tensor
yerine bazı katman nesneleri NumPy dizilerini de girdi olarak alabilmektedir. Örneğimizde çıktı olarak aslında bir Tensor
nesnesi elde edilmiştir. Biz de bu Tensor nesnesini yeniden NumPy dizisine dönüştürdük. Yukarıdaki örnekte elde ettiğimiz
NumPy dizisi (32, 16) boyutlarında olacaktır.
2024-09-16 10:49:44 +03:00
Fonksiyonel olarak oluşturduğumuz yapıya dikkat ediniz:
2024-09-16 10:49:44 +03:00
inp = Input(...)
result = Dense(...)(inp)
result = Dense(...)(result)
result = Dense(...)(result)
out = Dense(result)
2024-09-16 10:49:44 +03:00
Burada sonuçta bir girdi bir de çıktı tensörü oluşturulmuştur. İşlemlerin yapılabilmesi için bu girdi ve çıktı tensörleri ile
bir Model nesnesinin yaratılması 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:
2024-09-16 10:49:44 +03:00
model = Model(inputs=inp, outputs=out, name='MyModel')
2024-09-16 10:49:44 +03:00
Aslında burada oluşturmaya çalıştığımız modelin Sequential eşdeğeri şöyledir:
2024-09-16 10:49:44 +03:00
model = Sequential('MyModel')
model.add(Input(...))
model.add(Dense(...)
model.add(Dense(...))
model.add(Dense(...))
model.add(Dense(...))
2024-09-16 10:49:44 +03:00
Aslında asıl olan model fonksiyonel modeldir. Sequential sınıfı fonksiyonel model kullanılarak yazılmış olan yüksek seviyeli
yardımcı bir sınıftır. Ancak önceki paragraflarda da belirttiğimiz gibi Sequential model bazı uygulamalarda yetersiz kalmaktadır.
Yani aslında Sequential sınıfında add işlemi yapıldıkça yukarıdaki gibi fonksiyonel modele fonksiyon çağırma operatöryle eklemeler
yapılmaktadır. Bir fikir vermesi için Sequential sınıfının aşağıdaki biçimde yazılmış olduğunu varsayabilirsiniz:
2024-09-16 10:49:44 +03:00
class Sequential:
def __init__(self):
self.result = None
def add(self, layer):
if self.result is None:
self.inp = layer
self.result = self.inp
else:
self.result = self.result(layer)
def compile(self, *args):
self.model = Model(inputs=self.inp, outputs=self.result)
# ....
Tensör kavramı ve konunun ayrıntıları kurusumuzu Tensorflow kütüphanesinin anlatıldığı bölümde ele alınacaktır.
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
#----------------------------------------------------------------------------------------------------------------------------
Şimdi daha önce yapmış olduğumuz "iris" örneğini fonksiyonel modelle yeniden yapalım. Model şöyle kurulabilir:
2024-09-16 10:49:44 +03:00
from tensorflow.keras import Model
from tensorflow.keras.layers import Input, Dense
2024-09-16 10:49:44 +03:00
inp = Input((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(dataset_y.shape[1], activation='softmax', name='Output')(result)
2024-09-16 10:49:44 +03:00
model = Model(inputs=inp, outputs=out, name='FunctionalModel')
2024-09-16 10:49:44 +03:00
Görüldüğü gibi modelde bir girdi katmanı iki saklı katman ve bir de çıktı katmanı bulunmaktadır. Aşağıda örneğin tamamı
verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
import pandas as pd
2024-09-16 10:49:44 +03:00
df = pd.read_csv('Iris.csv')
2024-09-16 10:49:44 +03:00
dataset_x = df.iloc[:, 1:-1].to_numpy(dtype='float32')
2024-09-16 10:49:44 +03:00
from sklearn.preprocessing import OneHotEncoder
2024-09-16 10:49:44 +03:00
ohe = OneHotEncoder(sparse_output= False)
dataset_y = ohe.fit_transform(df.iloc[:, -1].to_numpy().reshape(-1, 1))
from sklearn.model_selection import train_test_split
2024-09-16 10:49:44 +03:00
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y, test_size=0.1)
2024-09-16 10:49:44 +03:00
from sklearn.preprocessing import StandardScaler
2024-09-16 10:49:44 +03:00
ss = StandardScaler()
ss.fit(training_dataset_x)
scaled_training_dataset_x = ss.transform(training_dataset_x)
2024-09-16 10:49:44 +03:00
from tensorflow.keras import Model
from tensorflow.keras.layers import Dense, Input
2024-09-16 10:49:44 +03:00
inp = Input((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(dataset_y.shape[1], activation='softmax', name='Output')(result)
2024-09-16 10:49:44 +03:00
model = Model(inputs=inp, outputs=out, name='FunctionalModel')
model.summary()
2024-09-16 10:49:44 +03:00
model.compile('rmsprop', loss='categorical_crossentropy', metrics=['categorical_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
2024-09-16 10:49:44 +03:00
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=10)
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
2024-09-16 10:49:44 +03:00
plt.figure(figsize=(14, 6))
plt.title('Categorcal Accuracy - Validation Categorical Accuracy', fontsize=14, pad=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()
2024-09-16 10:49:44 +03:00
scaled_test_dataset_x = ss.transform(test_dataset_x)
eval_result = model.evaluate(scaled_test_dataset_x , test_dataset_y, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
2024-09-16 10:49:44 +03:00
predict_dataset_x = pd.read_csv('predict-iris.csv').to_numpy(dtype='float32')
scaled_predict_dataset_x = ss.transform(predict_dataset_x)
import numpy as np
predict_result = model.predict(scaled_predict_dataset_x)
predict_indexes = np.argmax(predict_result, axis=1)
for pi in predict_indexes:
print(ohe.categories_[0][pi])
"""
predict_categories = ohe.categories_[0][predict_indexes]
print(predict_categories)
"""
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda daha önce yapmış olduğumuz "Boston Housing Prices" örneği fonksiyonel bir biçimde oluşturulmuştur. Modelin
oluşturulma biçimi önceki örnekle benzerdir:
from tensorflow.keras import Model
from tensorflow.keras.layers import Dense, Input
inp = Input((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, name='BostonHousingPrices')
model.summary()
#----------------------------------------------------------------------------------------------------------------------------
import pandas as pd
df = pd.read_csv('housing.csv', delimiter=r'\s+', header=None)
highway_class = df.iloc[:, 8].to_numpy()
from sklearn.preprocessing import OneHotEncoder
ohe = OneHotEncoder(sparse_output=False)
ohe_highway = ohe.fit_transform(highway_class.reshape(-1, 1))
dataset_y = df.iloc[:, -1].to_numpy()
df.drop([8, 13], axis=1, inplace=True)
dataset_x = pd.concat([df, pd.DataFrame(ohe_highway)], 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.1)
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 Model
from tensorflow.keras.layers import Dense, Input
inp = Input((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, name='BostonHousingPrices')
model.summary()
model.compile('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=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=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=(14, 6))
plt.title('Mean Absolute Error - Validation Mean Absolute Error Graph', fontsize=14, pad=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, batch_size=32)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
"""
import pickle
model.save('boston-housing-prices.h5')
with open('boston-housing-prices.pickle', 'wb') as f:
pickle.dump([ohe, ss], f)
"""
predict_df = pd.read_csv('predict-boston-housing-prices.csv', delimiter=r'\s+', header=None)
highway_class = predict_df.iloc[:, 8].to_numpy()
ohe_highway = ohe.transform(highway_class.reshape(-1, 1))
predict_df.drop(8, axis=1, inplace=True)
predict_dataset_x = pd.concat([predict_df, pd.DataFrame(ohe_highway)], axis=1).to_numpy()
scaled_predict_dataset_x = ss.transform(predict_dataset_x )
predict_result = model.predict(scaled_predict_dataset_x)
for val in predict_result[:, 0]:
print(val)
#----------------------------------------------------------------------------------------------------------------------------
2024-10-11 00:32:36 +03:00
Bir kestirim modelinde veriler farklı alanlara ilişkin olabilir. Örneğin veri kümesindeki sütunlardan biri bir yazı olabilir,
2024-09-16 10:49:44 +03:00
diğerleri sayısal sütunlar olabilir. Bu tür veri kümelerine "çok modaliteye sahip (multimodal)" ya da "karışık (mixed)"
veri kümeleri de denilmektedir. ("Multimodal" sözcüğü aslında "psikoloji" ve "bişilsel bilimlerden" aktarılmış bir terimdir.
Buradaki "modalite"" farklı duyu organlarına hitap eden bilgiler anlamına gelmektedir.) Önceki paragraflarda da belirttiğimiz
gibi karışık veri kümelerinde Sequential model kullanılamamaktadır. Bu tür durumlarda mecburen fonksiyonel modelin kullanılması
gerekmektedir. Farklı alanlardaki girdilerin fonksiyonel modelle oluşturulabilmesi birenden fazla girdi katmanının bulundurulması
gerekir. Tipik olarak bu girdi karmanlarına farklı işlemler uygulandıktan sonra bunlar birleştirilirler. Birleştirme işlemi için
Concatenate katmanı kullanılmaktadır. Concatenate katmanı yine fonksiyonel biçimde kullanılabilmektedir. Örneğin:
inp1 = Input(...)
...
inp2 = Input(...)
...
result = Concatenate()([inp1, inp2])
result = Dense(...)(result)
result = Dense(...)(result)
out = Dense(...)(result)
Aslında Concatenate katmanının yanı sıra tensorflow.keras modülünde aynı zamanda concatenate isminde bir fonksiyon da vardır.
Concatenate katmanı yerine concatenate fonksiyonu da kullanılabilir:
inp1 = Input(...)
...
inp2 = Input(...)
...
result = concatenate([inp1, inp2])
result = Dense(...)(result)
result = Dense(...)(result)
out = Dense(...)(result)
Bu biçimde birden fazla girdi katmanının olduğu durumda Model nesnesi yaratılırken inputs parametresine girdi katmanları
bir liste biçiminde (liste olması şart değil)verilmelidir. Örneğin:
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, testi ve kestirimi nasıl yapılacaktır? İşte bu işlemlerde bizim
girdileri bir liste ile (liste olmak zorunda değil) ayrı ayrı vermemiz gerekir.
model.fit([training_dataset_x1, training_dataset_x2], training_dataset_y, ...)
Tabii yukarıdaki gibi iki girişli bir modelde aslında x verileri de iki parçadan oluşacaktır. Burada training_dataset_x1 ve
training_dataset_x2 veri kümeleri bu parçaları temsil etmektedir. Benzer biçimde modelin test edilmesi sırasında da evaluate
metodunda yine x verileri bir liste biçiminde verilmelidir:
eval_result = model.evaluate([test_dataset_x1, test_dataset_x2], test_dataset_y)
Burada test verilerinin de iki parça haline oluşturulduğuna dikkat ediniz. test_datset_x1 ve test_dataset_x2 bu parçaları
temsil etmektedir.
Benzer biçimde kestirim işleminde de predict metodunda x verileri bir liste biçiminde (liste olmak zorunda değil) girilir.
Örneğin:
predict_result = model.predict([predict_dataset_x1, predict_dataset_x2])
Burada predict_dataset_x1 ve predict_dataset_x2 bu parçaları temsil etmektedir.
Birden fazla girdiye sahip olan modellerde özellik ölçeklemesi iki model birleştirildiğinde uyumlu olacak biçimde yapılmalıdır.
Bunun için girişlere tür olarak aynı özellik ölçeklemesini uygulayabilirsiniz.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
63. Ders - 15/09/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda hem nünerik sütunların hem de yazısal sütunların kullanıldığı bir veri kğmesinde lojistik olmayan regresyon
örneği verilmiştir. Örnekte veri kümesinin nümerik kısmı ve yazısal kısmı farklı işlemlere sokulmuştur. Sonra bu kısımlar
Concateb-nate katmanıyla birleştirilmiş ve Dense katmanlardan sonra çıktı katmanı elde edilmiştir. Kodun ilgili kısmı
şöyledir:
inp1 = Input(shape=(training_dataset_x1.shape[1], ), name='Numeric-Input')
inp2 = Input(shape=(1, ), dtype='string', name='Text-Input')
tv = TextVectorization(output_mode='count')
tv.adapt(training_dataset_x2)
result = tv(inp2)
result = Concatenate()([inp1, result])
result = Dense(128, activation='relu', name='Hidden-1')(result)
result = Dense(128, activation='relu', name='Hidden-2')(result)
out = Dense(1, activation='linear', name='Output')(result)
model = Model(inputs=[inp1, inp2], outputs=[out], name='MixedRandomModel')
model.summary()
model.compile('rmsprop', loss='mse', metrics=['mae'])
hist = model.fit([scaled_training_dataset_x1, training_dataset_x2], training_dataset_y,
batch_size=32, epochs=100, validation_split=0.2)
Bu örnekte "dataset.csv" dosyası da "create-random-mixed-data.py" isimli program tarafından oluşturulmuştur. Bu programın
oluşturduğuğu CSV dosyası aşağıdaki gibi bir görünümdedir:
"Yaş","Gelir","Harcamalar","Kredi_Skoru","İnternet_Aboneliği","Yorum","Puan"
56,39930,21657,733,3,"Güzel hizmet, ama daha iyi olabilir.",326
69,33285,35347,622,19,"Harika ürünler!",889
46,65863,15314,721,10,"Ürünler kaliteli, ama fiyatlar yüksek.",481
32,46704,28840,740,14,"Yine de memnun kaldım.",770
60,48705,18662,683,19,"Yine de memnun kaldım.",803
25,30555,27754,705,11,"Beklentilerimin altında.",930
38,51323,17629,663,21,"Güzel hizmet, ama daha iyi olabilir.",946
56,96922,21324,724,6,"Beklediğimden daha iyi.",376
36,83915,35247,651,19,"Güzel hizmet, ama daha iyi olabilir.",651
40,40619,27558,647,14,"Kaliteli, ama fiyat biraz yüksek.",14
28,89274,26494,660,8,"İnternetten daha iyi bekliyordum.",899
28,51225,29943,616,2,"Harika ürünler!",695
41,56739,16692,639,22,"Harika ürünler!",480
.....
Kestirim için kullanılan CSV dosyasının içeriği de şöyledir:
"Yaş","Gelir","Harcamalar","Kredi_Skoru","İnternet_Aboneliği","Yorum"
61,37642,24599,716,18,"Yine de memnun kaldım."
47,85556,14872,735,11,"Beklediğimden daha iyi."
55,63760,37547,672,9,"İnternetten daha iyi bekliyordum."
19,68192,20573,644,8,"Yine de memnun kaldım."
38,34381,23691,616,21,"Hizmet çok iyi, teşekkürler."
50,49558,21482,676,8,"Kaliteli, ama fiyat biraz yüksek."
29,60782,14753,736,5,"Ürünler kaliteli, ama fiyatlar yüksek."
39,34782,11833,690,13,"Harika ürünler!"
61,84712,15928,705,7,"Beklentilerimin altında."
42,49462,18645,700,21,"İnternetten daha iyi bekliyordum."
66,90030,16816,702,5,"Yeterli bir deneyim."
44,98519,27124,623,13,"Ürünler kaliteli, ama fiyatlar yüksek."
59,86411,17256,672,20,"Beklentilerimin altında."
45,85206,28764,720,16,"Yine de memnun kaldım."
33,76950,20549,705,14,"Yeterli bir deneyim."
Aşağıda her iki program da verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
# create-random-mixed-data.py
import pandas as pd
import numpy as np
import csv
import random
# Random veri üretimi için seed
np.random.seed(42)
random.seed(42)
num_rows = 10000
data = {
'Yaş': np.random.randint(18, 70, size=num_rows),
'Gelir': np.random.randint(30000, 100000, size=num_rows),
'Harcamalar': np.random.randint(10000, 40000, size=num_rows),
'Kredi_Skoru': np.random.randint(600, 750, size=num_rows),
'İnternet_Aboneliği': np.random.randint(1, 25, size=num_rows),
'Yorum': [random.choice(["Harika ürünler!", "Güzel hizmet, ama daha iyi olabilir.",
"Beklentilerimin altında.", "Yine de memnun kaldım.", "Ürünler kaliteli, ama fiyatlar yüksek.",
"Yeterli bir deneyim.", "İnternetten daha iyi bekliyordum.", "Hizmet çok iyi, teşekkürler.",
"Beklediğimden daha iyi.", "Kaliteli, ama fiyat biraz yüksek."])
for _ in range(num_rows)],
'Puan': np.random.randint(0, 1000, size=num_rows),
}
df = pd.DataFrame(data)
df.to_csv('dataset.csv', quoting=csv.QUOTE_NONNUMERIC, index=False)
print("Veri kümesi 'dataset.csv' olarak kaydedildi.")
# mixed-numeric-text-random.py
import pandas as pd
dataset = pd.read_csv('dataset.csv')
dataset_x = dataset.iloc[:, :-1]
dataset_y = dataset.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.1)
training_dataset_x1 = training_dataset_x.iloc[:, :-1].to_numpy(dtype='float32')
training_dataset_x2 = training_dataset_x.iloc[:, -1]
test_dataset_x1 = test_dataset_x.iloc[:, :-1].to_numpy(dtype='float32')
test_dataset_x2 = test_dataset_x.iloc[:, -1]
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(training_dataset_x1)
scaled_training_dataset_x1 = ss.transform(training_dataset_x1)
scaled_test_dataset_x1 = ss.transform(test_dataset_x1)
from tensorflow.keras import Model
from tensorflow.keras.layers import Input, TextVectorization, Dense, Concatenate
inp1 = Input(shape=(training_dataset_x1.shape[1], ), name='Numeric-Input')
inp2 = Input(shape=(1, ), dtype='string', name='Text-Input')
tv = TextVectorization(output_mode='count')
tv.adapt(training_dataset_x2)
result = tv(inp2)
result = Concatenate()([inp1, result])
result = Dense(128, activation='relu', name='Hidden-1')(result)
result = Dense(128, activation='relu', name='Hidden-2')(result)
out = Dense(1, activation='linear', name='Output')(result)
model = Model(inputs=[inp1, inp2], outputs=[out], name='MixedRandomModel')
model.summary()
model.compile('rmsprop', loss='mse', metrics=['mae'])
hist = model.fit([scaled_training_dataset_x1, training_dataset_x2], training_dataset_y, batch_size=32, epochs=100, validation_split=0.2)
import matplotlib.pyplot as plt
plt.figure(figsize=(14, 6))
plt.title('Epoch - Loss Graph', fontsize=14, pad=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=(14, 6))
plt.title('Mean Absolute Error - Validation Mean Absolute Error Graph', fontsize=14, pad=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_x1, test_dataset_x2], test_dataset_y)
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_dataset = pd.read_csv('predict.csv')
predict_dataset_x1 = predict_dataset.iloc[:, :-1]
predict_dataset_x2 = predict_dataset.iloc[:, -1]
scaled_predict_dataset_x1 = ss.transform(predict_dataset_x1)
predict_result = model.predict([scaled_predict_dataset_x1, predict_dataset_x2])
for val in predict_result[:, 0]:
print(val)
#----------------------------------------------------------------------------------------------------------------------------
Makine öğrenmesinde çok çıkışlı modeller çok girişli modellere göre daha seyrek kullanılmaktadır. Ancak biz burada çok
çıkışlı modeller üzerinde de örnek vermek istiyoruz. Çok çıkışlı modeller de yine Sequential model ile oluşturulamamaktadır.
Çok çıkışlı modeller ancak fonksiyonel biçimde oluşturulmaktadır. Örneğin bir modelin iki çıktısı olabilir. Çıktıların bir
tanesi "olumlu", "olumsuz" biçiminde iki sınıflı kategorik bir çıktı iken diğeri bir regresyon çıktısı olabilir. Bunu şekilsel
olarak şöyle gösterbiliriz:
Dense (sigmoid activation, binary_crossentropy loss, binary_accuracy metrics)
Input --> Dense ---> Dense
Dense (linear activation, mean_squared_error loss, mean_absolte_error metrics)
Bu modelin oluşturulması aşağıdaki gibi yapılabilir:
inp = Input(..., name='Input')
result = Dense(..., name='Hidden-1')(inp)
result = Dense(..., name=''Hidden-2)(result)
out1 = Dense(..., name='Output-1')(result)
out2 = Dense(..., name='Output-2')(result)
model = Model(inputs=inp, outputs=[out1, out2])
Burada Model nesnesi oluşturulurken outputs parametresinin iki çıkışı içeren bir liste olduğuna (liste olmak zorunda değil)
dikkat ediniz. Pekiyi bu model nasıl derlenecek ve eğitilecektir?
Çok çıkışlı modellerde en küçüklenmeye çalışılan loss fonksiyonu nasıl olacaktır? Bilindiği gibi loss fonksiyonu problemin
türüne göre farklı seçilmektedir. Örneğin modelde çıktılardan biri ikili sınıflandırmaya ilişkinse o çıktı için "binary-crossentropy"
loss fonksiyonunu kullanmak gerekir. Çıktılardan diğeri (lojistik olmayan) regresyona ilişkinse bu çıktı için de "mean_squred_error"
loss fonksiyonunu kullanmak gerekir. Biz şimdiye kadar compile metodunda loss fonksiyonu olarak bir tane fonksiyon belirttik.
Ancak aslında çok çıkışlı modellerde compile metonda her çıkış için ayrı bir loss fonksiyonu verilebilmektedir. Bunun için
loss parametresinde bir liste (liste olmak zorunda değil) ya da sözlük girilebilir. Eğer loss fonksiyonları liste biçimde 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
toplamanı minimize etmeye çalışmaktadır. Ancak loss fonksiyonlarının verdiği değerlerin skalaları farklı olabilir. Bu durumda
çıktıların önem dereceleri (yani bir çeşit ağırlıklı ortalamaları) compile metodunun loss_weights parametresi ile belirtilebilmektedir.
Bu parametreye ağırlıklı ortalama için gereken ağırlık değerleri verilir. Örneğin:
model.compile(optimizer='rmsprop', loss={'Output-1': 'binary_corssentropy, 'Output-2': 'mse'},
loss_weights={'Output-1': 1, 'Output-2': 0.1}, ...)
Bu örnekte toplam loss değeri hesaplanırken birinci çıkışa ilişkin loss değeri 1 ile ikinci çıkışa ilişkin loss değeri 0.1 ile
çarpılarak toplanmaktadır. Default durumda sanki bu ağırlık çarpanlarının 1 olduğu düşünülebilir.
Çok çıktılı modellerde genellikle birden fazla metrik değer kullanılır. Çünkü 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 (liste olmak
zorunda değil) 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. Çıktılara ilişkin y değerleri yine bir liste
biçiminde (liste olmak zorunda değil) girilmelidir. Ö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 bize bir liste vermektedir. Listenin elemanları sırasıyla çıktı değerlerinden oluşmaktadır. Tabii bu çıktı
değerleri de aslında iki boyutlu bir NumPy dizilerş 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ğıda örnekte bir tane girdi ve iki tane çıktı olan bir model örneği verilmiştir. 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.0, 'Output-2': 1.0}, 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).astype('float32')
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])
print(eval_result)
# 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])
#----------------------------------------------------------------------------------------------------------------------------
64. Ders - 21/09/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yazıların sınıflandırılması ve onlardan anlam çıkartılması için kullanılan önemli tekniklerden biri de "word embedding"
2024-10-03 14:16:20 +03:00
denilen tekniktir. Biz şimdiye kadar IMDB ve Retures gibi örneklerde yazıları sütun sayısı tüm sözcük haznesi (vocabulary)
kadar olan vektörlerle temsil ettik. Bu yöntemin en önemli dezavantajları her yazının büyük bir vektörle ifade edilmesi 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" bu teknikten daha ileri bir tekniktir. Word embedding yukarıda belirttiğimiz iki dezavantajı azaltmaktadır. Yani
bu teknikle hem sözcükler arasında anlamsal bir ilişki kurulur hem de yazılar daha kısa vektörlerle temsil edilir.
Word embedding yönteminde yazı içerisindeki sözcüklerin her biri eşit uzunlukta gerçek değerlere sahip vektörlerle ifade
edilmektedir. Ö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?
2024-10-03 14:16:20 +03:00
Word Embedding yönteminde sözcüklere ilişkin vektörler oluşturulduktan sonra bunların arasında Öklit uzaklıkları (Eucledian
distances) birbirine yakın sözcüklerin daha az biribirine uzak sözcüklerin daha fazla olacağı biçimdedir. Öklit uzaklığı iki
nokta arasındaki en kısa yola ilişkin uzaklıktır. Örneğin iki boyutlu uzayda (x1, y1) ve (x2, y2) noktaları arasındaki Öklit
uzaklığı sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) biçimindedir. Üç boyutlu uzayda (x1, yy1, z1) ve (x2, y2, z2) noktaları
arasındaki Öklit uzaklıkları ise sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2 + (z1 - z2) ** 2) biçiminde hesaplanmaktadır. Tabii n
boyutlu uzay için bu formül genelleştirilebilir. Örneğin sözcükleri iki elemanlı vektörlerle temsil edelim. Bu iki elemanlı
vektörler düzlemde (iki boyutlu uzayda) birer nokta belirtirler. Bu durumda birbirlerine yakın anlamdaki sözcükler düzlemde
birbirlerine daha yakın, uzak anlamdaki sözcükler birbirlerine 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 Word Embedding işlemlerinde bu vektörler nasıl oluşturulmaktadır? Bu konuda çeşitli algoritmalar önerilmiştir. Örneğin
Google'ın "Word2Vec" algoritması Stanford'ın "GloVe" algoritması Facebook'un "fastText" algoritması en fazla kullanılanlardandır.
Word2Vec algoritması 2013 yılında tasarlanmıştır. Metin anlamlandırmalarında önemli bir ilerleme sağlamıştır. Ancak Keras'ın
Embedding katmanı doğrudan bu algoritmaları kullanmaz. Ana fikir olarak bu bu algoritmalar temel alınmıştır ancak Embedding
katmanı bir öğrenme katmanı olarak çalışmaktadır. Biz burada bu algoritmaların üzerinde durmayacağız.
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 çıkartılması biçimindeki faaliyetlerde önemli iyileşme sağlamaktadır. Tabii
aslında istenirse daha önce oluşturulmuş olan hazır vektörler de doğrudan kullanılabilir. Bu sayede eğitim çok daha verimli
biçimde yapılabilmektedir.
Yukarıda da belirttiğimiz gibi Keras'ta word embedding işlemleri Embedding isimli katmanla yapılmaktadı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 sınıfının __init__
metodunun parametreleri şöyledir:
tf.keras.layers.Embedding(
input_dim,
output_dim,
embeddings_initializer='uniform',
embeddings_regularizer=None,
embeddings_constraint=None,
mask_zero=False,
weights=None,
lora_rank=None,
**kwargs
)
2024-09-16 10:49:44 +03:00
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ı gerekir. (Vektörizasyon işleminde zaten yazılar farklı miktarda sözcüklerden
oluşsa bile girdi vektörleri vocabulary kadar olduğu için girdiler doğal olarak aynı boyutta olmaktadır.) 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
eğer yazı küçükse yazının başının ya da sonunun boş sözcüklerle doldurulması işlemidir. Tabii yazı büyükse tam ters olarak yazının
başından ya da sonundan sözük atılmalıdır. weights parametresi ağırlık değerleri zaten bir biçimde uygulamacının elinde bulunuyorsa
o ağırlık değerleriyle katmanın set edilmesini sağlamaktadır. önceden belirlendiği biçimde verilmesini Embedding katmanının
kullanımına şöyle bir örnek verilebilir:
...
2024-09-16 10:49:44 +03:00
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. Eskiden Embedded katmanı aynı zamanda bir girdi katmanı gibi de kullanılmaktaydı. Ancak
Tensorflow'un ileri sürümlerinde artık girdi katmanının her zaman Input katmanıyla oluşturulması yöntemi benimsenmiştir.
Bu nedenle artık Embedding katmanındaki input_length parametresi "deprecated" yapılmıştır. Yani artık girdi büyüklüğünün
Input katmanıyla verilmesi yönteminin kullanılması önerilmektedir. Bu durumda Embedding katmanı aşağıdaki gibi oluşturulabilir:
2024-09-16 10:49:44 +03:00
...
model.add(Input((100, )))
model.add(Embedding(30000, 32))
...
2024-09-16 10:49:44 +03:00
Burada tüm yazılardaki tüm sözcüklerin sayısı 30000 tanedeir. Her sözük 32 eleman uzunluğundaki vektörle temsil edilmektedir.
Yazılar da 100 sözcük içermektedir.
2024-09-16 10:49:44 +03:00
Embedding katmanı sözcükleri vektörlere dönüştürmektedir. Pekiyi Embedding katmanının girdisi nasıl olmalıdı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.
2024-09-16 10:49:44 +03:00
Embedding katmanındaki eğitilebilir parametrelerin sayısı "vocabulary'deki sözcük sayısı * vektör uzunluğu" kadardır. Yani
yukarıdaki örnekte Embedding katmanındaki eğitilebilir parametrelerin sayısı 30000 * 32 tane olacaktır.
2024-09-16 10:49:44 +03:00
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 argüman NumPy dizilerinden oluşan listeler olabilir ya da listelerden oluşan listeler olabilir.) İkinci parametre
hedeflenen sütun uzunluğunu belirtir. (Yani bu parametre her yazının kaç sözcükle ifade edileceğini belirtmektedir.) dtype
parametresi hedef matristeki elemanların dtype türünü belirtmektedir. padding ve trucanting parametreleri padding ve kırpma
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. Bu değerin default olarak 0 biçiminde olduğuna dikat ediniz.
Bu durumda sözcük numaralarını 1'den aşlatabilirsiniz. pad_sequences işleminin sonucunda iki boyutlu bir NumPy dizisi elde
edilmektedir. Örneğin:
2024-09-16 10:49:44 +03:00
from tensorflow.keras.utils import pad_sequences
a = [[1, 2, 3], [3, 4, 5, 6, 7], [10], [11, 12]]
result = pad_sequences(a, 3, padding='post')
2024-09-16 10:49:44 +03:00
print(result)
Buradan şöyle bir çıktı elde edilecektir:
[[ 1 2 3]
[ 5 6 7]
[10 0 0]
[11 12 0]]
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Embedding katmanının çıktısı her bir yazı için iki boyutludur. Çıktı yazıdaki sözcük sayısı kadar satırdan, her sözcük için
de belirlenen vektör uzunluğu kadar sütundan oluşmaktadır. Burada bir noktaya dikkat ediniz: Embedding katmanı aslında tüm
vocabulary için vektörler oluşturmaktadır. Ancak çıktı olarak yazılardaki sözcüklere ilişkin vektörleri vermektedir. Anımsanacağı
2024-10-03 14:16:20 +03:00
gibi Dense katmanlarının girdilerinin tek boyutlu olması gerekiyordu. O halde bizim Embedding katmanının çıktısını Flatten
ya da Reshape katmanına sokarak onu tek boyutlu hale getirmemiz sonra Dense katmanlara vermemiz gerekir.
Örneğin:
Input --> Embedding --> Flatten/Reshape --> Dense --> Dense --> Dense (Çıktı katmanı)
Ancak maalesef kursun yapıldığı Keras versiyonunda Embedding katmanından sonra Flatten katmanının kullanılması bazı durumlarda
sorunlara yol açabilmektedir. Bunun bir böcek olduğunu düşünüyoruz. Bu nedenle biz örneklerimizde Embedding katmanının çıktısını
Reshape katmanına sokarak onu tek boyuta indirgeyeceğiz.
2024-10-03 14:16:20 +03:00
Şimdi daha önce vektörizasyon yöntemiyle yapmış olduğumuz IMDB örneğini word embedding ile yeniden yapalım. Örneğimizdeki
her yorumun 250 sözcükten oluştuğunu varsayalım. Bunu TEXT_SIZE değişkeni ile temsil edelim:
2024-10-03 14:16:20 +03:00
TEXT_SIZE = 250
2024-10-03 14:16:20 +03:00
Bizim ilk olarak tüm yazılardaki tüm sözcüklerden bir "sözcük haznesi (vocabulary)" elde etmemiz ve her sözcüğe bir indeks
numarası vermemiz gerekir. Biz daha önce işlemi birkaç kere yapmıştık. Anımsanacağı gibi CountVectorizer sınıfı zaten fit
işleminden sonra böyle bir sözlüğü bizim için oluşturuyordu. Örneğin:
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer()
cv.fit(df['review'])
2024-10-03 14:16:20 +03:00
Bu işlemden sonra artık cv nesnesinin vocabulary_ özniteliğinde vocabulary için bir sözlük oluşturulmuş durumdadır. Şimdi
bizim tüm yorumları sözcük indekslerinden oluşan liste listesi biçiminde ifade etmemiz gerekir. Bunun için yorumları tek tek
sözcüklere ayıracağız onlar yerine onların indekslerini atayacağız. Padding işlemleri için 0'ıncı indeksi boş bırakabiliriz.
Örneğin:
text_vectors = [[cv.vocabulary_[word] + 1 for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
Ancak burada her yazının indeks dizisi farklı uzunluktadır. İşte bizim pad_sequences fonksiyonu ile bunları eşit uzunluğa
getirmemiz gerekir:
from tensorflow.keras.utils import pad_sequences
dataset_x = pad_sequences(text_vectors, TEXT_SIZE, dtype='float32')
IMDB örneğinde anımsanacağı gibi her yazının pozitif ya da negatif yargı içerdiği tahmin edilmeye çalışılıyordu. O halde
dataset_y de şöyle oluşturulabilir:
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
Artık veri kümelerini eğitim ve test biçiminde iki kısma ayırabiliriz:
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)
Şimde de modelimizi oluşturalım:
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Embedding, Reshape, Dense
model = Sequential(name='IMBD-WordEmbedding')
model.add(Input((TEXT_SIZE, ), name='Input'))
model.add(Embedding(len(cv.vocabulary_), WORD_VECT_SIZE, name='Embedding'))
model.add(Reshape((-1, ), name='Reshape'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
Modelin summary metodu ile elde edilen özet bilgileri şöyledir:
2024-09-16 10:49:44 +03:00
Model: "IMBD-WordEmbedding"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ Embedding (Embedding) │ (None, 250, 64) │ 6,521,344 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Reshape (Reshape) │ (None, 16000) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Hidden-1 (Dense) │ (None, 256) │ 4,096,256 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Hidden-2 (Dense) │ (None, 256) │ 65,792 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Output (Dense) │ (None, 1) │ 257 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 10,683,649 (40.75 MB)
Trainable params: 10,683,649 (40.75 MB)
Non-trainable params: 0 (0.00 B)
Burada Embedding katmanının çıktısının (250, 64) boyutunda bir matris olduğu görülmektedir. Her yazının 250 sözcük uzunluğunda
olduğunu anımsayınız. Her sözcük de 64 elemanlı bir vektörle temsil edilmektedir. Embedding katmanındaki eğitilebilir parametrelerin
sayısı 6,521,344 biçiminde rapor edilmiştir. Bu sayıyı anlayabilmek için Embedding katmanının içsel çalışmasını az çok bilmek
gerekir. Ancak bu sayı toplam vocabulary uzunluğu ile vektör uzunluğunun çarpımı kadardır. Bizim örneğimizde toplam vocabulary
2024-10-03 14:16:20 +03:00
uzunluğu (len(cv.vocabulary_) + 1 ) = 1,018,96 kadardır. Bu değeri 64 ile çarptığımızda 6,521,344 değeri elde edilmektedir.
Reshape katmanının modele ek bir eğitilebilir parametre eklemediğine dikkat ediniz. Birinci saklı katmanın girdisinde 250 * 64
nörün vardır. Bu katmanda toplam 256 nöron bulunduğuna göre bu katmandaki eğitilebilir parametrelerin sayısı 250 * 64 * 256
+ 256 = 4,096,256 kadardır. İkinci saklı katmanın girdisi 256 nörondur. Burada 256 nöron olduğuna göre ikinci saklı katmandaki
eğitilebilir parametrelerin sayısı 256 * 256 + 256 = 65,792 biçimindedir. Nihayet çıktı katmanının giridisi 256 nöron çıktısı
da 1 nöron olduğuna göre bu katmandaki eğitilebilir parametrelerin sayısı 256 * 1 + 1 = 257 olacaktır.
Şimdi de compile ve fit işlemlerini yapalım:
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
from tensorflow.keras.callbacks import EarlyStopping
esc = EarlyStopping(monitor="val_loss", 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])
Burada üst üste 5 kez val_loss değeri iyileştirilmezse eğitim sonlandırılmaktadır. Kestirim işleminde yine yazıların aynı
biçimde indekslere dönüştürülüp predict metoduna verilmesi gerekir:
2024-09-16 10:49:44 +03:00
predict_df = pd.read_csv('predict-imdb.csv')
predict_text_vectors = [[cv.vocabulary_[word] + 1 for word in re.findall(r'(?u)\b\w\w+\b', text.lower())]
for text in predict_df['review']]
predict_dataset_x = pad_sequences(predict_text_vectors, TEXT_SIZE, dtype='float32')
predict_result = model.predict(predict_dataset_x)
2024-09-16 10:49:44 +03:00
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
Geri kalan işlemler benzer biçimde yürütülecektir. Aşağıda örneği bir bütün haline veriyoruz.
2024-09-16 10:49:44 +03:00
#----------------------------------------------------------------------------------------------------------------------------
TEXT_SIZE = 250
WORD_VECT_SIZE = 64
2024-09-16 10:49:44 +03:00
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 re
text_vectors = [[cv.vocabulary_[word] + 1 for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
2024-09-16 10:49:44 +03:00
from tensorflow.keras.utils import pad_sequences
dataset_x = pad_sequences(text_vectors, TEXT_SIZE, dtype='float32')
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
2024-09-16 10:49:44 +03:00
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 Input, Embedding, Reshape, Dense
2024-09-16 10:49:44 +03:00
model = Sequential(name='IMBD-WordEmbedding')
model.add(Input((TEXT_SIZE, ), name='Input'))
model.add(Embedding(len(cv.vocabulary_) + 1, WORD_VECT_SIZE, name='Embedding'))
model.add(Reshape((-1, ), name='Reshape'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, activation='relu', name='Hidden-2'))
2024-09-16 10:49:44 +03:00
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(monitor="val_loss", patience=5, restore_best_weights=True)
2024-09-16 10:49:44 +03:00
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=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
2024-09-16 10:49:44 +03:00
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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
2024-09-16 10:49:44 +03:00
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation Accuracy'])
2024-09-16 10:49:44 +03:00
plt.show()
eval_result = model.evaluate(test_dataset_x, test_dataset_y)
2024-09-16 10:49:44 +03:00
for i in range(len(eval_result)):
print(f'{model.metrics_names[i]}: {eval_result[i]}')
predict_df = pd.read_csv('predict-imdb.csv')
predict_text_vectors = [[cv.vocabulary_[word] + 1 for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in predict_df['review']]
predict_dataset_x = pad_sequences(predict_text_vectors, TEXT_SIZE, dtype='float32')
predict_result = model.predict(predict_dataset_x)
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Aslında daha önce görmüş olduğumuz TextVectorization katmanıyla bu işlemler daha kolay yapılabilmektedir. TextVectorization
sınıfının __init__ metodunun parametrik yapısını yeniden anımsaymak istiyoruz:
tf.keras.layers.TextVectorization(
max_tokens=None,
standardize='lower_and_strip_punctuation',
split='whitespace',
ngrams=None,
output_mode='int',
output_sequence_length=None,
pad_to_max_tokens=False,
vocabulary=None,
idf_weights=None,
sparse=False,
ragged=False,
encoding='utf-8',
name=None,
**kwargs
)
Anımsanacağı gibi burada output_mode parametresi "int" olarak geçildiğinde (default durum) aslında TextVectorization katmanı
vektör oluşturmak yerine onların indeks numaralarını oluşturuyordu. Bu katman pad_sequences işlemini de kendisi yapmaktadır.
Eğer katmanda output_sequence_length parametresi spesifik bir değer olarak girilirse padding otomatik olarak yapılmaktadır.
Metodun max_tokens parametresi sözcük sayısını üst bir limitte kısıtlamak için kullanılmaktadır. İşte eğer bu katman kullanılırsa
artık girdi katmanına doğrudan yazılar verilir. Yani bu katman zaten bizim yukarıda CountVectorizer ile yaptığımız işlemleri
2024-10-03 14:16:20 +03:00
kendisi yapmaktadır. Bu durumda TextVectorization katmanın kullanıldığı model şöyle oluşturulabilir:
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, TextVectorization, Embedding, Dense, Reshape
tv = TextVectorization(output_sequence_length=TEXT_SIZE, output_mode='int')
tv.adapt(dataset_x)
model = Sequential(name='IMBD-WordEmbedding')
model.add(Input((1, ), dtype='string', name='Input'))
model.add(tv)
model.add(Embedding(tv.vocabulary_size(), WORD_VECT_SIZE, name='Embedding'))
model.add(Reshape((-1, ), name='Reshape'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
Vocabulary'nin TextVectorization nesnesi üzerinde adapt işleminden sonra oluşturulduğunu anımsayınız. Biz toplam
vocabulary genişliğini sınıfın vocabulary_size() metodu ile elde edip Embedding katmanına ilettik. Tabii TextVectorization
katmanını kullandıktan sonra artık kestirim işleminde yalnızca kestirim yapılacak yazıları predict metoduna vermeliyiz:
predict_df = pd.read_csv('predict-imdb.csv')
predict_result = model.predict(predict_df['review'])
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
2024-10-03 14:16:20 +03:00
TextVectoriation sınıfı default olarak sözcükleri boşluk karakterlerinden ayırmakta ve bazı özel ayırma biçimlerini de
kullanmaktadır. Bu sınıfın sözcükleri ayırmasındaki default davranış bazı yazılar için uygun olmayabilir. Bu örneğimizdeki
başarı (binary accuracy) bizim sözcükleri kendimizin ayırdığı CountVectorizer örneğine kıyasla oldukça kötü çıkmıştır. Bunun
nedeni muhtemelen sözcüklerin elde edilme biçimidir.
TextVectorization sınıfında __init__ metodunun split parametresine eğer bir çağrılabilir (callable) nesne girilirse bu durumda
uygulamacı kendi ssözcük ayırma yönteminin kullanılmasını sağlayabilmektedir. Aynı biçimde metodun standardize parametresine
de çağrılabilir bir nesne girilebilmektedir. Ancak bu parametreye girilen çağrılabilir nesneye tüm yazı tek bir tensör biçiminde
geçirilmektedir. Bu paramterler için fonksiyon yazımı Tensorflow bilgisi gerektirebilmektedir.
Örnek bir bütün olarak aşağıda verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
TEXT_SIZE = 250
WORD_VECT_SIZE = 64
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
dataset_x = df['review']
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
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 Input, TextVectorization, Embedding, Dense, Reshape
tv = TextVectorization(output_sequence_length=TEXT_SIZE, output_mode='int')
tv.adapt(dataset_x)
model = Sequential(name='IMBD-WordEmbedding')
model.add(Input((1, ), dtype='string', name='Input'))
model.add(tv)
model.add(Embedding(tv.vocabulary_size(), WORD_VECT_SIZE, name='Embedding'))
model.add(Reshape((-1, ), name='Reshape'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, 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 EarlyStopping
esc = EarlyStopping(monitor="val_loss", 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=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation 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_df = pd.read_csv('predict-imdb.csv')
predict_result = model.predict(predict_df['review'])
for presult in predict_result[:, 0]:
if (presult > 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 birbine uzak sözcüklerin Öklit uzaklıklarının
daha uzak biçimde olmasını sağlamaktadır. Biz IMDB örneğinden hareketle bu durumu görsel olarak da temsil edebiliriz. Tabii
bu işlemi yaparken sözcüklerin 2 elemanlı vektörlerle ifade edilmesini sağlamamız gerekir. Aşağıdaki örnekte önce IMDB
modeli eğitilmiş sonra da Embedded katmanına girdi uygulanıp çıktı elde edilmiştir. Bu çıktılar da saçılma grafiğinde
2024-10-03 14:16:20 +03:00
gösterilmiştir. Elde edilen grafik incelendiğinde birbirine yakın anlamlı sözcüklerin grafikte birbirine daha yakın
olduğunu, uzak anlamlı sözcüklerin biribirinden daha uzak konumlandırıldığını göreceksizniz.
#----------------------------------------------------------------------------------------------------------------------------
TEXT_SIZE = 250
WORD_VECT_SIZE = 64
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 re
text_vectors = [[cv.vocabulary_[word] + 1 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, dtype='float32')
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
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 Input, Embedding, Reshape, Dense
model = Sequential(name='IMBD-WordEmbedding')
model.add(Input((TEXT_SIZE, ), name='Input'))
model.add(Embedding(len(cv.vocabulary_) + 1, WORD_VECT_SIZE, name='Embedding'))
model.add(Reshape((-1, ), name='Reshape'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, 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 EarlyStopping
esc = EarlyStopping(monitor="val_loss", 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=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation 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_df = pd.read_csv('predict-imdb.csv')
predict_text_vectors = [[cv.vocabulary_[word] + 1 for word in re.findall(r'(?u)\b\w\w+\b', text.lower())]
for text in predict_df['review']]
predict_dataset_x = pad_sequences(predict_text_vectors, TEXT_SIZE, dtype='float32')
predict_result = model.predict(predict_dataset_x)
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
2024-10-03 14:16:20 +03:00
#----------------------------------------------------------------------------------------------------------------------------
66. Ders - 28/09/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yukarıda da belirttiğimiz gibi aslında word embedding vektörlerini sıfırdan oluşturmak yerine zaten oluşturulmuş olan
2024-10-03 14:16:20 +03:00
vektörleri de kullanabiliriz. Çeşitli diller için önceden oluşturulmuş geniş kapasiteli ve büyük veri kümeleriyle eğitilmiş
hazır vektörler bulunmaktadır. Örneğin Facebook'un "fasttext" algoritması kullanılarak hazırlanmış vektörler aşağıdaki
bağlantıdan indirilebilir:
https://fasttext.cc/docs/en/crawl-vectors.html
Glove algoritması ile hazırlanmış olan vektörleri de aşağıdaki bağlantıdan indiribeilirsiniz:
https://nlp.stanford.edu/projects/glove/
2024-10-03 14:16:20 +03:00
Benzer çalışmalar başka kurumlar tarafından da yapılmıştır. Internet'te çeşitli alternatifleri bulabilirsiniz.
2024-10-03 14:16:20 +03:00
Genellikle bu sitelerden indirilen word embedding vektörleri text bir formattadır. İlgili text dosyanın her satırında da
bir sözcük ve o sözcüğüe ilişkin vektör değerleri kodlanmıştır. Yani tipik bir dosyanın bir satırının görünümü şöyledir:
2024-10-03 14:16:20 +03:00
<sözcük> <değer> <değer> <değer> <değer> ....
2024-10-03 14:16:20 +03:00
Bu tür dosyaların başında genellikle iki elemanlı bir başlık kısmı bulunmaktadır. Burada toplam sözcük sayısı ve bir sözcüğün
hangi uzunlukta vektörle ifade edileceği bilgisi yer almaktadır. Örneğin İngilizce için fasttext'ten indirdiğimiz hazır
word embedding vektör dosyasının başlık kısmı şöyledir:
2024-10-03 14:16:20 +03:00
2000000 300
2024-10-03 14:16:20 +03:00
Burada toplam 2,000,000 sözcük için veektörler bulunmaktadır. (Yani dosya toplam 2,000,000 satır büyüklüğündedir.) Her sözcük
300 eleman uzunluğunda vektörden oluşmaktadır. İngilizce'de yaklaşık 800,000 sözcük vardır. Ancak bu vektörlerde yalnızca
sözcükler değil özel isimler, tireli sözcükler, kısaltmalar da bulunmaktadır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-10-03 14:16:20 +03:00
Hazır word embedding vektörlerini kullanmak için yapılacak ilk işlem vektörlerin bulunduğu dosyayı okuyup onu bir Python
sözlüğü haline getirmektir. Burada sözlüğün anahtarları sözcüklerden değerleri de o sözcüğün hazır vektör değerlerinden
oluşturulabilir. Bu işlemi şöyle yapabiliriz:
FASTTEXT_WORD_EMBEDDING_FILE = R'C:\Users\aslan\Downloads\cc.en.300.vec'
import numpy as np
we_dict = {}
with open(FASTTEXT_WORD_EMBEDDING_FILE, 'r', encoding='utf-8') as f:
for line in f:
tokens = line.rstrip().split(' ')
we_dict[tokens[0]] = np.array([float(vecdata) for vecdata in tokens[1:]], dtype='float32')
Burada FASTTEXT_WORD_EMBEDDING_FILE bizim aşağıdaki adresten indirip açtığımız text dosyanın yol ifadesini belirtmektedir.
(Bu dosya çok uzun olduğu için dosyayı kursumuzun ilgili klasörüne dahil etmedik.):
https://fasttext.cc/docs/en/crawl-vectors.html
Dosya buradaki liste içerisindeki "English: bin, text" bağlantısından indirilmiştir. Bu dosyada her bir İngilizce sözcük
300'lük bir vektörle oluşturulmuştur.
Pekiyi biz neden bu dosyayı doğrudan Pandas'la okuyup DataFrame nesnesi yapmadık da onu satır satır okuyup bir sözlük
nesnesi haline getirdik? İşte aslında izleyen paragraflarda da açıklayacağımız gibi biz bu hazır vektör dosyasının hepsini
değil gerekli satırlarını alıp kullanacağız. Gerekli satırları bulmak için böylesi büyük bir dosyadan elde edilen DataFrame
nesnesi üzerinde sıralı arama uygulamak çok yavaş bir yöntemdir. Hızlı arama için sözlük nesneleri kullanılmalıdır. (Tabii
dosyayı önce DataFrame haline getirip sonra bundan bir sözlük oluşturmak iyi bir fikir değildir. Çünkü bu durumda DataFrame
nesnesi de bellekte çok yer kaplayacaktır.)
Pekiyi bundan sonra ne yapacağız? Anımsanacağı gibi Embedding katmanının girdisi aslında sözük numaralarından oluşmaktadır.
Biz bu sözcük numaralarını ya manuel olarak CountVectorizer sınıfını kullanarak oluşturduk ya da hazır TextVecorization
katmanının oluşturmasını sağladık. İşte Embedding katmanında weights isimli parametre önceden hazırlanmış olan vektörlerin
kullanılmasını sağlamak için bulundurulmuştur. Eğer biz bu parametreye önceden hazırlanmış vektör matrisini girersek bu
katman doğrudan bu matristeki vektörleri kullanacaktır. Örneğin:
model.add(Embedding(VOCAB_LEN, WORD_VECT_SIZE, weights = [pretrained_matrix], name='Embedding'))
Buradaki pretrained_matrix hazır vektörlerden elde edilmiş matrisi temsil etmektedir. Bu tür durumlarda uygulamacı artık
Embedding katmanını eğitminden çıkartmak da isteyebilir. Ne de olsa zaten veektörler hazır bir biçimde verilmiştir. İşte
katman nesnelerinde trainable isimli bir parametre ve bu parametreyi temsil eden bir öznitelik vardır. Eğer bu parametre
ya da öznitelik False biçimde geçilirse ilgili katman "eğitimde yokmuş gibi ancak kestirim ve test işlemlerinde
varmış gibi" ele alınmaktadır. O halde Embedding katmanı hazır vektörlerle şöyle kullanılabilir:
model.add(Embedding(VOCAB_LEN, WORD_VECT_SIZE, weights = [pretrained_matrix], trainable=False, name='Embedding'))
Tabii uygulamacı hem hazır vektörleri kullanıp hem de onları eldeki veri kümesine göre iyileştirmek de isteyebilir. Bu
durumda trainable patametresi False geçilmez. Bu parametrenin default durumu zaten True biçimdedir.
Fakat burada dikkat edilmesi gereken başka bir nokta daha vardır. Bizim weights parametresiyle girdiğimiz önceden eğitilmiş
vektörlerin matristeki satır numaralarıyla sözüklerin numaralarının örtüşmesi gerekir. Yani örneğin IMDB veri kümesinde
"fine" sözcüğünün numarası 1172 ise bizim pretained_matrix ismiyle oluşturduğumuz matrisin 1172'inci satırı "fine" sözcüğüne
ilişkin vektör olmalıdır. O halde bizim dosyadan hareketle elde ettiğimiz vektörlerden kendi veri kümemizdeki sözüklere
karşı gelen sayılarla uyumlu bir matris elde etmemiz gerekir. Eğer biz katman olarak TextVectorization katmanını kullanıyorsak
bu katman nesnesindeki get_vocabulary metodu bize zaten numaralarla uyumlu sözcük listesini vermektedir. O halde biz bu listeden
hareketle bir döngü içerisinde weights parametresi için gereken matrisi (pretrained_matrix) aşağıdaki gibi oluştuabiliriz:
pretrained_matrix = np.zeros((len(vocab_list), WORD_VECT_SIZE), dtype='float32')
for index, word in enumerate(vocab_list):
vect = we_dict.get(word)
if vect is None:
vect = np.zeros(WORD_VECT_SIZE)
pretrained_matrix[index] = vect
Burada önce IMDB'deki sözcüklerin sayısı kadar satıra sahip ve önceden eğitilmiş word embedding vektörlerinin uzunluğu
kadar (örneğimizde 300) sütuna sahip içi sıfırlarla dolu bir matris oluşturulmuştur. Sonra IMDB'deki sözcükler önceden
eğitilmiş vektörlerin bulunduğu sözlükte aranmış ve oradan alınarak aynı sırada matrisin satırlarına yerleştirilmiştir.
Örneğin bütün olarak kodları aşağıda verilmiştir. Ancak burada TextVectorization katmanındaki sözcük ayırmanın yeteri
kadar iyi olmamasından kaynaklanan bir performan düşümü söz konusu olabilecektir.
#----------------------------------------------------------------------------------------------------------------------------
TEXT_SIZE = 250
WORD_VECT_SIZE = 300
FASTTEXT_WORD_EMBEDDING_FILE = R'C:\Users\aslan\Downloads\cc.en.300.vec'
import numpy as np
we_dict = {}
with open(FASTTEXT_WORD_EMBEDDING_FILE, 'r', encoding='utf-8') as f:
for line in f:
tokens = line.rstrip().split(' ')
we_dict[tokens[0]] = np.array([float(vecdata) for vecdata in tokens[1:]], dtype='float32')
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 re
text_vectors = [[cv.vocabulary_[word] + 1 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, dtype='float32')
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
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 numpy as np
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Embedding, Reshape, Dense
2024-10-03 14:16:20 +03:00
pretrained_matrix = np.zeros((len(cv.vocabulary_), WORD_VECT_SIZE), dtype='float32')
for index, word in enumerate(cv.vocabulary_):
vect = we_dict.get(word)
if vect is None:
vect = np.zeros(WORD_VECT_SIZE)
pretrained_matrix[index] = vect
model = Sequential(name='IMBD-WordEmbedding')
model.add(Input((TEXT_SIZE, ), name='Input'))
model.add(Embedding(len(cv.vocabulary_), WORD_VECT_SIZE, weights=[pretrained_matrix], name='Embedding'))
model.add(Reshape((-1, ), name='Reshape'))
2024-10-03 14:16:20 +03:00
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, 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 EarlyStopping
esc = EarlyStopping(monitor="val_loss", 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=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation 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_df = pd.read_csv('predict-imdb.csv')
predict_text_vectors = [[cv.vocabulary_[word] + 1 for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in predict_df['review']]
predict_dataset_x = pad_sequences(predict_text_vectors, TEXT_SIZE, dtype='float32')
predict_result = model.predict(predict_dataset_x)
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
67. Ders - 29/09/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Yukarıdaki TextVectorization sınıfı kullanılarak yapılan örneğin performansı sözcük ayırmalardaki sorun yüzünden düşük
kalmıştır. Aşağıda aynı örnek bu katman kullanılmadan yapılmıştır.
#----------------------------------------------------------------------------------------------------------------------------
TEXT_SIZE = 250
WORD_VECT_SIZE = 300
FASTTEXT_WORD_EMBEDDING_FILE = R'C:\Users\aslan\Downloads\cc.en.300.vec'
import numpy as np
2024-10-03 14:16:20 +03:00
we_dict = {}
with open(FASTTEXT_WORD_EMBEDDING_FILE, 'r', encoding='utf-8') as f:
for line in f:
tokens = line.rstrip().split(' ')
we_dict[tokens[0]] = np.array([float(vecdata) for vecdata in tokens[1:]], dtype='float32')
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 re
text_vectors = [[cv.vocabulary_[word] + 1 for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
2024-10-03 14:16:20 +03:00
from tensorflow.keras.utils import pad_sequences
dataset_x = pad_sequences(text_vectors, TEXT_SIZE, dtype='float32')
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
2024-10-03 14:16:20 +03:00
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 numpy as np
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Input, Embedding, Reshape, Dense
2024-10-03 14:16:20 +03:00
pretrained_matrix = np.zeros((len(cv.vocabulary_), WORD_VECT_SIZE), dtype='float32')
for index, word in enumerate(cv.vocabulary_):
vect = we_dict.get(word)
if vect is None:
vect = np.zeros(WORD_VECT_SIZE)
pretrained_matrix[index] = vect
model = Sequential(name='IMBD-WordEmbedding')
model.add(Input((TEXT_SIZE, ), name='Input'))
model.add(Embedding(len(cv.vocabulary_), WORD_VECT_SIZE, weights=[pretrained_matrix], name='Embedding'))
model.add(Reshape((-1, ), name='Reshape'))
2024-10-03 14:16:20 +03:00
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, 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 EarlyStopping
esc = EarlyStopping(monitor="val_loss", 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=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation 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_df = pd.read_csv('predict-imdb.csv')
predict_text_vectors = [[cv.vocabulary_[word] + 1 for word in re.findall(r'(?u)\b\w\w+\b', text.lower())]
for text in predict_df['review']]
2024-10-03 14:16:20 +03:00
predict_dataset_x = pad_sequences(predict_text_vectors, TEXT_SIZE, dtype='float32')
predict_result = model.predict(predict_dataset_x)
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
2024-10-03 14:16:20 +03:00
#----------------------------------------------------------------------------------------------------------------------------
İçerisinde zamana dayalı bilgilerin bulunduğu veri kümelerine zamansal veri kümeleri (temporal data set) denilmektedir.
Eğer bir zamansal veri kümesindeki her satırın zamansal verisi bir sıra izliyorsa bu tür veri kümeleri de genellikle
"zaman serileri (time series)" biçiminde isimlendirilmektedir. Örneğin yağmurun yağıp yağmayacağını tahmin etmek için her
10 dakikada bir hava durumuna ilişkin ölçüm alındığını düşünelim. Bu ölçüm verileri zamansal verilerdir ve bunlara "zaman
serileri" de denilmektedir. Çünkü bu ölçümler birbirinden kopuk değil zaman içerisinde birbirlerini 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şılamayabilir. Ancak geriye doğru bir grup ölçüm bize gidişat hakkında daha iyi bilgiler verebilecektir. İşte eğitim
sırasında verilerin kopuk kopuk değil peşi sıra bir bağlam içerisinde değerlendirilmesi gerekir. Biz daha önce resimsel
veriler üzerinde resmin pixel'lerini ilişkilendirebilmek için "evrişim (convoluiton)" uygulamıştık. İşte zaman serisi verileri
için de benzer biçimde evrişim uygulanabilmektedir. Böylesi bir evrişim işlemi zaman serisi verilerinin tek tek değil birbiriyle
ilişkili biçimde ele alınmasını sağlamaktadır.
Aslında zamansal veriler geniş bir tanımla "yağmurun yağıp yağmayacağına ilişkin 10'ar dakikalık ölçümler" gibi olmak zorunda
değildir. Yazılardaki sözcükler de bu bağlamda zamansal verilere benzemektedir. Yazıdaki sözcükler ondan önce gelen ve ondan
sonra gelen sözcüklerle ilişkilendirilirse daha iyi anlamlandırı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 filtrenin tek boyutta kaydırılması demektir.
Metin anlamlandırma işlemlerinde de tek boyutlu evrişim uygulanmaktadır.
Tek boyutlu evrişim işleminde filtre büyüklüğü tek boyutludur (yani tek bir sayıdan 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
...............
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:
2024-10-03 14:16:20 +03:00
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. Asıl matrisin satır sayısının N olduğunu filtrenin (kernel) satır sayısının da K
olduğunu varsayalım. Bu durumda "padding uygulandığında" elde edilecek matris (N, 1) boyutunda, "padding uygulanmmadığında"
ise (N - K + 1, 1) boyutunda olacaktır. Örneğin biz biz 6 sözcük uzunluğundaki yazıların sözcüklerini word embedding yöntemi
ile 8 elemanlı vektörle ifade etmiş olalım. Bu durumda yazımız aşağıdaki gibi bir görüntüye sahip olacaktır:
XXXXXXXX -> sözcük
XXXXXXXX -> sözcük
XXXXXXXX -> sözcük
XXXXXXXX -> sözcük
XXXXXXXX -> sözcük
XXXXXXXX -> sözcük
Şimdi biz 2 uzunlukta bir filtre ile tek boyutlu evrişim uygulamak isteyelim. Bu durumda filtenin yapısı şöyle olacaktır:
FFFFFFFF
FFFFFFFF
Biz bu filtreyi "padding uygulamadan" yukarıdan aşağıya doğru gezdirirsek aşağıdaki gibi bir vektör elde ederiz:
R
R
R
R
R
Burada R değerleri filtre matrisi ile sözcüklere ilişkin word embedding matrisinin çakıştırılması ile uygulanan "dot-product"
ve sonrasında uygulanan aktivasyon fonksiyonunun çıktısını temsil etmektedir. Biz böylece (6, 8)'lik matris yerine (5, 1)'lik
bir matris elde etmiş olduk. Tabii biz birden fazla filtre de uygulayabiliriz. Örneğin toplamda 16 filtre uygularsak elde
edeceğimiz matris (16, 5, 1) boyutunda olacaktır. Biz bu katmanda birden fazla filtre uyguladığımız zaman çıktı da yine aşağdaki
gibi çok boyutlu bir yapıda olacaktır:
R R R R R R R .... R R R
R R R R R R R .... R R R
R R R R R R R .... R R R
R R R R R R R .... R R R
.....
Burada çıktının sütun sayısı uygulanan filtrenin sayısı kadardır.
2024-10-03 14:16:20 +03:00
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 parametrik
yapısı şöyledir:
tf.keras.layers.Conv1D(
filters,
kernel_size,
strides=1,
padding='valid',
data_format=None,
dilation_rate=1,
groups=1,
activation=None,
use_bias=True,
kernel_initializer='glorot_uniform',
bias_initializer='zeros',
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
**kwargs
)
Metodun ilk parametresi filtre sayısını, ikinci parametresi filtrenin (kernel) boyutunu belirtmektedir. Tabii burada
boyut tek bir sayıdan oluşur (yani yukarıdaki örnekte filtrenin satır uzunluğu). Yine metodun strides ve padding parametreleri
vardır. Bu padding parametresi "valid" ise padding uygulanmaz, "same" ise padding uygulanır. stride değeri yukarıdan aşağıya
kaydırmanın kaçar kaçar yapılacağını belirtmektedir. Bu parametrenin default değeri 1'dir.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
68. Ders - 05/10/2024 - Cumartesi
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda IMDB örneğinde word embedding yapıldıktan sonra bir kez tek boyutlu evrişim işlemi uygulanmıştır. Modelin katmanları
şöyledir:
2024-10-03 14:16:20 +03:00
TextVectorization --> Embedding --> Conv1D --> Flatten/Reshape --> Dense --> Dense --> Dense (Output)
2024-10-03 14:16:20 +03:00
Model Keras'ta aşağıdaki gibi oluşturulmuştur:
2024-10-03 14:16:20 +03:00
tv = TextVectorization(output_sequence_length=TEXT_SIZE, output_mode='int')
tv.adapt(dataset_x)
2024-10-03 14:16:20 +03:00
model = Sequential(name='IMBD-WordEmbedding')
model.add(Input((1, ), dtype='string', name='Input'))
model.add(tv)
model.add(Embedding(tv.vocabulary_size(), WORD_VECT_SIZE, name='Embedding'))
model.add(Conv1D(128, 3, activation='relu', padding='same', name='Conv1D'))
model.add(Reshape((-1, ), name='Reshape'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
2024-10-03 14:16:20 +03:00
Burada görüldüğü gibi Embedding katmanından sonra Conv1D katmanı uygulanmıştır. Modelin özet (summary) görünümü şöyledir:
2024-10-03 14:16:20 +03:00
Model: "IMBD-WordEmbedding"
┌─────────────────────────────────┬────────────────────────┬───────────────┐
│ Layer (type) │ Output Shape │ Param # │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ text_vectorization_2 │ (None, 250) │ 0 │
│ (TextVectorization) │ │ │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Embedding (Embedding) │ (None, 250, 64) │ 11,630,208 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Conv1D (Conv1D) │ (None, 250, 128) │ 24,704 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Reshape (Reshape) │ (None, 32000) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Hidden-1 (Dense) │ (None, 256) │ 8,192,256 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Hidden-2 (Dense) │ (None, 256) │ 65,792 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Output (Dense) │ (None, 1) │ 257 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 19,913,217 (75.96 MB)
Trainable params: 19,913,217 (75.96 MB)
Non-trainable params: 0 (0.00 B)
Burada TextVectorization katmanının girdisi yazılardan, çıktısı ise bu yazıların sözcük numaralarından oluşmaktadır.
Bu sözcük numaraları Embedding katmanına girdi olarak verilmektedir. Embedding katmanının da çıktısının (250, 64) boyutunda
vektörler olduğunu görüyorsunuz. Anımsanacağı gibi bu katmandaki eğitilebilir parametrelerin sayısı vocabulary uzunluğu ile
vektör uzunluğunun çarpımı kadardır. Burada bu katman için rapor edilen eğitilebilir parametrelerin sayısının 11,639,208
olduğu görülmektedir. Modelimizdeki sözcük haznesi (vocabulary) uzunluğu TextVectorization get_vocabulary() metodu ile elde
edildiğinde 181722 olduğu görülmektedir. O halde bu katmandaki eğitilebilir parametrelerin sayısı 181722 * 64 = 11,630,208
değeri elde edilmektedir. Conv1D katmanının çıktısının (250, 128) boyutlarında olduğunu görüyorsunuz. Eğer bu katmanda tek
bir filtre uygulanmış olsaydı çıktı (250, 1) boyutunda olacaktı. 128 filte uygulandığı için boyut (250, 128) olmuştur.
Şimdi bu katmandaki eğitilebilir parametrelerin sayısını hesaplayalım. Tek boyutlu evrişim işleminde içsel olarak dot-product
işlemindeki eleman sayısı kadar nöron bulunacaktır. Örneğimizde bir filtre için 64 * 3 tane nöron söz konusudur. Tabii
dot-product yapıldıktan sonra bu bir bias değerle toplanacağından bir filtre için eğitilebilir parametrelerin sayısı 64 * 3 + 1
tane olacaktır. Evirişim işleminde hep aynı filtre matrisinin kaydırıldığını anımsayınız. Örneğimizde toplam 128 farklı filtre
vardır. O halde burada toplam eğitilebilir parametrelerin sayısı (64 * 3 + 1) * 128 = 24704 olacaktır. Conv1D katmanının
çıktısının (250, 128) boyutunda bir matris olduğunu belirtmiştik. Bu çıktı Reshape katmanı ile (Flatten katmanının bilinmeyen
bir problem yarattığından bahsetmiştik) tek boyutlu hale getirilmiştir. O halde Reshape katmanının çıktısı 150 * 128 = 32000
biçimindedir. Tabii Reshape katmanında herhangi bir eğitilebilir parametre yoktur. Sonra birinci Dense katmandaki eğitilabilir
parametrelerin sayısı 32000 * 256 + 256 = 8,192,256 tanedir. Bu katmanın 256'lık bir bir nörondan oluşmaktadır. Bu 266 nöron
sonraki Dense katmana sokulduğunda buradaki eğitilebilir parametrelerin sayısı 256 * 256 + 256 = 65,792 kadardır. Bu Dense
katmanın çıktısında 256 nöron bulunmaktadır. Nihayet çıktı katmanına 256 nöron girip 1 nöron çıkmaktadır. Buradaki eğitilebilir
parametrelerin sayısı 256 * 1 + 1 = 257 tabedir.
Uygulamanın tüm kodları aşağıda verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
2024-10-03 14:16:20 +03:00
TEXT_SIZE = 250
WORD_VECT_SIZE = 64
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
dataset_x = df['review']
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
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 Input, TextVectorization, Conv1D, Embedding, Dense, Reshape
tv = TextVectorization(output_sequence_length=TEXT_SIZE, output_mode='int')
tv.adapt(dataset_x)
model = Sequential(name='IMBD-WordEmbedding')
model.add(Input((1, ), dtype='string', name='Input'))
model.add(tv)
model.add(Embedding(tv.vocabulary_size(), WORD_VECT_SIZE, name='Embedding'))
model.add(Conv1D(128, 3, padding='same', name='Conv1D'))
model.add(Reshape((-1, ), name='Reshape'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, 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 EarlyStopping
esc = EarlyStopping(monitor="val_loss", 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=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation 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_df = pd.read_csv('predict-imdb.csv')
predict_result = model.predict(predict_df['review'])
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Aslında tıpkı resimlerde olduğu gibi metinsel ve zamansal verilerde de evrişim işlemi sonrasında eğitilebilir parametreleri
azaltmak ve bazı nitelikleri belirgin hale getirmek için "pooling" işlemleri uygulanabilmektedir. Tabii buradaki pooling
işlemleri iki boyutlu değil tek boyutludur. Tek boyutlu "pooling" işlemleri için MaxPooling1D ve AveragePooling1D sınıfları
bulundurulmuştur.
Tek boyutlu pooling işlemlerinde pool_size parametresi için tek bir sayı girilmektedir. 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 yani sütunsal olarak 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
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 girmiş olalım. 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
edilecektir. Yukarıdaki verilerin pool_size 3 alınarak tek boyutlu "pooling" işlemine sokulmasıyla elde edilen matris şöyle
olacaktır:
P P P P P P P P P P ==> ilk üç satırın sütunlarının pooling değerleri
P P P P P P P P P P ==> sonraki üç satırın sütunlarının pooling değerleri
P P P P P P P P P P ==> sonraki üç satırın sütunlarının pooling değerleri
Tıpkı resimsel uygulamalarda olduğu gibi metinsel uygulamalarda ve zamansal uygulamalarda da evrişim ve pooling
işlemleri bir kez değil üst üste birden fazla kez uygulanmaktadır.
Pekiyi metinsel işlemlerde MaxPooling1D katmanı mı yoksa AveragePooling1D katmanı mı tercih edilmelidir? Aslında hedefe
bağlı olarak bu tercih değişebilir. Ancak genel olarak metinsel uygulamalarda MaxPooling1D katmanı tercih edilmektedir.
Max pooling işlemi o bölgedeki en önemli sözcüklere dikkat edilmesini sağlamaktadır. MaxPooling1D ve AveragePooling1D
sınıflarının __init__metotlarının parametrik yapısı şöyledir:
tf.keras.layers.MaxPool1D(
pool_size=2,
strides=None,
padding='valid',
data_format=None,
name=None,
**kwargs
)
ttf.keras.layers.AveragePooling1D(
pool_size,
strides=None,
padding='valid',
data_format=None,
name=None,
**kwargs
)
Metotlardaki pool_size parametresi pooling uygulanacak satır uzunluğunu, strides parametresi kaydırma miktarını belirtmektedir.
Bu parametrelerin default değerleri None biçimindedir. Bu durumda kaydırma pool_size parametresinde belirtilen değer kadar
yapılmaktadır. padding parametreleri yine "same" ya da "valid" biçiminde girilebilmektedir.
Aşağıdaki örnekte IMDB veri kümesi üzerinde yine önce word embedding sonra evrişim ve pooling işlemleri art arda
uygulanmıştır. Modelin katmanları şöyledir:
TextVectorization --> Embedding --> Conv1D --> MaxPooling1D --> Conv1D --> MaxPooling1D --> Conv1D --> MaxPooling1D
Flatten/Reshape --> Dense --> Dense --> Dense (Output)
Model Keras'ta şöyle kurulmuştur:
model = Sequential(name='IMBD-WordEmbedding')
model.add(Input((1, ), dtype='string', name='Input'))
model.add(tv)
model.add(Embedding(tv.vocabulary_size(), WORD_VECT_SIZE, name='Embedding'))
model.add(Conv1D(128, 3, padding='same', name='Conv1D-1'))
model.add(MaxPooling1D(2, padding='same', name='MaxPooling1D-1'))
model.add(Conv1D(128, 3, padding='same', name='Conv1D-2'))
model.add(MaxPooling1D(2, padding='same', name='MaxPooling1D-2'))
model.add(Conv1D(128, 3, padding='same', name='Conv1D-3'))
model.add(MaxPooling1D(2, padding='same', name='MaxPooling1D-3'))
model.add(Reshape((-1, ), name='Reshape'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, activation='relu', name='Hidden-2'))
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
Modelden elde edilen özet görüntü (summary) şöyledir:
Model: "IMBD-WordEmbedding"
┌─────────────────────────────────┬────────────────────────┬───────────────┐
│ Layer (type) │ Output Shape │ Param # │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ text_vectorization │ (None, 250) │ 0 │
│ (TextVectorization) │ │ │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Embedding (Embedding) │ (None, 250, 64) │ 11,630,208 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Conv1D-1 (Conv1D) │ (None, 250, 128) │ 24,704 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ MaxPooling1D-1 (MaxPooling1D) │ (None, 125, 128) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Conv1D-2 (Conv1D) │ (None, 125, 128) │ 49,280 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ MaxPooling1D-2 (MaxPooling1D) │ (None, 63, 128) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Conv1D-3 (Conv1D) │ (None, 63, 128) │ 49,280 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ MaxPooling1D-3 (MaxPooling1D) │ (None, 32, 128) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Reshape (Reshape) │ (None, 4096) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Hidden-1 (Dense) │ (None, 256) │ 1,048,832 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Hidden-2 (Dense) │ (None, 256) │ 65,792 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ Output (Dense) │ (None, 1) │ 257 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 12,868,353 (49.09 MB)
Trainable params: 12,868,353 (49.09 MB)
Non-trainable params: 0 (0.00 B)
Burada MaxPooling1D katmanlarının boyutu iki kat azalttığına dikkat ediniz.
Örnek bir bütün olarak aşağıda verilmiştir.
#----------------------------------------------------------------------------------------------------------------------------
TEXT_SIZE = 250
WORD_VECT_SIZE = 64
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
dataset_x = df['review']
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
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 Input, TextVectorization, Conv1D, MaxPooling1D, Embedding, Dense, Reshape
tv = TextVectorization(output_sequence_length=TEXT_SIZE, output_mode='int')
tv.adapt(dataset_x)
model = Sequential(name='IMBD-WordEmbedding')
model.add(Input((1, ), dtype='string', name='Input'))
model.add(tv)
model.add(Embedding(tv.vocabulary_size(), WORD_VECT_SIZE, name='Embedding'))
model.add(Conv1D(128, 3, padding='same', name='Conv1D-1'))
model.add(MaxPooling1D(2, padding='same', name='MaxPooling1D-1'))
model.add(Conv1D(128, 3, padding='same', name='Conv1D-2'))
model.add(MaxPooling1D(2, padding='same', name='MaxPooling1D-2'))
model.add(Conv1D(128, 3, padding='same', name='Conv1D-3'))
model.add(MaxPooling1D(2, padding='same', name='MaxPooling1D-3'))
model.add(Reshape((-1, ), name='Reshape'))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, 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 EarlyStopping
esc = EarlyStopping(monitor="val_loss", 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=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation 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_df = pd.read_csv('predict-imdb.csv')
predict_result = model.predict(predict_df['review'])
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
AveragePooling1D ve MaxPooling1D katmanlarının global biçimleri de vardır. Bu global pooling katmanları GlobalAveragePooling1D
ve GlobalMaxPooling1D isimleriyle bulundurulmuştur. Tıpkı iki boyutlu evrişim işlemlerinde olduğu gibi tek boyutlu evrişim
işlemlerinde de bu katmanlar tek bir çıktı üretmektedir. Örneğin GlobalAveragePooling1D katmanı toplamda tek bir satır
üretir. Örneğin bu katmanın girdisi (250, 128) boyunda bir matris ise bu durumda bu katman her sütun için o sütunun toplamdaki
en büyük değerini elde edecektir. Bu değerler de toplamda 128 tane olacaktır. Bu katmanlar da bunların iki boyutlularında
olduğu gibi evrişim katmanlarının en sonunda yani Dense katmanlardan hemen önce bulunudurulmalıdır.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
2024-10-11 00:32:36 +03:00
69. Ders - 06/10/2024 - Pazar
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
Aşağıda daha önce yapmış olduğumuz "fasttext"örneğinin bir benzeri verilmiştir. Burada hem önceden eğitilmiş "fasttext"
vektörleri kullanılmıştır hem de evrişim ve pooling katmanları modele eklenmiştir.
#----------------------------------------------------------------------------------------------------------------------------
TEXT_SIZE = 250
WORD_VECT_SIZE = 300
FASTTEXT_WORD_EMBEDDING_FILE = R'C:\Users\aslan\Downloads\cc.en.300.vec'
import numpy as np
we_dict = {}
with open(FASTTEXT_WORD_EMBEDDING_FILE, 'r', encoding='utf-8') as f:
for line in f:
tokens = line.rstrip().split(' ')
we_dict[tokens[0]] = np.array([float(vecdata) for vecdata in tokens[1:]], dtype='float32')
import pandas as pd
df = pd.read_csv('IMDB Dataset.csv')
dataset_x = df['review']
dataset_y = (df['sentiment'] == 'positive').to_numpy(dtype='uint8')
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 Input, TextVectorization, Embedding, Conv1D, MaxPooling1D, Dense, Reshape
tv = TextVectorization(output_sequence_length=TEXT_SIZE, output_mode='int')
tv.adapt(dataset_x)
vocab_list = tv.get_vocabulary()
pretrained_matrix = np.zeros((len(vocab_list), WORD_VECT_SIZE), dtype='float32')
for index, word in enumerate(vocab_list):
vect = we_dict.get(word)
if vect is None:
vect = np.zeros(WORD_VECT_SIZE)
pretrained_matrix[index] = vect
model = Sequential(name='IMBD-WordEmbedding')
model.add(Input((1, ), dtype='string', name='Input'))
model.add(tv)
model.add(Embedding(len(vocab_list), WORD_VECT_SIZE, weights=[pretrained_matrix], name='Embedding'))
model.add(Conv1D(128, 3, padding='same', name='Conv1D-1'))
model.add(MaxPooling1D(2, padding='same', name='MaxPooling1D-1'))
model.add(Conv1D(128, 3, padding='same', name='Conv1D-2'))
model.add(MaxPooling1D(2, padding='same', name='MaxPooling1D-2'))
model.add(Conv1D(128, 3, padding='same', name='Conv1D-3'))
model.add(MaxPooling1D(2, padding='same', name='MaxPooling1D-3'))
model.add(Reshape((-1, )))
model.add(Dense(256, activation='relu', name='Hidden-1'))
model.add(Dense(256, 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 EarlyStopping
esc = EarlyStopping(monitor="val_loss", 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=(14, 6))
plt.title('Epoch - Loss Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 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=(14, 6))
plt.title('Epoch - Binary Accuracy Graph', pad=10, fontsize=14)
plt.xticks(range(0, 300, 10))
plt.plot(hist.epoch, hist.history['binary_accuracy'])
plt.plot(hist.epoch, hist.history['val_binary_accuracy'])
plt.legend(['Accuracy', 'Validation 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_df = pd.read_csv('predict-imdb.csv')
predict_result = model.predict(predict_df['review'])
for presult in predict_result[:, 0]:
if (presult > 0.5):
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
Tek boyutlu evrişim ve pooling işlemleri yalnızca metinsel veri kümelerinde değil aynı zamanda zamansal (temporal)
veri kümelerinde de uygulabilmektedir. Gerçi izlen paragraflarda biz zamansal veriler için daha iyi performans gösteren
geri beslemeli (recurrent) ağları kullanacağız. Ancak burada zamansal veriler üzerinde de tek boyutlu evirişim işlemlerine
bir örnek vermek istiyoruz.
Zamansal verilerle (temporal data) ilgili klasik bir örnek "hava durumunun tahmin edilmesi" olabilir. Örneğin 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 sıcak 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. Tabii biz önceki durumu bilmeden de
o andaki verileri gö zönüne alarak hava sıcaklığını tahmin etmeye çalışabiliriz. Ancak önceki verilerin de dikkate alınması
tahminin daha isabetli yapılmasına yol açmaktadır. Aynı durum örneğin borsalarda da benzerdir. Bir kağıdın ya da kripto paranın
fiyatı birtakım olaylar sonucunda bir bağlam içerisinde değişmektedir. Yani birtakım kestirimlerde yalnızca o andaki duruma
değil geçmişe de bakıp bağlamı da dikkate almak kestirimi güçlendirmektedir. Finansal piyasalar bunlara tipik bir örnek
oluşturmaktadır. Finansal piyasalarda ilgili finansal durum bir bağlam çerçevesinde gelişip bir noktaya gelmektedir.
Hava durumu tahminine örnek için kullanılan veri kümelerinden biri "Jena Climate" ("yena klaymit" biçiminde okunuyor) isimli
bir veri kümesidir . Bu veri kümesi aşağıdaki bağlantıdan indirilebilir:
https://www.kaggle.com/datasets/mnassrib/jena-climate?resource=download
Bu siteden veri kümesi indirilip açıldığında "jena_climate_2009_2016.csv" isimli bir dosya edilecektir. Veri kümesinin görünümü
aşağıdaki gibidir:
"Date Time","p (mbar)","T (degC)","Tpot (K)","Tdew (degC)","rh (%)","VPmax (mbar)","VPact (mbar)","VPdef (mbar)","sh
(g/kg)","H2OC (mmol/mol)","rho (g/m**3)","wv (m/s)","max. wv (m/s)","wd (deg)"
01.01.2009 00:10:00,996.52,-8.02,265.40,-8.90,93.30,3.33,3.11,0.22,1.94,3.12,1307.75,1.03,1.75,152.30
01.01.2009 00:20:00,996.57,-8.41,265.01,-9.28,93.40,3.23,3.02,0.21,1.89,3.03,1309.80,0.72,1.50,136.10
01.01.2009 00:30:00,996.53,-8.51,264.91,-9.31,93.90,3.21,3.01,0.20,1.88,3.02,1310.24,0.19,0.63,171.60
01.01.2009 00:40:00,996.51,-8.31,265.12,-9.07,94.20,3.26,3.07,0.19,1.92,3.08,1309.19,0.34,0.50,198.00
01.01.2009 00:50:00,996.51,-8.27,265.15,-9.04,94.10,3.27,3.08,0.19,1.92,3.09,1309.00,0.32,0.63,214.30
01.01.2009 01:00:00,996.50,-8.05,265.38,-8.78,94.40,3.33,3.14,0.19,1.96,3.15,1307.86,0.21,0.63,192.70
...............................
Dosyada bir başlık kısmı olduğunu görüyorsunuz. Bu veri kümesi 10'ar dakikalık periyotlarla havaya ilişkin birtakım değerlerin
ölçülerek saklanmasıyla oluşturulmuştur. Sütunlardan biri (üçüncü sütun) derece cinsinden hava sıcaklığını belirtmektedir.
Veri kümesinde eksik veri bulunmamaktadı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 satır ilerideki değerlerdir. O halde bizim eğitim verilerini oluştururken her x ile 144 ilerideki satırın 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. Pekiyi zamansal veri hangi ölçek türündendir? İşte tarih ve zaman bilgileri uğraşılan konuya değişik biçimlerde ele
alınabilmektedir. Sürekli artan bir tarih-zaman bilgisinin kestirim modellerinde hiçbir kullanım gerekçesi yoktur. Tarih-zaman
bilgileri genellikle "özellik mÜhendisliği (feature engineering)" teknikleriyle bileşene ayrılır ve bu bileşenler ayrı sütunlar
biçiminde veri kümesine eklenir. Tarih bilgisinin aylara, günlere ya da haftanın günlerine ayrılması ve bunların da kategorik
bir bilgiler gibi ele alınması yaygındır. Yıl bilgisi de yine kategorik bir bilgi olarak ele alınabilir. Buradaki "Jena Climate"
veri kümesinde tarih bilgisinin ay ve gün bileşenlerinden faydalanılabilir. Ölçümün günün hangi 10 dakikasına ilişkin olduğu da
kestirimde önemli bir bilgi oluşturabilmektedir. Gerçi zaman serisi tarzındaki veri kümelerinde zaten biz ağın bu örüntüyü
kendisinin yakalamasını isteriz. Bu nedenle ağın mimarisine göre bu tür bilgilerin önemi değişebilmektedir. Veri kümesinin diğer
sütunları zaten nümerik sütunalardır. Orada bir dönüştürmenin yapılmasına gerek yoktur. Tabii özellik ölçeklemesi uygulamak
gerekir. Buradaki sütunların anlamlandırlması meteorolojiye ilişkin bazı özel bilgilere gereksinim vardır. Biz bu sütunların
anlamları üzerinde burada durmayacağız.
Veri kümesini aşağıdaki gibi okumuş olalım:
import pandas as pd
df = pd.read_csv('jena_climate_2009_2016.csv')
Biz tarih ve zaman bilgisi sütununu Pandas'ın datetime türüne dönüştürebiliriz:
df['Date Time'] = pd.to_datetime(df['Date Time'])
Artık biz bu sütunun bileşenlerini elde edebiliriz. Ancak bu yöntem aslında bizim için daha zahmetlidir. Doğrudan biz
yazının içerisindeki ilgili kısımları yine yazı olarak alıp one-hot-encoding uygulayabiliriz:
df = pd.read_csv('jena_climate_2009_2016.csv')
df['Month'] = df['Date Time'].str[3:5]
df['Hour-Minute'] = df['Date Time'].str[11:16]
df.drop(['Date Time'], axis=1, inplace=True)
df = pd.get_dummies(df, columns=['Month', 'Hour-Minute'], dtype='int8')
dataset = df.to_numpy('float32')
Bir günün kaç 10 dakikadan oluştuğunu aşağıdaki gibi bir değişkenle ifade edebiliriz:
PREDICTION_INTERVAL = 24 * 60 // 10 # 144
Biz evirişim katmanı olarak tek boyutlu Conv1D katmanını kullanacağız. Ancak bu katman bizden girdiyi iki boyutlu
matrisler biçiminde istemektedir. Yani bizim sinir ağına girdileri 144'lük (PREDICTION_INTERVAL) matrisler biçiminde vermemiz
gerekir. dataset_x ve dataset_y veri kümelerini hazırlarken bizim 144'lük peşi sıra giden kaydırmalı bir veri kümesi oluşturmamız
gerekir. Tabii burada kaydırma miktarını istediğimiz gibi alabilir. dataset_x veri kümesinin aşağıdaki gibi bir yapıya sahip
olması gerekir:
<ilk 144'lük satır>
<Sonraki 144'lük satır>
<Sonraki 144'lük satır>
<Sonraki 144'lük satır>
....
Tabii burada oluşturulacak matris çok büyük olabilir. Bunun için kaydırmayı birer değil daha daha geniş uygulayabiliriz.
Ya da bu tür durumlarda parçalı eğitim yoluna gidebiliriz.
<BURADA KALDIK>
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ı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 hafı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'])
2024-09-16 10:49:44 +03:00
import numpy as np
2024-09-16 10:49:44 +03:00
dataset_y = np.zeros(len(df), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
2024-09-16 10:49:44 +03:00
import re
2024-09-16 10:49:44 +03:00
text_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
2024-09-16 10:49:44 +03:00
from tensorflow.keras.utils import pad_sequences
2024-09-16 10:49:44 +03:00
dataset_x = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
from sklearn.model_selection import train_test_split
2024-09-16 10:49:44 +03:00
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
2024-09-16 10:49:44 +03:00
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, Bidirectional, LSTM, Dropout, Flatten, Dense
2024-09-16 10:49:44 +03:00
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'))
2024-09-16 10:49:44 +03:00
model.add(Dense(1, activation='sigmoid', name='Output'))
2024-09-16 10:49:44 +03:00
model.summary()
2024-09-16 10:49:44 +03:00
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
2024-09-16 10:49:44 +03:00
from tensorflow.keras.callbacks import EarlyStopping
2024-09-16 10:49:44 +03:00
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))
2024-09-16 10:49:44 +03:00
plt.title('Epoch-Binary Accuracy Graph', fontsize=14, fontweight='bold')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.xticks(range(0, 210, 10))
2024-09-16 10:49:44 +03:00
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()
2024-09-16 10:49:44 +03:00
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]}')
2024-09-16 10:49:44 +03:00
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']
2024-09-16 10:49:44 +03:00
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)
2024-09-16 10:49:44 +03:00
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.
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
TExT_SIZE = 250
2024-09-16 10:49:44 +03:00
import pandas as pd
2024-09-16 10:49:44 +03:00
df = pd.read_csv('IMDB Dataset.csv')
2024-09-16 10:49:44 +03:00
from sklearn.feature_extraction.text import CountVectorizer
2024-09-16 10:49:44 +03:00
cv = CountVectorizer()
2024-09-16 10:49:44 +03:00
cv.fit(df['review'])
2024-09-16 10:49:44 +03:00
import numpy as np
2024-09-16 10:49:44 +03:00
dataset_y = np.zeros(len(df), dtype='int8')
dataset_y[df['sentiment'] == 'positive'] = 1
2024-09-16 10:49:44 +03:00
import re
2024-09-16 10:49:44 +03:00
text_vectors = [[cv.vocabulary_[word] for word in re.findall(r'(?u)\b\w\w+\b', text.lower())] for text in df['review']]
2024-09-16 10:49:44 +03:00
from tensorflow.keras.utils import pad_sequences
2024-09-16 10:49:44 +03:00
dataset_x = pad_sequences(text_vectors, TExT_SIZE, padding='post', truncating='post')
2024-09-16 10:49:44 +03:00
from sklearn.model_selection import train_test_split
2024-09-16 10:49:44 +03:00
training_dataset_x, test_dataset_x, training_dataset_y, test_dataset_y = train_test_split(dataset_x, dataset_y)
2024-09-16 10:49:44 +03:00
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Embedding, Bidirectional, GRU, Dropout, Flatten, Dense
2024-09-16 10:49:44 +03:00
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'))
2024-09-16 10:49:44 +03:00
model.add(Dense(1, activation='sigmoid', name='Output'))
model.summary()
2024-09-16 10:49:44 +03:00
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['binary_accuracy'])
2024-09-16 10:49:44 +03:00
from tensorflow.keras.callbacks import EarlyStopping
2024-09-16 10:49:44 +03:00
esc = EarlyStopping(patience=5, restore_best_weights=True)
2024-09-16 10:49:44 +03:00
hist = model.fit(training_dataset_x, training_dataset_y, epochs=100, batch_size=32, validation_split=0.2, callbacks=[esc])
2024-09-16 10:49:44 +03:00
import matplotlib.pyplot as plt
2024-09-16 10:49:44 +03:00
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))
2024-09-16 10:49:44 +03:00
plt.plot(hist.epoch, hist.history['loss'])
plt.plot(hist.epoch, hist.history['val_loss'])
plt.legend(['Loss', 'Validation Loss'])
plt.show()
2024-09-16 10:49:44 +03:00
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))
2024-09-16 10:49:44 +03:00
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()
2024-09-16 10:49:44 +03:00
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]}')
2024-09-16 10:49:44 +03:00
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']
2024-09-16 10:49:44 +03:00
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)
2024-09-16 10:49:44 +03:00
for result in predict_result[:, 0]:
if result > 0.5:
print('Positive')
else:
print('Negative')
#----------------------------------------------------------------------------------------------------------------------------
2024-09-16 10:49:44 +03:00
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.
2024-09-16 10:49:44 +03:00
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.
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
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şulul 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 a
nlamı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 bilgi iş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
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------------------------------------